Source: flashphoner-core.js

  1. 'use strict';
  2. const { v1: uuid_v1 } = require('uuid');
  3. const constants = require("./constants");
  4. const util = require('./util');
  5. const LoggerObject = require('./util').logger;
  6. const clientInfo = require('./client-info');
  7. const Promise = require('promise-polyfill');
  8. const KalmanFilter = require('kalmanjs');
  9. const browserDetails = require('webrtc-adapter').default.browserDetails;
  10. const LOG_PREFIX = "core";
  11. var coreLogger;
  12. var loggerConf = {push: false, severity: "INFO"};
  13. var isUsingTemasysPlugin = false;
  14. var clientUAData;
  15. /**
  16. * @namespace Flashphoner
  17. */
  18. const SESSION_STATUS = constants.SESSION_STATUS;
  19. const STREAM_EVENT = constants.STREAM_EVENT;
  20. const STREAM_EVENT_TYPE = constants.STREAM_EVENT_TYPE;
  21. const STREAM_STATUS = constants.STREAM_STATUS;
  22. const CALL_STATUS = constants.CALL_STATUS;
  23. const CONNECTION_QUALITY = constants.CONNECTION_QUALITY;
  24. const ERROR_INFO = constants.ERROR_INFO;
  25. const VIDEO_RATE_GOOD_QUALITY_PERCENT_DIFFERENCE = 20;
  26. const VIDEO_RATE_BAD_QUALITY_PERCENT_DIFFERENCE = 50;
  27. const LOW_VIDEO_RATE_THRESHOLD_BAD_PERFECT = 50000;
  28. const LOW_VIDEO_RATE_BAD_QUALITY_PERCENT_DIFFERENCE = 150;
  29. const OUTBOUND_VIDEO_RATE = "outboundVideoRate";
  30. const INBOUND_VIDEO_RATE = "inboundVideoRate";
  31. const CONSTRAINT_AUDIO = "audio";
  32. const CONSTRAINT_AUDIO_STEREO = "stereo";
  33. const CONSTRAINT_AUDIO_BITRATE = "bitrate";
  34. const CONSTRAINT_AUDIO_FEC = "fec";
  35. const CONSTRAINT_AUDIO_OUTPUT_ID = "outputId";
  36. const CONSTRAINT_VIDEO = "video";
  37. const CONSTRAINT_VIDEO_BITRATE = "video.bitrate";
  38. const CONSTRAINT_VIDEO_MIN_BITRATE = "video.minBitrate";
  39. const CONSTRAINT_VIDEO_MAX_BITRATE = "video.maxBitrate";
  40. const CONSTRAINT_VIDEO_QUALITY = "video.quality";
  41. const CONSTRAINT_VIDEO_WIDTH = "video.width";
  42. const CONSTRAINT_VIDEO_HEIGHT = "video.height";
  43. var MediaProvider = {};
  44. var sessions = {};
  45. var initialized = false;
  46. var disableConnectionQualityCalculation;
  47. /**
  48. * Static initializer.
  49. *
  50. * @param {Object} options Global api options
  51. * @param {Function=} options.mediaProvidersReadyCallback Callback of initialized WebRTC Plugin
  52. * @param {String=} options.flashMediaProviderSwfLocation Location of media-provider.swf file
  53. * @param {string=} options.preferredMediaProvider DEPRECATED: Use preferred media provider if available
  54. * @param {Array=} options.preferredMediaProviders Use preferred media providers order
  55. * @param {String=} options.receiverLocation Location of WSReceiver.js file
  56. * @param {String=} options.decoderLocation Location of video-worker2.js file
  57. * @param {String=} options.screenSharingExtensionId Chrome screen sharing extension id
  58. * @param {Object=} options.constraints Default local media constraints
  59. * @param {Object=} options.logger Core logger options
  60. * @param {Boolean=} options.collectClientInfo Collect client OS and system data available for debugging purposes
  61. * @throws {Error} Error if none of MediaProviders available
  62. * @memberof Flashphoner
  63. */
  64. var init = async function (options) {
  65. if (!initialized) {
  66. if (!options) {
  67. options = {};
  68. }
  69. // init global logger
  70. coreLogger = createLogger(options.logger);
  71. var waitingTemasys = false;
  72. try {
  73. var audioContext = new (window.AudioContext || window.webkitAudioContext)();
  74. } catch (e) {
  75. console.warn("Failed to create audio context");
  76. }
  77. disableConnectionQualityCalculation = options.disableConnectionQualityCalculation;
  78. var webRtcProvider = require("./webrtc-media-provider");
  79. if (webRtcProvider && webRtcProvider.hasOwnProperty('available') && webRtcProvider.available()) {
  80. MediaProvider.WebRTC = webRtcProvider;
  81. // WCS-2996 Fix audio-video out of sync in case of using Samsung browser
  82. var enableGainNode = util.Browser.isSamsungBrowser() || util.Browser.isAndroidFirefox() ? false : options.createMicGainNode;
  83. var webRtcConf = {
  84. constraints: options.constraints || getDefaultMediaConstraints(),
  85. extensionId: options.screenSharingExtensionId,
  86. audioContext: audioContext,
  87. logger: coreLogger,
  88. createMicGainNode: enableGainNode
  89. };
  90. webRtcProvider.configure(webRtcConf);
  91. } else {
  92. webRtcProvider = require("./temasys-media-provider");
  93. if (webRtcProvider && webRtcProvider.hasOwnProperty('available') && AdapterJS) {
  94. waitingTemasys = true;
  95. AdapterJS.webRTCReady(function (isUsingPlugin) {
  96. isUsingTemasysPlugin = isUsingPlugin;
  97. if (isUsingPlugin || webRtcProvider.available()) {
  98. MediaProvider.WebRTC = webRtcProvider;
  99. var webRtcConf = {
  100. constraints: options.constraints || getDefaultMediaConstraints(),
  101. extensionId: options.screenSharingExtensionId,
  102. logger: coreLogger
  103. };
  104. webRtcProvider.configure(webRtcConf);
  105. // Just reorder media provider list
  106. var _MediaProvider = {};
  107. _MediaProvider.WebRTC = MediaProvider.WebRTC;
  108. for (var p in MediaProvider) {
  109. _MediaProvider[p] = MediaProvider[p];
  110. }
  111. MediaProvider = _MediaProvider;
  112. }
  113. if (options.mediaProvidersReadyCallback) {
  114. options.mediaProvidersReadyCallback(Object.keys(MediaProvider));
  115. }
  116. });
  117. }
  118. }
  119. // flashMediaProvider is disables by default due to end of support in the most browsers #WCS-3577
  120. var flashProvider = null;
  121. if (flashProvider && flashProvider.hasOwnProperty('available') && flashProvider.available() &&
  122. (!MediaProvider.WebRTC || (options.preferredMediaProviders && options.preferredMediaProviders.indexOf("Flash") >= 0))) {
  123. MediaProvider.Flash = flashProvider;
  124. var flashConf = {
  125. constraints: options.constraints || getDefaultMediaConstraints(),
  126. flashMediaProviderSwfLocation: options.flashMediaProviderSwfLocation,
  127. logger: coreLogger
  128. };
  129. flashProvider.configure(flashConf);
  130. }
  131. var mediaSourceMediaProvider = require("./media-source-media-provider");
  132. if (mediaSourceMediaProvider && mediaSourceMediaProvider.hasOwnProperty('available') && mediaSourceMediaProvider.available()) {
  133. MediaProvider.MSE = mediaSourceMediaProvider;
  134. var mseConf = {
  135. audioContext: audioContext,
  136. browserDetails: browserDetails.browser
  137. };
  138. mediaSourceMediaProvider.configure(mseConf);
  139. }
  140. var websocketProvider = require("./websocket-media-provider");
  141. if (websocketProvider && websocketProvider.hasOwnProperty('available') && websocketProvider.available(audioContext)) {
  142. MediaProvider.WSPlayer = websocketProvider;
  143. var wsConf = {
  144. receiverLocation: options.receiverLocation,
  145. decoderLocation: options.decoderLocation,
  146. audioContext: audioContext,
  147. logger: coreLogger
  148. };
  149. websocketProvider.configure(wsConf);
  150. }
  151. //check at least 1 provider available
  152. if (getMediaProviders().length == 0) {
  153. throw new Error('None of MediaProviders available');
  154. } else if (options.preferredMediaProvider) {
  155. if (MediaProvider.hasOwnProperty(options.preferredMediaProvider)) {
  156. if (getMediaProviders()[0] != options.preferredMediaProvider) {
  157. // Just reorder media provider list
  158. var _MediaProvider = {};
  159. _MediaProvider[options.preferredMediaProvider] = MediaProvider[options.preferredMediaProvider];
  160. for (var p in MediaProvider) {
  161. _MediaProvider[p] = MediaProvider[p];
  162. }
  163. MediaProvider = _MediaProvider;
  164. }
  165. } else {
  166. corelogger.warn(LOG_PREFIX, "Preferred media provider is not available.");
  167. }
  168. }
  169. if (options.preferredMediaProviders && options.preferredMediaProviders.length > 0) {
  170. var newMediaProvider = {};
  171. for (var i in options.preferredMediaProviders) {
  172. if (options.preferredMediaProviders.hasOwnProperty(i)) {
  173. var pMP = options.preferredMediaProviders[i];
  174. if (MediaProvider.hasOwnProperty(pMP)) {
  175. newMediaProvider[pMP] = MediaProvider[pMP];
  176. }
  177. }
  178. }
  179. if (util.isEmptyObject(newMediaProvider)) {
  180. throw new Error("None of preferred MediaProviders available");
  181. } else {
  182. MediaProvider = newMediaProvider;
  183. }
  184. }
  185. if (!waitingTemasys && options.mediaProvidersReadyCallback) {
  186. options.mediaProvidersReadyCallback(Object.keys(MediaProvider));
  187. }
  188. coreLogger.info(LOG_PREFIX, "Initialized");
  189. initialized = true;
  190. if (options.collectClientInfo === undefined || options.collectClientInfo) {
  191. clientUAData = await clientInfo.getClientInfo(window.navigator);
  192. coreLogger.info(LOG_PREFIX, "Client system data: " + JSON.stringify(clientUAData));
  193. }
  194. }
  195. };
  196. /**
  197. * Get available MediaProviders.
  198. *
  199. * @returns {Array} Available MediaProviders
  200. * @memberof Flashphoner
  201. */
  202. var getMediaProviders = function () {
  203. return Object.keys(MediaProvider);
  204. };
  205. /**
  206. * Play audio chunk
  207. * @param {boolean} noise Use noise in playing
  208. * @memberof Flashphoner
  209. */
  210. var playFirstSound = function(noise) {
  211. var mediaProvider = getMediaProviders()[0];
  212. MediaProvider[mediaProvider].playFirstSound(noise);
  213. };
  214. /**
  215. * Play video chunk
  216. *
  217. * @memberof Flashphoner
  218. */
  219. var playFirstVideo = function (display, isLocal, src, useControls) {
  220. for (var mp in MediaProvider) {
  221. return MediaProvider[mp].playFirstVideo(display, isLocal, src, useControls);
  222. }
  223. };
  224. /**
  225. * Get core logger
  226. *
  227. * @returns {Object} Logger
  228. * @memberof Flashphoner
  229. */
  230. var getLogger = function () {
  231. if (!initialized) {
  232. console.warn("Initialize API first.");
  233. } else {
  234. return coreLogger;
  235. }
  236. }
  237. /**
  238. * @typedef Flashphoner.MediaDeviceList
  239. * @type Object
  240. * @property {Flashphoner.MediaDevice[]} audio Audio devices (microphones)
  241. * @property {Flashphoner.MediaDevice[]} video Video devices (cameras)
  242. */
  243. /**
  244. * @typedef Flashphoner.MediaDevice
  245. * @type Object
  246. * @property {String} type Type of device: mic, camera, screen
  247. * @property {String} id Unique id
  248. * @property {String} label Device label
  249. */
  250. /**
  251. * Get available local media devices
  252. *
  253. * @param {String=} mediaProvider Media provider that will be asked for device list
  254. * @param {Boolean=} labels Ask user for microphone access before getting device list.
  255. * This will make device label available.
  256. * @param {Flashphoner.constants.MEDIA_DEVICE_KIND} kind Media devices kind to access:
  257. * MEDIA_DEVICE_KIND.INPUT (default) get access to input devices only (camera, mic).
  258. * MEDIA_DEVICE_KIND.OUTPUT get access to output devices only (speaker, headphone).
  259. * MEDIA_DEVICE_KIND.ALL get access to all devices (cam, mic, speaker, headphone).
  260. * @param {Object=} deviceConstraints If labels == true.
  261. * If {audio: true, video: false}, then access to the camera will not be requested.
  262. * If {audio: false, video: true}, then access to the microphone will not be requested.
  263. * @returns {Promise.<Flashphoner.MediaDeviceList>} Promise with media device list on fulfill
  264. * @throws {Error} Error if API is not initialized
  265. * @memberof Flashphoner
  266. */
  267. var getMediaDevices = function (mediaProvider, labels, kind, deviceConstraints) {
  268. if (!initialized) {
  269. throw new Error("Flashphoner API is not initialized");
  270. }
  271. if (!mediaProvider) {
  272. mediaProvider = getMediaProviders()[0];
  273. }
  274. return MediaProvider[mediaProvider].listDevices(labels, kind, deviceConstraints);
  275. };
  276. /**
  277. * Get access to local media
  278. *
  279. * @param {Object} constraints Media constraints
  280. * @param {Object} constraints.audio Audio constraints
  281. * @param {String=} constraints.audio.deviceId Audio device id
  282. * @param {Object} constraints.video Video constraints
  283. * @param {String=} constraints.video.deviceId Video device id
  284. * @param {number} constraints.video.width Video width
  285. * @param {number} constraints.video.height Video height
  286. * @param {number} constraints.video.frameRate Video fps
  287. * @param {String} constraints.video.type Video device type: camera, screen
  288. * @param {String} constraints.video.mediaSource Video source type for FF: screen, window
  289. * @param {HTMLElement} display Div element local media should be displayed in
  290. * @param {String} mediaProvider Media provider type
  291. * @param {Boolean} disableConstraintsNormalization Disable constraints normalization
  292. * @returns {Promise.<HTMLElement>} Promise with display on fulfill
  293. * @throws {Error} Error if API is not initialized
  294. * @memberof Flashphoner
  295. */
  296. var getMediaAccess = function (constraints, display, mediaProvider, disableConstraintsNormalization) {
  297. if (!initialized) {
  298. throw new Error("Flashphoner API is not initialized");
  299. }
  300. if (!mediaProvider) {
  301. mediaProvider = getMediaProviders()[0];
  302. }
  303. return MediaProvider[mediaProvider].getMediaAccess(constraints, display, disableConstraintsNormalization);
  304. };
  305. //default constraints helper
  306. //WCS-3016 16:9 ratio
  307. var getDefaultMediaConstraints = function () {
  308. if (browserDetails.browser == "safari") {
  309. return {
  310. audio: true,
  311. video: {
  312. width: {min: 320, max: 640},
  313. height: {min: 180, max: 360}
  314. }
  315. };
  316. }
  317. else {
  318. return {
  319. audio: true,
  320. video: {
  321. width: 320,
  322. height: 240
  323. }
  324. }
  325. }
  326. };
  327. function getConstraintsProperty(constraints, property, defaultValue) {
  328. if (!constraints || !property) return defaultValue;
  329. var res;
  330. var properties = property.split(".");
  331. for (var prop in constraints) {
  332. if (prop == properties[0]) {
  333. res = constraints[prop];
  334. if (properties.length > 1) res = getConstraintsProperty(constraints[prop], properties[1], defaultValue);
  335. } else if (typeof constraints[prop] === "object") {
  336. for (var p in constraints[prop]) {
  337. if (p == property) res = constraints[prop][p];
  338. }
  339. }
  340. }
  341. if (typeof res === "boolean") return res;
  342. return res || defaultValue;
  343. }
  344. /**
  345. * Release local media
  346. *
  347. * @param {HTMLElement} display Div element with local media
  348. * @param {String=} mediaProvider Media provider type
  349. * @returns {Boolean} True if media was found and released
  350. * @throws {Error} Error if API is not initialized
  351. * @memberof Flashphoner
  352. */
  353. var releaseLocalMedia = function (display, mediaProvider) {
  354. if (!initialized) {
  355. throw new Error("Flashphoner API is not initialized");
  356. }
  357. if (!mediaProvider) {
  358. mediaProvider = getMediaProviders()[0];
  359. }
  360. return MediaProvider[mediaProvider].releaseMedia(display);
  361. };
  362. /**
  363. * Get active sessions.
  364. *
  365. * @returns {Session[]} Array containing active sessions
  366. * @memberof Flashphoner
  367. */
  368. var getSessions = function () {
  369. return util.copyObjectToArray(sessions);
  370. };
  371. /**
  372. * Get session by id.
  373. *
  374. * @param {string} id Session id
  375. * @returns {Session} Session
  376. * @memberof Flashphoner
  377. */
  378. var getSession = function (id) {
  379. return sessions[id];
  380. };
  381. // Get logger configuration from options
  382. var getLoggerConf = function(loggerOptions) {
  383. var conf = loggerOptions || loggerConf;
  384. if (loggerOptions !== null) {
  385. conf.enableLogs = true;
  386. }
  387. return conf;
  388. }
  389. // Create a new logger object
  390. var createLogger = function(loggerOptions, parentLogger = coreLogger) {
  391. var newLogger = parentLogger;
  392. if (newLogger === undefined || loggerOptions != undefined) {
  393. var loggerConf = getLoggerConf(loggerOptions);
  394. newLogger = new LoggerObject;
  395. newLogger.init(loggerConf.severity || "INFO", loggerConf.push || false, loggerConf.customLogger, loggerConf.enableLogs);
  396. }
  397. return newLogger;
  398. }
  399. /**
  400. * Create new session and connect to server.
  401. *
  402. * @param {Object} options Session options
  403. * @param {string} options.urlServer Server address in form of [ws,wss]://host.domain:port
  404. * @param {string} options.authToken Token for auth on server with keepalived client
  405. * @param {Boolean=} options.keepAlive Keep alive client on server after disconnect
  406. * @param {string=} options.lbUrl Load-balancer address
  407. * @param {string=} options.flashProto Flash protocol [rtmp,rtmfp]
  408. * @param {Integer=} options.flashPort Flash server port [1935]
  409. * @param {string=} options.appKey REST App key
  410. * @param {Object=} options.custom User provided custom object that will be available in REST App code
  411. * @param {Object=} options.sipOptions Sip configuration
  412. * @param {Object=} options.mediaOptions Media connection configuration
  413. * @param {Integer=} options.timeout Connection timeout in milliseconds
  414. * @param {Integer=} options.pingInterval Server ping interval in milliseconds [0]
  415. * @param {Integer=} options.receiveProbes A maximum subsequental pings received missing count [0]
  416. * @param {Integer=} options.probesInterval Interval to check subsequental pings received [0]
  417. * @param {Object=} options.logger Session logger options
  418. * @param {Boolean=} options.sendClientInfo Send client system info for debugging purposes
  419. * @returns {Session} Created session
  420. * @throws {Error} Error if API is not initialized
  421. * @throws {TypeError} Error if options.urlServer is not specified
  422. * @memberof Flashphoner
  423. */
  424. var createSession = function (options) {
  425. if (!initialized) {
  426. throw new Error("Flashphoner API is not initialized");
  427. }
  428. if (!options || !options.urlServer) {
  429. throw new TypeError("options.urlServer must be provided");
  430. }
  431. // Set session logger #WCS-2434
  432. var sessionLogger = createLogger(options.logger)
  433. // Override logger for all low level operations
  434. var logger = sessionLogger;
  435. var id_ = uuid_v1();
  436. var sessionStatus = SESSION_STATUS.PENDING;
  437. var urlServer = options.urlServer;
  438. var lbUrl = options.lbUrl;
  439. var flashProto = options.flashProto || "rtmfp";
  440. var flashPort = options.flashPort || 1935;
  441. var appKey = options.appKey || "defaultApp";
  442. var mediaOptions = options.mediaOptions;
  443. var keepAlive = options.keepAlive;
  444. var timeout = options.timeout;
  445. var sendClientInfo = options.sendClientInfo !== undefined ? options.sendClientInfo : true;
  446. var wsPingSender = new WSPingSender(options.pingInterval || 0);
  447. var wsPingReceiver = new WSPingReceiver(options.receiveProbes || 0, options.probesInterval || 0);
  448. var connectionTimeout;
  449. var cConfig;
  450. //SIP config
  451. var sipConfig;
  452. if (options.sipOptions) {
  453. sipConfig = {
  454. sipLogin: options.sipOptions.login,
  455. sipAuthenticationName: options.sipOptions.authenticationName,
  456. sipPassword: options.sipOptions.password,
  457. sipDomain: options.sipOptions.domain,
  458. sipOutboundProxy: options.sipOptions.outboundProxy,
  459. sipProxy: options.sipOptions.proxy,
  460. sipPort: options.sipOptions.port,
  461. sipRegisterRequired: options.sipOptions.registerRequired
  462. }
  463. }
  464. //media provider auth token received from server
  465. var authToken = options.authToken;
  466. //object for storing new and active streams
  467. var streams = {};
  468. var calls = {};
  469. var mediaConnections = {};
  470. //session to stream event callbacks
  471. var streamEventRefreshHandlers = {};
  472. //session to stream callbacks
  473. var streamRefreshHandlers = {};
  474. //session to call callbacks
  475. var callRefreshHandlers = {};
  476. /**
  477. * Represents connection to REST App.
  478. * Can create and store Streams.
  479. *
  480. * @see Flashphoner.createSession
  481. * @namespace Session
  482. */
  483. var session = {};
  484. //callbacks added using session.on()
  485. var callbacks = {};
  486. var wsConnection;
  487. if (lbUrl) {
  488. requestURL(lbUrl);
  489. } else {
  490. createWS(urlServer)
  491. }
  492. //todo remove
  493. var remoteSdpCache = {};
  494. //Request URL from load-balancer
  495. function requestURL(url) {
  496. var request = new XMLHttpRequest();
  497. request.open('GET', url, true);
  498. request.timeout = 5000;
  499. request.ontimeout = function () {
  500. logger.warn(LOG_PREFIX, "Timeout during geting url from balancer!");
  501. createWS(urlServer);
  502. }
  503. request.error = function () {
  504. logger.warn(LOG_PREFIX, "Error during geting url from balancer!")
  505. createWS(urlServer);
  506. }
  507. request.onload = function (e) {
  508. if (request.status == 200 && request.readyState == 4) {
  509. var result = JSON.parse(request.responseText);
  510. if (urlServer.indexOf("wss://") !== -1) {
  511. urlServer = "wss://" + result.server + ":" + result.wss;
  512. } else {
  513. urlServer = "ws://" + result.server + ":" + result.ws;
  514. }
  515. flashPort = result.flash;
  516. logger.debug(LOG_PREFIX, "Got url from load balancer " + result.server);
  517. createWS(urlServer);
  518. }
  519. }
  520. request.send();
  521. }
  522. //connect session to server
  523. function createWS(url) {
  524. wsConnection = new WebSocket(url);
  525. if (timeout != undefined && timeout > 0) {
  526. connectionTimeout = setTimeout(function() {
  527. if (wsConnection.readyState == 0) {
  528. logger.warn(LOG_PREFIX, "WS connection timeout");
  529. wsConnection.close();
  530. }
  531. }, timeout);
  532. }
  533. wsConnection.onerror = function () {
  534. onSessionStatusChange(SESSION_STATUS.FAILED);
  535. };
  536. wsConnection.onclose = function () {
  537. if (sessionStatus !== SESSION_STATUS.FAILED) {
  538. onSessionStatusChange(SESSION_STATUS.DISCONNECTED);
  539. }
  540. };
  541. wsConnection.onopen = function () {
  542. onSessionStatusChange(SESSION_STATUS.CONNECTED);
  543. clearTimeout(connectionTimeout);
  544. cConfig = {
  545. appKey: appKey,
  546. mediaProviders: Object.keys(MediaProvider),
  547. keepAlive: keepAlive,
  548. authToken: authToken,
  549. clientVersion: "2.0",
  550. clientOSVersion: window.navigator.appVersion,
  551. clientBrowserVersion: window.navigator.userAgent,
  552. msePacketizationVersion: 2,
  553. custom: options.custom
  554. };
  555. if (sendClientInfo && clientUAData) {
  556. cConfig.clientInfo = clientUAData;
  557. }
  558. if (sipConfig) {
  559. util.copyObjectPropsToAnotherObject(sipConfig, cConfig);
  560. }
  561. //connect to REST App
  562. send("connection", cConfig);
  563. logger.setConnection(wsConnection);
  564. // Send ping messages to server to check if connection is still alive #WCS-3410
  565. wsPingSender.start();
  566. // Check subsequintel pings received from server to check if connection is still alive #WCS-3410
  567. wsPingReceiver.start();
  568. };
  569. wsConnection.onmessage = function (event) {
  570. var data = {};
  571. if (event.data instanceof Blob) {
  572. data.message = "binaryData";
  573. } else {
  574. data = JSON.parse(event.data);
  575. var obj = data.data[0];
  576. }
  577. switch (data.message) {
  578. case 'ping':
  579. logger.debug(LOG_PREFIX, "<<< ping");
  580. send("pong", null);
  581. logger.debug(LOG_PREFIX, ">>> pong");
  582. break;
  583. case 'getUserData':
  584. authToken = obj.authToken;
  585. cConfig = obj;
  586. onSessionStatusChange(SESSION_STATUS.ESTABLISHED, obj);
  587. break;
  588. case 'setRemoteSDP':
  589. var mediaSessionId = data.data[0];
  590. var sdp = data.data[1];
  591. if (streamRefreshHandlers[mediaSessionId]) {
  592. //pass server's sdp to stream
  593. streamRefreshHandlers[mediaSessionId](null, sdp);
  594. } else if (callRefreshHandlers[mediaSessionId]) {
  595. //pass server's sdp to call
  596. callRefreshHandlers[mediaSessionId](null, sdp);
  597. } else {
  598. remoteSdpCache[mediaSessionId] = sdp;
  599. logger.info(LOG_PREFIX, "Media not found, id " + mediaSessionId);
  600. }
  601. break;
  602. case 'notifyVideoFormat':
  603. case 'notifyStreamStatusEvent':
  604. if (streamRefreshHandlers[obj.mediaSessionId]) {
  605. //update stream status
  606. streamRefreshHandlers[obj.mediaSessionId](obj);
  607. }
  608. break;
  609. case 'notifyStreamEvent':
  610. if (streamEventRefreshHandlers[obj.mediaSessionId]) {
  611. //update stream status
  612. streamEventRefreshHandlers[obj.mediaSessionId](obj);
  613. }
  614. break;
  615. case 'DataStatusEvent':
  616. restAppCommunicator.resolveData(obj);
  617. break;
  618. case 'OnDataEvent':
  619. if (callbacks[SESSION_STATUS.APP_DATA]) {
  620. callbacks[SESSION_STATUS.APP_DATA](obj);
  621. }
  622. break;
  623. case 'fail':
  624. if (obj.apiMethod && obj.apiMethod == "StreamStatusEvent") {
  625. if (streamRefreshHandlers[obj.id]) {
  626. //update stream status
  627. streamRefreshHandlers[obj.id](obj);
  628. }
  629. }
  630. if (callbacks[SESSION_STATUS.WARN]) {
  631. callbacks[SESSION_STATUS.WARN](obj);
  632. }
  633. break;
  634. case 'registered':
  635. onSessionStatusChange(SESSION_STATUS.REGISTERED);
  636. break;
  637. case 'notifyAudioCodec':
  638. // This case for Flash only
  639. var mediaSessionId = data.data[0];
  640. var codec = data.data[1];
  641. if (callRefreshHandlers[mediaSessionId]) {
  642. callRefreshHandlers[mediaSessionId](null, null, codec);
  643. }
  644. break;
  645. case 'notifyTransferEvent':
  646. callRefreshHandlers[obj.callId](null, null, null, obj);
  647. break;
  648. case 'notifyTryingResponse':
  649. case 'hold':
  650. case 'ring':
  651. case 'talk':
  652. case 'finish':
  653. if (callRefreshHandlers[obj.callId]) {
  654. //update call status
  655. callRefreshHandlers[obj.callId](obj);
  656. }
  657. break;
  658. case 'notifyIncomingCall':
  659. if (callRefreshHandlers[obj.callId]) {
  660. logger.error(LOG_PREFIX, "Call already exists, id " + obj.callId);
  661. }
  662. if (callbacks[SESSION_STATUS.INCOMING_CALL]) {
  663. callbacks[SESSION_STATUS.INCOMING_CALL](createCall(obj));
  664. } else {
  665. //todo hangup call
  666. }
  667. break;
  668. case 'notifySessionDebugEvent':
  669. logger.info(LOG_PREFIX, "Session debug status " + obj.status);
  670. if (callbacks[SESSION_STATUS.DEBUG]) {
  671. callbacks[SESSION_STATUS.DEBUG](obj);
  672. }
  673. break;
  674. case 'availableStream':
  675. var availableStream = {};
  676. availableStream.mediaSessionId = obj.id;
  677. availableStream.available = obj.status;
  678. availableStream.reason = obj.info;
  679. if (streamRefreshHandlers[availableStream.mediaSessionId]) {
  680. streamRefreshHandlers[availableStream.mediaSessionId](availableStream);
  681. }
  682. break;
  683. case OUTBOUND_VIDEO_RATE:
  684. case INBOUND_VIDEO_RATE:
  685. if (streamRefreshHandlers[obj.mediaSessionId]) {
  686. obj.status = data.message;
  687. streamRefreshHandlers[obj.mediaSessionId](obj);
  688. }
  689. break;
  690. default:
  691. logger.info(LOG_PREFIX, "Unknown server message " + data.message);
  692. }
  693. // Reset missing pings counter on any message received successfully #WCS-4343
  694. logger.debug(LOG_PREFIX, "Reset missing pings counter by " + data.message + " message");
  695. wsPingReceiver.success();
  696. };
  697. }
  698. //WebSocket send helper
  699. function send(message, data) {
  700. wsConnection.send(JSON.stringify({
  701. message: message,
  702. data: [data]
  703. }));
  704. }
  705. //Session status update helper
  706. function onSessionStatusChange(newStatus, obj) {
  707. sessionStatus = newStatus;
  708. if (sessionStatus == SESSION_STATUS.DISCONNECTED || sessionStatus == SESSION_STATUS.FAILED) {
  709. // Stop pinging server #WCS-3410
  710. wsPingSender.stop();
  711. // Stop checking pings received #WCS-3410
  712. wsPingReceiver.stop();
  713. //remove streams
  714. for (var prop in streamRefreshHandlers) {
  715. if (streamRefreshHandlers.hasOwnProperty(prop) && typeof streamRefreshHandlers[prop] === 'function') {
  716. streamRefreshHandlers[prop]({status: STREAM_STATUS.FAILED});
  717. }
  718. }
  719. //remove session from list
  720. delete sessions[id_];
  721. }
  722. if (callbacks[sessionStatus]) {
  723. callbacks[sessionStatus](session, obj);
  724. }
  725. }
  726. // Websocket periodic ping sender
  727. function WSPingSender(interval) {
  728. this.interval = interval || 0;
  729. this.intervalId = null;
  730. this.start = function() {
  731. if (this.interval > 0) {
  732. this.intervalId = setInterval(function() {
  733. send("ping", null);
  734. }, this.interval);
  735. }
  736. };
  737. this.stop = function() {
  738. if (this.intervalId) {
  739. clearInterval(this.intervalId);
  740. }
  741. };
  742. return(this);
  743. }
  744. // Websocket ping receive prober
  745. function WSPingReceiver(receiveProbes, probesInterval) {
  746. this.maxPings = receiveProbes || 0;
  747. this.interval = probesInterval || 0;
  748. this.intervalId = null;
  749. this.pingsMissing = 0;
  750. this.start = function() {
  751. if (this.maxPings > 0 && this.interval > 0) {
  752. let receiver = this;
  753. this.intervalId = setInterval(function() {
  754. receiver.checkPingsReceived();
  755. }, this.interval);
  756. }
  757. };
  758. this.stop = function() {
  759. if (this.intervalId) {
  760. clearInterval(this.intervalId);
  761. }
  762. this.pingsMissing = 0;
  763. };
  764. this.checkPingsReceived = function() {
  765. this.pingsMissing++;
  766. if (this.pingsMissing >= this.maxPings) {
  767. this.failure();
  768. }
  769. };
  770. this.success = function() {
  771. this.pingsMissing = 0;
  772. };
  773. this.failure = function() {
  774. logger.info(LOG_PREFIX, "Missing " + this.pingsMissing + " pings from server, connection seems to be down");
  775. onSessionStatusChange(SESSION_STATUS.FAILED);
  776. wsConnection.close();
  777. };
  778. return(this);
  779. }
  780. /**
  781. * @callback sdpHook
  782. * @param {Object} sdp Callback options
  783. * @param {String} sdp.sdpString Sdp from the server
  784. * @returns {String} sdp New sdp
  785. */
  786. /**
  787. * Create call.
  788. *
  789. * @param {Object} options Call options
  790. * @param {string} options.callee Call remote party id
  791. * @param {string=} options.visibleName Call caller visible name
  792. * @param {Object} options.constraints Call constraints
  793. * @param {string} options.mediaProvider MediaProvider type to use with this call
  794. * @param {Boolean=} options.receiveAudio Receive audio
  795. * @param {Boolean=} options.receiveVideo Receive video
  796. * @param {Boolean=} options.cacheLocalResources Display will contain local video after call release
  797. * @param {HTMLElement} options.localVideoDisplay Div element local video should be displayed in
  798. * @param {HTMLElement} options.remoteVideoDisplay Div element remote video should be displayed in
  799. * @param {Object=} options.custom User provided custom object that will be available in REST App code
  800. * @param {string=} options.stripCodecs Comma separated strings of codecs which should be stripped from WebRTC SDP (ex. "SILK,G722")
  801. * @param {Array<string>=} options.sipSDP Array of custom SDP params (ex. bandwidth (b=))
  802. * @param {Array<string>=} options.sipHeaders Array of custom SIP headers
  803. * @param {string=} options.videoContentHint Video content hint for browser ('motion' by default to maintain bitrate and fps), {@link Flashphoner.constants.CONTENT_HINT_TYPE}
  804. * @param {Boolean=} options.useControls Use a standard HTML5 video controls (play, pause, fullscreen). May be a workaround for fullscreen mode to work in Safari 16
  805. * @param {Object=} options.logger Call logger options
  806. * @param {Boolean=} options.collectDeviceInfo Collect a media devices info when publishing a WebRTC stream
  807. * @param {sdpHook} sdpHook The callback that handles sdp from the server
  808. * @returns {Call} Call
  809. * @throws {TypeError} Error if no options provided
  810. * @throws {Error} Error if session state is not REGISTERED
  811. * @memberof Session
  812. * @inner
  813. */
  814. var createCall = function (options) {
  815. //check session state
  816. if (sessionStatus !== SESSION_STATUS.REGISTERED && sessionStatus !== SESSION_STATUS.ESTABLISHED) {
  817. throw new Error('Invalid session state ' + sessionStatus);
  818. }
  819. //check options
  820. if (!options) {
  821. throw new TypeError("options must be provided");
  822. }
  823. // Set call logger #WCS-2434
  824. var callLogger = createLogger(options.logger, sessionLogger);
  825. // Override logger for all low level operations
  826. var logger = callLogger;
  827. var login = (appKey == 'clickToCallApp') ? '' : cConfig.sipLogin;
  828. var caller_ = (options.incoming) ? options.caller : login;
  829. var callee_ = options.callee;
  830. var visibleName_ = options.visibleName || login;
  831. var id_ = options.callId || uuid_v1();
  832. var mediaProvider = options.mediaProvider || getMediaProviders()[0];
  833. var mediaConnection;
  834. var localDisplay = options.localVideoDisplay;
  835. var remoteDisplay = options.remoteVideoDisplay;
  836. var info_;
  837. var errorInfo_;
  838. // Constraints
  839. if (options.constraints) {
  840. var constraints = options.constraints;
  841. }
  842. if (options.disableConstraintsNormalization) {
  843. var disableConstraintsNormalization = options.disableConstraintsNormalization;
  844. }
  845. var audioOutputId;
  846. var audioProperty = getConstraintsProperty(constraints, CONSTRAINT_AUDIO, undefined);
  847. if (typeof audioProperty === 'object') {
  848. audioOutputId = getConstraintsProperty(audioProperty, CONSTRAINT_AUDIO_OUTPUT_ID, 0);
  849. }
  850. var stripCodecs = options.stripCodecs || [];
  851. // Receive media
  852. var receiveAudio = (typeof options.receiveAudio !== 'undefined') ? options.receiveAudio : true;
  853. var receiveVideo = (typeof options.receiveVideo !== 'undefined') ? options.receiveVideo : true;
  854. var cacheLocalResources = options.cacheLocalResources;
  855. var status_ = CALL_STATUS.NEW;
  856. var callbacks = {};
  857. var hasTransferredCall = false;
  858. var sdpHook = options.sdpHook;
  859. var sipSDP = options.sipSDP;
  860. var sipHeaders = options.sipHeaders;
  861. var videoContentHint = options.videoContentHint;
  862. var useControls = options.useControls;
  863. var collectDeviceInfo = options.collectDeviceInfo !== undefined ? options.collectDeviceInfo : true;
  864. var minBitrate = getConstraintsProperty(constraints, CONSTRAINT_VIDEO_MIN_BITRATE, 0);
  865. var maxBitrate = getConstraintsProperty(constraints, CONSTRAINT_VIDEO_MAX_BITRATE, 0);
  866. /**
  867. * Represents sip call.
  868. *
  869. * @namespace Call
  870. * @see Session~createCall
  871. */
  872. var call = {};
  873. callRefreshHandlers[id_] = function (callInfo, sdp, codec, transfer) {
  874. if (transfer) {
  875. if (!mediaConnections[id_]) {
  876. mediaConnections[id_] = mediaConnection;
  877. }
  878. if (transfer.status == "COMPLETED") {
  879. delete mediaConnections[id_];
  880. }
  881. return;
  882. }
  883. //transferred call
  884. if (!mediaConnection && Object.keys(mediaConnections).length != 0) {
  885. for (var mc in mediaConnections) {
  886. mediaConnection = mediaConnections[mc];
  887. hasTransferredCall = true;
  888. delete mediaConnections[mc];
  889. }
  890. }
  891. //set audio codec (Flash only)
  892. if (codec) {
  893. if (mediaProvider == "Flash") {
  894. mediaConnection.changeAudioCodec(codec.name);
  895. }
  896. return;
  897. }
  898. //set remote sdp
  899. if (sdp && sdp !== '') {
  900. sdp = sdpHookHandler(sdp, sdpHook);
  901. // Adjust publishing bitrate #WCS-4013
  902. sdp = util.setPublishingBitrate(sdp, mediaConnection, minBitrate, maxBitrate);
  903. mediaConnection.setRemoteSdp(sdp, hasTransferredCall, id_).then(function () {});
  904. return;
  905. }
  906. var event = callInfo.status;
  907. status_ = event;
  908. //release call
  909. if (event == CALL_STATUS.FAILED || event == CALL_STATUS.FINISH ||
  910. event == CALL_STATUS.BUSY) {
  911. delete calls[id_];
  912. delete callRefreshHandlers[id_];
  913. if (Object.keys(calls).length == 0) {
  914. if (mediaConnection)
  915. mediaConnection.close(cacheLocalResources);
  916. }
  917. }
  918. //fire call event
  919. if (callbacks[event]) {
  920. callbacks[event](call);
  921. }
  922. };
  923. /**
  924. * Initiate outgoing call.
  925. *
  926. * @throws {Error} Error if call status is not {@link Flashphoner.constants.CALL_STATUS.NEW}
  927. * @memberof Call
  928. * @name call
  929. * @inner
  930. */
  931. var call_ = function () {
  932. if (status_ !== CALL_STATUS.NEW) {
  933. throw new Error("Invalid call state");
  934. }
  935. status_ = CALL_STATUS.PENDING;
  936. var hasAudio = true;
  937. //get access to camera
  938. MediaProvider[mediaProvider].getMediaAccess(constraints, localDisplay, disableConstraintsNormalization).then(function () {
  939. if (status_ == CALL_STATUS.FAILED) {
  940. //call failed while we were waiting for media access, release media
  941. if (!cacheLocalResources) {
  942. releaseLocalMedia(localDisplay, mediaProvider);
  943. }
  944. return;
  945. }
  946. //create mediaProvider connection
  947. MediaProvider[mediaProvider].createConnection({
  948. id: id_,
  949. localDisplay: localDisplay,
  950. remoteDisplay: remoteDisplay,
  951. authToken: authToken,
  952. mainUrl: urlServer,
  953. flashProto: flashProto,
  954. flashPort: flashPort,
  955. bidirectional: true,
  956. login: login,
  957. constraints: constraints,
  958. connectionConfig: mediaOptions,
  959. audioOutputId: audioOutputId,
  960. videoContentHint: videoContentHint,
  961. useControls: useControls,
  962. logger: logger
  963. }).then(function (newConnection) {
  964. mediaConnection = newConnection;
  965. return mediaConnection.createOffer({
  966. sendAudio: true,
  967. sendVideo: true,
  968. receiveAudio: receiveAudio,
  969. receiveVideo: receiveVideo,
  970. stripCodecs: stripCodecs
  971. });
  972. }).then(function (offer) {
  973. let callData = {
  974. callId: id_,
  975. incoming: false,
  976. hasVideo: offer.hasVideo,
  977. hasAudio: offer.hasAudio,
  978. status: status_,
  979. mediaProvider: mediaProvider,
  980. sdp: offer.sdp,
  981. sipSDP: sipSDP,
  982. caller: login,
  983. callee: callee_,
  984. custom: options.custom,
  985. visibleName: visibleName_
  986. };
  987. // Get local media info to send in publishStream message
  988. if (collectDeviceInfo) {
  989. callData.localMediaInfo = collectLocalMediaInfo(MediaProvider[mediaProvider], localDisplay);
  990. }
  991. send("call", callData);
  992. });
  993. }).catch(function (error) {
  994. logger.error(LOG_PREFIX, error);
  995. status_ = CALL_STATUS.FAILED;
  996. info_ = ERROR_INFO.LOCAL_ERROR;
  997. errorInfo_ = error.message;
  998. callRefreshHandlers[id_]({status: CALL_STATUS.FAILED});
  999. hangup();
  1000. });
  1001. };
  1002. /**
  1003. * Hangup call.
  1004. *
  1005. * @memberof Call
  1006. * @inner
  1007. */
  1008. var hangup = function () {
  1009. if (status_ == CALL_STATUS.NEW) {
  1010. callRefreshHandlers[id_]({status: CALL_STATUS.FAILED});
  1011. return;
  1012. } else if (status_ == CALL_STATUS.PENDING) {
  1013. if (!cacheLocalResources) {
  1014. releaseLocalMedia(localDisplay, mediaProvider);
  1015. }
  1016. callRefreshHandlers[id_]({status: CALL_STATUS.FAILED});
  1017. if (options.incoming) {
  1018. send("hangup", {
  1019. callId: id_
  1020. });
  1021. }
  1022. return;
  1023. }
  1024. send("hangup", {
  1025. callId: id_
  1026. });
  1027. //free media provider
  1028. if (mediaConnection) {
  1029. mediaConnection.close(cacheLocalResources);
  1030. }
  1031. };
  1032. /**
  1033. * @callback sdpHook
  1034. * @param {Object} sdp Callback options
  1035. * @param {String} sdp.sdpString Sdp from the server
  1036. * @returns {String} sdp New sdp
  1037. */
  1038. /**
  1039. * Answer incoming call.
  1040. * @param {Object} answerOptions Call options
  1041. * @param {HTMLElement} answerOptions.localVideoDisplay Div element local video should be displayed in
  1042. * @param {HTMLElement} answerOptions.remoteVideoDisplay Div element remote video should be displayed in
  1043. * @param {Boolean=} answerOptions.receiveAudio Receive audio
  1044. * @param {Boolean=} answerOptions.receiveVideo Receive video
  1045. * @param {String=} answerOptions.constraints Answer call with constraints
  1046. * @param {String=} answerOptions.stripCodecs Comma separated string of codecs which should be stripped from WebRTC SDP (ex. "SILK,G722")
  1047. * @param {Array<string>=} answerOptions.sipSDP Array of custom SDP params (ex. bandwidth (b=))
  1048. * @param {Array<string>=} answerOptions.sipHeaders Array of custom SIP headers
  1049. * @param {sdpHook} sdpHook The callback that handles sdp from the server
  1050. * @throws {Error} Error if call status is not {@link Flashphoner.constants.CALL_STATUS.NEW}
  1051. * @memberof Call
  1052. * @name call
  1053. * @inner
  1054. */
  1055. var answer = function (answerOptions) {
  1056. if (status_ !== CALL_STATUS.NEW && status_ !== CALL_STATUS.RING) {
  1057. throw new Error("Invalid call state");
  1058. }
  1059. localDisplay = answerOptions.localVideoDisplay;
  1060. remoteDisplay = answerOptions.remoteVideoDisplay;
  1061. constraints = answerOptions.constraints || getDefaultMediaConstraints();
  1062. status_ = CALL_STATUS.PENDING;
  1063. var sdp;
  1064. var sdpHook = answerOptions.sdpHook;
  1065. var minBitrate = getConstraintsProperty(constraints, CONSTRAINT_VIDEO_MIN_BITRATE, 0);
  1066. var maxBitrate = getConstraintsProperty(constraints, CONSTRAINT_VIDEO_MAX_BITRATE, 0);
  1067. sipSDP = answerOptions.sipSDP;
  1068. sipHeaders = answerOptions.sipHeaders;
  1069. if (!remoteSdpCache[id_]) {
  1070. logger.error(LOG_PREFIX, "No remote sdp available");
  1071. throw new Error("No remote sdp available");
  1072. } else {
  1073. sdp = sdpHookHandler(remoteSdpCache[id_], sdpHook);
  1074. // Adjust publishing bitrate #WCS-4013
  1075. sdp = util.setPublishingBitrate(sdp, null, minBitrate, maxBitrate);
  1076. delete remoteSdpCache[id_];
  1077. }
  1078. if (util.SDP.matchPrefix(sdp, "m=video").length == 0) {
  1079. constraints.video = false;
  1080. }
  1081. var stripCodecs = answerOptions.stripCodecs || [];
  1082. var hasAudio = true;
  1083. //get access to camera
  1084. MediaProvider[mediaProvider].getMediaAccess(constraints, localDisplay, disableConstraintsNormalization).then(function () {
  1085. if (status_ == CALL_STATUS.FAILED) {
  1086. //call failed while we were waiting for media access, release media
  1087. if (!cacheLocalResources) {
  1088. releaseLocalMedia(localDisplay, mediaProvider);
  1089. }
  1090. return;
  1091. }
  1092. //create mediaProvider connection
  1093. MediaProvider[mediaProvider].createConnection({
  1094. id: id_,
  1095. localDisplay: localDisplay,
  1096. remoteDisplay: remoteDisplay,
  1097. authToken: authToken,
  1098. mainUrl: urlServer,
  1099. flashProto: flashProto,
  1100. flashPort: flashPort,
  1101. bidirectional: true,
  1102. login: cConfig.sipLogin,
  1103. constraints: constraints,
  1104. connectionConfig: mediaOptions,
  1105. audioOutputId: audioOutputId,
  1106. useControls: useControls
  1107. }).then(function (newConnection) {
  1108. mediaConnection = newConnection;
  1109. // Set publishing bitrate via sender encodings if SDP feature is not supported
  1110. mediaConnection.setPublishingBitrate(minBitrate, maxBitrate);
  1111. return mediaConnection.setRemoteSdp(sdp);
  1112. }).then(function () {
  1113. return mediaConnection.createAnswer({
  1114. receiveAudio: options.receiveAudio,
  1115. receiveVideo: options.receiveVideo,
  1116. stripCodecs: stripCodecs
  1117. });
  1118. }).then(function (sdp) {
  1119. if (status_ != CALL_STATUS.FINISH && status_ != CALL_STATUS.FAILED) {
  1120. let callData = {
  1121. callId: id_,
  1122. incoming: true,
  1123. hasVideo: true,
  1124. hasAudio: hasAudio,
  1125. status: status_,
  1126. mediaProvider: mediaProvider,
  1127. sdp: sdp,
  1128. sipSDP: sipSDP,
  1129. caller: cConfig.login,
  1130. callee: callee_,
  1131. custom: options.custom
  1132. };
  1133. // Get local media info to send in publishStream message
  1134. if (collectDeviceInfo) {
  1135. callData.localMediaInfo = collectLocalMediaInfo(MediaProvider[mediaProvider], localDisplay);
  1136. }
  1137. send("answer", callData);
  1138. } else {
  1139. hangup();
  1140. }
  1141. });
  1142. }).catch(function (error) {
  1143. logger.error(LOG_PREFIX, error);
  1144. info_ = ERROR_INFO.LOCAL_ERROR;
  1145. errorInfo_ = error.message;
  1146. status_ = CALL_STATUS.FAILED;
  1147. callRefreshHandlers[id_]({status: CALL_STATUS.FAILED});
  1148. });
  1149. };
  1150. /**
  1151. * Get call status.
  1152. *
  1153. * @returns {string} One of {@link Flashphoner.constants.CALL_STATUS}
  1154. * @memberof Call
  1155. * @inner
  1156. */
  1157. var status = function () {
  1158. return status_;
  1159. };
  1160. /**
  1161. * Get call id.
  1162. *
  1163. * @returns {string} Call id
  1164. * @memberof Call
  1165. * @inner
  1166. */
  1167. var id = function () {
  1168. return id_;
  1169. };
  1170. /**
  1171. * Get caller id.
  1172. *
  1173. * @returns {string} Caller id
  1174. * @memberof Call
  1175. * @inner
  1176. */
  1177. var caller = function () {
  1178. return caller_;
  1179. };
  1180. /**
  1181. * Get callee id.
  1182. *
  1183. * @returns {string} Callee id
  1184. * @memberof Call
  1185. * @inner
  1186. */
  1187. var callee = function () {
  1188. return callee_;
  1189. };
  1190. /**
  1191. * Get caller visible name.
  1192. *
  1193. * @returns {string} Caller visible name
  1194. * @memberof Call
  1195. * @inner
  1196. */
  1197. var visibleName = function () {
  1198. return visibleName_;
  1199. };
  1200. /**
  1201. * Media controls
  1202. */
  1203. /**
  1204. * Set other oupout audio device
  1205. *
  1206. * @param {string} id Id of output device
  1207. * @memberof Call
  1208. * @inner
  1209. */
  1210. var setAudioOutputId = function(id) {
  1211. audioOutputId = id;
  1212. if (mediaConnection && mediaConnection.setAudioOutputId) {
  1213. return mediaConnection.setAudioOutputId(id);
  1214. }
  1215. };
  1216. /**
  1217. * Set volume of remote media
  1218. *
  1219. * @param {number} volume Volume between 0 and 100
  1220. * @memberof Call
  1221. * @inner
  1222. */
  1223. var setVolume = function (volume) {
  1224. if (mediaConnection) {
  1225. mediaConnection.setVolume(volume);
  1226. }
  1227. };
  1228. /**
  1229. * Get current volume
  1230. *
  1231. * @returns {number} Volume or -1 if audio is not available
  1232. * @memberof Call
  1233. * @inner
  1234. */
  1235. var getVolume = function () {
  1236. if (mediaConnection) {
  1237. return mediaConnection.getVolume();
  1238. }
  1239. return -1;
  1240. };
  1241. /**
  1242. * Mute outgoing audio
  1243. *
  1244. * @memberof Call
  1245. * @inner
  1246. */
  1247. var muteAudio = function () {
  1248. if (mediaConnection) {
  1249. mediaConnection.muteAudio();
  1250. }
  1251. };
  1252. /**
  1253. * Unmute outgoing audio
  1254. *
  1255. * @memberof Call
  1256. * @inner
  1257. */
  1258. var unmuteAudio = function () {
  1259. if (mediaConnection) {
  1260. mediaConnection.unmuteAudio();
  1261. }
  1262. };
  1263. /**
  1264. * Check outgoing audio mute state
  1265. *
  1266. * @returns {boolean} True if audio is muted or not available
  1267. * @memberof Call
  1268. * @inner
  1269. */
  1270. var isAudioMuted = function () {
  1271. if (mediaConnection) {
  1272. return mediaConnection.isAudioMuted();
  1273. }
  1274. return true;
  1275. };
  1276. /**
  1277. * Mute outgoing video
  1278. *
  1279. * @memberof Call
  1280. * @inner
  1281. */
  1282. var muteVideo = function () {
  1283. if (mediaConnection) {
  1284. mediaConnection.muteVideo();
  1285. }
  1286. };
  1287. /**
  1288. * Unmute outgoing video
  1289. *
  1290. * @memberof Call
  1291. * @inner
  1292. */
  1293. var unmuteVideo = function () {
  1294. if (mediaConnection) {
  1295. mediaConnection.unmuteVideo();
  1296. }
  1297. };
  1298. /**
  1299. * Check outgoing video mute state
  1300. *
  1301. * @returns {boolean} True if video is muted or not available
  1302. * @memberof Call
  1303. * @inner
  1304. */
  1305. var isVideoMuted = function () {
  1306. if (mediaConnection) {
  1307. return mediaConnection.isVideoMuted();
  1308. }
  1309. return true;
  1310. };
  1311. /**
  1312. * @callback callbackFn
  1313. * @param {Object} result
  1314. */
  1315. /**
  1316. * Get statistics
  1317. *
  1318. * @param {callbackFn} callbackFn The callback that handles response
  1319. * @param {Boolean} nativeStats If true, use native browser statistics
  1320. * @returns {Object} Call audio\video statistics
  1321. * @memberof Call
  1322. * @inner
  1323. */
  1324. var getStats = function (callbackFn, nativeStats) {
  1325. if (mediaConnection) {
  1326. mediaConnection.getStats(callbackFn, nativeStats);
  1327. }
  1328. };
  1329. /**
  1330. * Place call on hold
  1331. *
  1332. * @memberof Call
  1333. * @inner
  1334. */
  1335. var hold = function () {
  1336. send("hold", {callId: id_});
  1337. }
  1338. /**
  1339. * Place call on hold for transfer
  1340. *
  1341. * @memberof Call
  1342. * @inner
  1343. */
  1344. var holdForTransfer = function () {
  1345. send("hold", {callId: id_, holdForTransfer: true});
  1346. }
  1347. /**
  1348. * Unhold the call
  1349. *
  1350. * @memberof Call
  1351. * @inner
  1352. */
  1353. var unhold = function () {
  1354. send("unhold", {callId: id_});
  1355. }
  1356. /**
  1357. * Send DTMF
  1358. *
  1359. * @param {number} number Number
  1360. * @param {string=} type DTMF Type (RFC2833, INFO, INFO_RELAY)
  1361. * @memberof Call
  1362. * @inner
  1363. */
  1364. var sendDTMF = function (number, type) {
  1365. send("sendDtmf", {
  1366. callId: id_,
  1367. type: type || "RFC2833",
  1368. dtmf: number
  1369. });
  1370. }
  1371. /**
  1372. * Transfer call
  1373. *
  1374. * @param {String} traget Transfer target
  1375. * @memberof Call
  1376. * @inner
  1377. */
  1378. var transfer = function (target) {
  1379. send("transfer", {callId: id_, target: target});
  1380. }
  1381. /**
  1382. * Call event callback.
  1383. *
  1384. * @callback Call~eventCallback
  1385. * @param {Call} call Call that corresponds to the event
  1386. */
  1387. /**
  1388. * Add call event callback.
  1389. *
  1390. * @param {string} event One of {@link Flashphoner.constants.CALL_STATUS} events
  1391. * @param {Call~eventCallback} callback Callback function
  1392. * @returns {Call} Call callback was attached to
  1393. * @throws {TypeError} Error if event is not specified
  1394. * @throws {Error} Error if callback is not a valid function
  1395. * @memberof Call
  1396. * @inner
  1397. */
  1398. var on = function (event, callback) {
  1399. if (!event) {
  1400. throw new TypeError("Event can't be null");
  1401. }
  1402. if (!callback || typeof callback !== 'function') {
  1403. throw new Error("Callback needs to be a valid function");
  1404. }
  1405. callbacks[event] = callback;
  1406. return call;
  1407. };
  1408. /**
  1409. * Switch camera in real-time.
  1410. * Works only with WebRTC
  1411. *
  1412. * @memberOf Call
  1413. * @inner
  1414. * @throws {Error} Error if call status is not {@link Flashphoner.constants.CALL_STATUS.ESTABLISHED} and not {@link Flashphoner.constants.CALL_STATUS.HOLD}
  1415. */
  1416. var switchCam = function(deviceId) {
  1417. if(status_ !== CALL_STATUS.ESTABLISHED && !constraints.video && status_ !== CALL_STATUS.HOLD){
  1418. throw new Error('Invalid call state');
  1419. }
  1420. return mediaConnection.switchCam(deviceId);
  1421. };
  1422. /**
  1423. * Switch mic in real-time.
  1424. * Works only with WebRTC
  1425. *
  1426. * @memberOf Call
  1427. * @inner
  1428. * @throws {Error} Error if call status is not {@link Flashphoner.constants.CALL_STATUS.ESTABLISHED} and not {@link Flashphoner.constants.CALL_STATUS.HOLD}
  1429. */
  1430. var switchMic = function(deviceId) {
  1431. if(status_ !== CALL_STATUS.ESTABLISHED && status_ !== CALL_STATUS.HOLD){
  1432. throw new Error('Invalid call state');
  1433. }
  1434. return mediaConnection.switchMic(deviceId);
  1435. };
  1436. /**
  1437. * Switch to screen in real-time.
  1438. * Works only with WebRTC
  1439. *
  1440. * @param {String} source Screen sharing source (for firefox)
  1441. * @param {Boolean} woExtension Screen sharing without extension (for chrome)
  1442. * @memberOf Call
  1443. * @inner
  1444. * @throws {Error} Error if stream status is not {@link Flashphoner.constants.STREAM_STATUS.PUBLISHING}
  1445. */
  1446. var switchToScreen = function (source, woExtension) {
  1447. if(status_ !== CALL_STATUS.ESTABLISHED && status_ !== CALL_STATUS.HOLD){
  1448. throw new Error('Invalid call state');
  1449. }
  1450. return mediaConnection.switchToScreen(source, woExtension);
  1451. };
  1452. /**
  1453. * Switch to cam in real-time.
  1454. * Works only with WebRTC
  1455. *
  1456. * @memberOf Call
  1457. * @inner
  1458. * @throws {Error} Error if stream status is not {@link Flashphoner.constants.STREAM_STATUS.PUBLISHING}
  1459. */
  1460. var switchToCam = function () {
  1461. if(status_ !== CALL_STATUS.ESTABLISHED && status_ !== CALL_STATUS.HOLD){
  1462. throw new Error('Invalid call state');
  1463. }
  1464. mediaConnection.switchToCam();
  1465. };
  1466. /**
  1467. * Get call info
  1468. * @returns {string} Info
  1469. * @memberof Call
  1470. * @inner
  1471. */
  1472. var getInfo = function () {
  1473. return info_;
  1474. };
  1475. /**
  1476. * Get stream error info
  1477. * @returns {string} Error info
  1478. * @memberof Call
  1479. * @inner
  1480. */
  1481. var getErrorInfo = function () {
  1482. return errorInfo_;
  1483. };
  1484. /**
  1485. * Get call logger
  1486. *
  1487. * @returns {Object} Logger
  1488. * @memberof Call
  1489. */
  1490. var getLogger = function () {
  1491. return callLogger;
  1492. };
  1493. call.call = call_;
  1494. call.answer = answer;
  1495. call.hangup = hangup;
  1496. call.id = id;
  1497. call.getInfo = getInfo;
  1498. call.getErrorInfo = getErrorInfo;
  1499. call.status = status;
  1500. call.getStats = getStats;
  1501. call.setAudioOutputId = setAudioOutputId;
  1502. call.setVolume = setVolume;
  1503. call.getVolume = getVolume;
  1504. call.muteAudio = muteAudio;
  1505. call.unmuteAudio = unmuteAudio;
  1506. call.isAudioMuted = isAudioMuted;
  1507. call.muteVideo = muteVideo;
  1508. call.unmuteVideo = unmuteVideo;
  1509. call.isVideoMuted = isVideoMuted;
  1510. call.caller = caller;
  1511. call.callee = callee;
  1512. call.visibleName = visibleName;
  1513. call.hold = hold;
  1514. call.holdForTransfer = holdForTransfer;
  1515. call.unhold = unhold;
  1516. call.sendDTMF = sendDTMF;
  1517. call.transfer = transfer;
  1518. call.on = on;
  1519. call.switchCam = switchCam;
  1520. call.switchMic = switchMic;
  1521. call.switchToScreen = switchToScreen;
  1522. call.switchToCam = switchToCam;
  1523. call.getLogger = getLogger;
  1524. calls[id_] = call;
  1525. return call;
  1526. };
  1527. /**
  1528. * @callback sdpHook
  1529. * @param {Object} sdp Callback options
  1530. * @param {String} sdp.sdpString Sdp from the server
  1531. * @returns {String} sdp New sdp
  1532. */
  1533. /**
  1534. * Create stream.
  1535. *
  1536. * @param {Object} options Stream options
  1537. * @param {string} options.name Stream name
  1538. * @param {Object=} options.constraints Stream constraints
  1539. * @param {Boolean|Object} [options.constraints.audio=true] Specifies if published stream should have audio. Played stream always should have audio: the property should not be set to false in that case.
  1540. * @param {string=} [options.constraints.audio.outputId] Set width to publish or play stream with this value
  1541. * @param {Boolean|Object} [options.constraints.video=true] Specifies if published or played stream should have video, or sets video constraints
  1542. * @param {Integer} [options.constraints.video.width=0] Set width to publish or play stream with this value
  1543. * @param {Integer} [options.constraints.video.height=0] Set height to publish or play stream with this value
  1544. * @param {Integer} [options.constraints.video.bitrate=0] DEPRECATED FOR PUBLISH: Set bitrate to publish or play stream with this value
  1545. * @param {Integer} [options.constraints.video.minBitrate=0] Set minimal bitrate to publish stream with this value
  1546. * @param {Integer} [options.constraints.video.maxBitrate=0] Set maximal bitrate to publish stream with this value
  1547. * @param {Integer} [options.constraints.video.quality=0] Set quality to play stream with this value
  1548. * @param {MediaStream} [options.constraints.customStream] Set a MediaStream for publish stream from canvas.
  1549. * @param {Boolean=} options.receiveAudio DEPRECATED: Receive audio
  1550. * @param {Boolean=} options.receiveVideo DEPRECATED: Receive video
  1551. * @param {Integer=} options.playWidth DEPRECATED: Set width to play stream with this value
  1552. * @param {Integer=} options.playHeight DEPRECATED: Set height to play stream with this value
  1553. * @param {string=} options.mediaProvider MediaProvider type to use with this stream
  1554. * @param {Boolean} [options.record=false] Enable stream recording
  1555. * @param {Boolean=} options.cacheLocalResources Display will contain local video after stream release
  1556. * @param {HTMLElement} options.display Div element stream should be displayed in
  1557. * @param {Object=} options.custom User provided custom object that will be available in REST App code
  1558. * @param {Integer} [options.flashBufferTime=0] Specifies how long to buffer messages before starting to display the stream (Flash-only)
  1559. * @param {string=} options.stripCodecs Comma separated string of codecs which should be stripped from WebRTC SDP (ex. "H264,PCMA,PCMU,G722")
  1560. * @param {string=} options.rtmpUrl Rtmp url stream should be forwarded to
  1561. * @param {Object=} options.mediaConnectionConstraints Stream specific constraints for underlying RTCPeerConnection
  1562. * @param {Boolean=} options.flashShowFullScreenButton Show full screen button in flash
  1563. * @param {string=} options.transport Transport to be used by server for WebRTC media, {@link Flashphoner.constants.TRANSPORT_TYPE}
  1564. * @param {Boolean=} options.cvoExtension Enable rtp video orientation extension
  1565. * @param {Integer=} options.playoutDelay Time delay between network reception of media and playout
  1566. * @param {string=} options.useCanvasMediaStream EXPERIMENTAL: when publish bind browser's media stream to the canvas. It can be useful for image filtering
  1567. * @param {string=} options.videoContentHint Video content hint for browser ('motion' by default to maintain bitrate and fps), {@link Flashphoner.constants.CONTENT_HINT_TYPE}
  1568. * @param {Boolean=} options.unmutePlayOnStart Unmute playback on start. May be used after user gesture only, so set 'unmutePlayOnStart: false' for autoplay
  1569. * @param {Boolean=} options.useControls Use a standard HTML5 video controls (play, pause, fullscreen). May be a workaround for fullscreen mode to work in Safari 16
  1570. * @param {Object=} options.logger Stream logger options
  1571. * @param {Boolean=} options.collectDeviceInfo Collect a media devices info when publishing a WebRTC stream
  1572. * @param {sdpHook} sdpHook The callback that handles sdp from the server
  1573. * @returns {Stream} Stream
  1574. * @throws {TypeError} Error if no options provided
  1575. * @throws {TypeError} Error if options.name is not specified
  1576. * @throws {Error} Error if session state is not ESTABLISHED
  1577. * @memberof Session
  1578. * @inner
  1579. */
  1580. var createStream = function (options) {
  1581. //Array to transmit promises from stream.available() to streamRefreshHandlers
  1582. var availableCallbacks = [];
  1583. //check session state
  1584. if (sessionStatus !== SESSION_STATUS.ESTABLISHED) {
  1585. throw new Error('Invalid session state ' + sessionStatus);
  1586. }
  1587. //check options
  1588. if (!options) {
  1589. throw new TypeError("options must be provided");
  1590. }
  1591. if (!options.name) {
  1592. throw new TypeError("options.name must be provided");
  1593. }
  1594. // Set stream logger #WCS-2434
  1595. var streamLogger = createLogger(options.logger, sessionLogger);
  1596. // Override logger for all low level operations
  1597. var logger = streamLogger;
  1598. var clientKf = new KalmanFilter();
  1599. var serverKf = new KalmanFilter();
  1600. var id_ = uuid_v1();
  1601. var name_ = options.name;
  1602. var mediaProvider = options.mediaProvider || getMediaProviders()[0];
  1603. var mediaConnection;
  1604. var display = options.display;
  1605. // Constraints
  1606. if (options.constraints && Object.keys(options.constraints).length != 0) {
  1607. var constraints = options.constraints;
  1608. }
  1609. if (options.disableConstraintsNormalization) {
  1610. var disableConstraintsNormalization = options.disableConstraintsNormalization;
  1611. }
  1612. var mediaConnectionConstraints = options.mediaConnectionConstraints;
  1613. // Receive media
  1614. var receiveAudio;
  1615. var audioOutputId;
  1616. var audioProperty = getConstraintsProperty(constraints, CONSTRAINT_AUDIO, undefined);
  1617. if (typeof audioProperty === 'boolean') {
  1618. receiveAudio = audioProperty;
  1619. } else if (typeof audioProperty === 'object') {
  1620. receiveAudio = true;
  1621. var _stereo = getConstraintsProperty(audioProperty, CONSTRAINT_AUDIO_STEREO, 0);
  1622. var _bitrate = getConstraintsProperty(audioProperty, CONSTRAINT_AUDIO_BITRATE, 0);
  1623. var _fec = getConstraintsProperty(audioProperty, CONSTRAINT_AUDIO_FEC, 0);
  1624. audioOutputId = getConstraintsProperty(audioProperty, CONSTRAINT_AUDIO_OUTPUT_ID, 0);
  1625. var _codecOptions = "";
  1626. if (_bitrate) _codecOptions += "maxaveragebitrate=" + _bitrate + ";";
  1627. if (_stereo) _codecOptions += "stereo=1;sprop-stereo=1;";
  1628. if (_fec) _codecOptions += "useinbandfec=1;";
  1629. } else {
  1630. receiveAudio = (typeof options.receiveAudio !== 'undefined') ? options.receiveAudio : true;
  1631. }
  1632. var receiveVideo;
  1633. var videoProperty = getConstraintsProperty(constraints, CONSTRAINT_VIDEO, undefined);
  1634. if (typeof videoProperty === 'boolean') {
  1635. receiveVideo = videoProperty;
  1636. } else if (typeof videoProperty === 'object') {
  1637. receiveVideo = true;
  1638. } else {
  1639. receiveVideo = (typeof options.receiveVideo !== 'undefined') ? options.receiveVideo : true;
  1640. }
  1641. // Bitrate
  1642. var bitrate = getConstraintsProperty(constraints, CONSTRAINT_VIDEO_BITRATE, 0);
  1643. var minBitrate = getConstraintsProperty(constraints, CONSTRAINT_VIDEO_MIN_BITRATE, 0);
  1644. var maxBitrate = getConstraintsProperty(constraints, CONSTRAINT_VIDEO_MAX_BITRATE, 0);
  1645. // Quality
  1646. var quality = getConstraintsProperty(constraints, CONSTRAINT_VIDEO_QUALITY, 0);
  1647. if (quality > 100) quality = 100;
  1648. // Play resolution
  1649. var playWidth = (typeof options.playWidth !== 'undefined') ? options.playWidth : getConstraintsProperty(constraints, CONSTRAINT_VIDEO_WIDTH, 0);
  1650. var playHeight = (typeof options.playHeight !== 'undefined') ? options.playHeight : getConstraintsProperty(constraints, CONSTRAINT_VIDEO_HEIGHT, 0);
  1651. var stripCodecs = options.stripCodecs || [];
  1652. var resolution = {};
  1653. var published_ = false;
  1654. var record_ = options.record || false;
  1655. var recordFileName = null;
  1656. var cacheLocalResources = options.cacheLocalResources;
  1657. var status_ = STREAM_STATUS.NEW;
  1658. var rtmpUrl = options.rtmpUrl;
  1659. var info_;
  1660. var errorInfo_;
  1661. var remoteBitrate = -1;
  1662. var networkBandwidth = -1;
  1663. var sdpHook = options.sdpHook;
  1664. var transportType = options.transport;
  1665. var cvoExtension = options.cvoExtension;
  1666. var remoteVideo = options.remoteVideo;
  1667. //callbacks added using stream.on()
  1668. var callbacks = {};
  1669. var playoutDelay = options.playoutDelay;
  1670. var useCanvasMediaStream = options.useCanvasMediaStream;
  1671. var videoContentHint = options.videoContentHint;
  1672. var unmutePlayOnStart = options.unmutePlayOnStart;
  1673. var useControls = options.useControls;
  1674. var collectDeviceInfo = options.collectDeviceInfo !== undefined ? options.collectDeviceInfo : true;
  1675. var audioState_;
  1676. var videoState_;
  1677. var connectionQuality;
  1678. var videoBytes = 0;
  1679. /**
  1680. * Represents media stream.
  1681. *
  1682. * @namespace Stream
  1683. * @see Session~createStream
  1684. */
  1685. var stream = {};
  1686. streamEventRefreshHandlers[id_] = function (streamEvent) {
  1687. if (streamEvent.type == STREAM_EVENT_TYPE.NOT_ENOUGH_BANDWIDTH) {
  1688. var info = streamEvent.payload.info.split("/");
  1689. remoteBitrate = info[0];
  1690. networkBandwidth = info[1];
  1691. } else if (streamEvent.type == STREAM_EVENT_TYPE.RESIZE) {
  1692. resolution.width = streamEvent.payload.streamerVideoWidth;
  1693. resolution.height = streamEvent.payload.streamerVideoHeight;
  1694. }
  1695. if (callbacks[STREAM_EVENT]) {
  1696. callbacks[STREAM_EVENT](streamEvent);
  1697. }
  1698. }
  1699. streamRefreshHandlers[id_] = function (streamInfo, sdp) {
  1700. //set remote sdp
  1701. if (sdp && sdp !== '') {
  1702. var _sdp = sdp;
  1703. if (_codecOptions) _sdp = util.SDP.writeFmtp(sdp, _codecOptions, "opus");
  1704. _sdp = sdpHookHandler(_sdp, sdpHook);
  1705. // Adjust publishing bitrate #WCS-4013
  1706. _sdp = util.setPublishingBitrate(_sdp, mediaConnection, minBitrate, maxBitrate);
  1707. mediaConnection.setRemoteSdp(_sdp).then(function () {});
  1708. return;
  1709. }
  1710. if (streamInfo.available != undefined) {
  1711. for (var i = 0; i < availableCallbacks.length; i++) {
  1712. info_ = streamInfo.reason;
  1713. if (streamInfo.available == "true") {
  1714. availableCallbacks[i].resolve(stream);
  1715. } else {
  1716. availableCallbacks[i].reject(stream);
  1717. }
  1718. }
  1719. availableCallbacks = [];
  1720. return;
  1721. }
  1722. var event = streamInfo.status;
  1723. if (event === INBOUND_VIDEO_RATE || event === OUTBOUND_VIDEO_RATE) {
  1724. detectConnectionQuality(event, streamInfo);
  1725. return;
  1726. }
  1727. if (event === STREAM_EVENT) {
  1728. if (!streamInfo.mediaSessionId)
  1729. streamInfo.mediaSessionId = id_;
  1730. streamEventRefreshHandlers[id_](streamInfo);
  1731. return;
  1732. }
  1733. //Deprecated. WCS-3228: RESIZE, SNAPSHOT_COMPLETE and NOT_ENOUGH_BANDWIDTH moved to STREAM_EVENT
  1734. if (event === STREAM_STATUS.RESIZE) {
  1735. resolution.width = streamInfo.streamerVideoWidth;
  1736. resolution.height = streamInfo.streamerVideoHeight;
  1737. } else if (event === STREAM_STATUS.SNAPSHOT_COMPLETE) {
  1738. } else if (event === STREAM_STATUS.NOT_ENOUGH_BANDWIDTH) {
  1739. var info = streamInfo.info.split("/");
  1740. remoteBitrate = info[0];
  1741. networkBandwidth = info[1];
  1742. } else {
  1743. status_ = event;
  1744. }
  1745. if (streamInfo.audioState)
  1746. audioState_ = streamInfo.audioState;
  1747. if (streamInfo.videoState)
  1748. videoState_ = streamInfo.videoState;
  1749. if (streamInfo.info)
  1750. info_ = streamInfo.info;
  1751. //release stream
  1752. if (event === STREAM_STATUS.FAILED || event === STREAM_STATUS.STOPPED ||
  1753. event === STREAM_STATUS.UNPUBLISHED) {
  1754. delete streams[id_];
  1755. delete streamRefreshHandlers[id_];
  1756. delete streamEventRefreshHandlers[id_];
  1757. if (mediaConnection) {
  1758. mediaConnection.close(cacheLocalResources);
  1759. }
  1760. }
  1761. if (record_ && typeof streamInfo.recordName !== 'undefined') {
  1762. recordFileName = streamInfo.recordName;
  1763. }
  1764. //fire stream event
  1765. if (callbacks[event]) {
  1766. callbacks[event](stream);
  1767. }
  1768. };
  1769. var detectConnectionQuality = function (event, streamInfo) {
  1770. if (disableConnectionQualityCalculation) {
  1771. return;
  1772. }
  1773. mediaConnection.getStats(function (stats) {
  1774. var bytesSentReceived = 0;
  1775. if (stats) {
  1776. if (event == OUTBOUND_VIDEO_RATE && stats.inboundStream && stats.inboundStream.video && stats.inboundStream.video.bytesReceived > 0) {
  1777. bytesSentReceived = stats.inboundStream.video.bytesReceived;
  1778. } else if (stats.outboundStream && stats.outboundStream.video && stats.outboundStream.video.bytesSent > 0) {
  1779. bytesSentReceived = stats.outboundStream.video.bytesSent;
  1780. } else {
  1781. return;
  1782. }
  1783. }
  1784. if (!videoBytes) {
  1785. videoBytes = bytesSentReceived;
  1786. return;
  1787. }
  1788. var currentVideoRate = ((bytesSentReceived - videoBytes) * 8);
  1789. if (currentVideoRate == 0) {
  1790. return;
  1791. }
  1792. var clientFiltered = clientKf.filter(currentVideoRate);
  1793. var serverFiltered = serverKf.filter(streamInfo.videoRate);
  1794. var videoRateDifference = Math.abs((serverFiltered - clientFiltered) / ((serverFiltered + clientFiltered) / 2)) * 100;
  1795. var currentQuality;
  1796. if (serverFiltered < LOW_VIDEO_RATE_THRESHOLD_BAD_PERFECT || clientFiltered < LOW_VIDEO_RATE_THRESHOLD_BAD_PERFECT) {
  1797. if (videoRateDifference > LOW_VIDEO_RATE_BAD_QUALITY_PERCENT_DIFFERENCE) {
  1798. currentQuality = CONNECTION_QUALITY.BAD;
  1799. } else {
  1800. currentQuality = CONNECTION_QUALITY.PERFECT;
  1801. }
  1802. } else {
  1803. if (videoRateDifference > VIDEO_RATE_BAD_QUALITY_PERCENT_DIFFERENCE) {
  1804. currentQuality = CONNECTION_QUALITY.BAD;
  1805. } else if (videoRateDifference > VIDEO_RATE_GOOD_QUALITY_PERCENT_DIFFERENCE) {
  1806. currentQuality = CONNECTION_QUALITY.GOOD;
  1807. } else {
  1808. currentQuality = CONNECTION_QUALITY.PERFECT;
  1809. }
  1810. }
  1811. if (callbacks[CONNECTION_QUALITY.UPDATE]) {
  1812. connectionQuality = currentQuality;
  1813. callbacks[CONNECTION_QUALITY.UPDATE](connectionQuality, clientFiltered, serverFiltered);
  1814. }
  1815. videoBytes = bytesSentReceived;
  1816. });
  1817. return;
  1818. };
  1819. /**
  1820. * Play stream.
  1821. *
  1822. * @throws {Error} Error if stream status is not {@link Flashphoner.constants.STREAM_STATUS.NEW}
  1823. * @memberof Stream
  1824. * @inner
  1825. */
  1826. var play = function () {
  1827. logger.debug(LOG_PREFIX, "Play stream " + name_);
  1828. if (status_ !== STREAM_STATUS.NEW) {
  1829. throw new Error("Invalid stream state " + status_);
  1830. }
  1831. status_ = STREAM_STATUS.PENDING;
  1832. //create mediaProvider connection
  1833. MediaProvider[mediaProvider].createConnection({
  1834. id: id_,
  1835. display: display,
  1836. authToken: authToken,
  1837. mainUrl: urlServer,
  1838. flashProto: flashProto,
  1839. flashPort: flashPort,
  1840. flashBufferTime: options.flashBufferTime || 0,
  1841. flashShowFullScreenButton: options.flashShowFullScreenButton || false,
  1842. connectionConfig: mediaOptions,
  1843. connectionConstraints: mediaConnectionConstraints,
  1844. audioOutputId: audioOutputId,
  1845. remoteVideo: remoteVideo,
  1846. playoutDelay: playoutDelay,
  1847. unmutePlayOnStart: unmutePlayOnStart,
  1848. useControls: useControls,
  1849. logger: logger,
  1850. unmuteRequiredEvent: fireUnmuteEvent
  1851. }, streamRefreshHandlers[id_]).then(function (newConnection) {
  1852. mediaConnection = newConnection;
  1853. try {
  1854. streamRefreshHandlers[id_]({status: status_});
  1855. } catch(e) {
  1856. console.warn(e);
  1857. }
  1858. return mediaConnection.createOffer({
  1859. receiveAudio: receiveAudio,
  1860. receiveVideo: receiveVideo,
  1861. stripCodecs: stripCodecs,
  1862. stereo: _stereo
  1863. });
  1864. }).then(function (offer) {
  1865. logger.debug(LOG_PREFIX, "Offer SDP:\n" + offer.sdp);
  1866. //request stream with offer sdp from server
  1867. send("playStream", {
  1868. mediaSessionId: id_,
  1869. name: name_,
  1870. published: published_,
  1871. hasVideo: true,
  1872. hasAudio: true,
  1873. status: status_,
  1874. record: false,
  1875. width: playWidth,
  1876. height: playHeight,
  1877. mediaProvider: mediaProvider,
  1878. sdp: offer.sdp,
  1879. custom: options.custom,
  1880. bitrate: bitrate,
  1881. minBitrate: minBitrate,
  1882. maxBitrate: maxBitrate,
  1883. quality: quality,
  1884. constraints: constraints,
  1885. transport: transportType,
  1886. cvoExtension: cvoExtension
  1887. });
  1888. if (offer.player) {
  1889. offer.player.play(id_);
  1890. }
  1891. }).catch(function (error) {
  1892. //todo fire stream failed status
  1893. throw error;
  1894. });
  1895. };
  1896. /**
  1897. * Publish stream.
  1898. *
  1899. * @throws {Error} Error if stream status is not {@link Flashphoner.constants.STREAM_STATUS.NEW}
  1900. * @memberof Stream
  1901. * @inner
  1902. */
  1903. var publish = function () {
  1904. logger.debug(LOG_PREFIX, "Publish stream " + name_);
  1905. if (status_ !== STREAM_STATUS.NEW) {
  1906. throw new Error("Invalid stream state " + status_);
  1907. }
  1908. status_ = STREAM_STATUS.PENDING;
  1909. published_ = true;
  1910. var hasAudio = true;
  1911. if (constraints && constraints.video && constraints.video.type && constraints.video.type == "screen") {
  1912. hasAudio = false;
  1913. }
  1914. //get access to camera
  1915. MediaProvider[mediaProvider].getMediaAccess(constraints, display, disableConstraintsNormalization, useCanvasMediaStream).then(function () {
  1916. if (status_ == STREAM_STATUS.FAILED) {
  1917. //stream failed while we were waiting for media access, release media
  1918. if (!cacheLocalResources) {
  1919. releaseLocalMedia(display, mediaProvider);
  1920. }
  1921. return;
  1922. }
  1923. //create mediaProvider connection
  1924. MediaProvider[mediaProvider].createConnection({
  1925. id: id_,
  1926. display: display,
  1927. authToken: authToken,
  1928. mainUrl: urlServer,
  1929. flashProto: flashProto,
  1930. flashPort: flashPort,
  1931. constraints: constraints,
  1932. connectionConfig: mediaOptions,
  1933. connectionConstraints: mediaConnectionConstraints,
  1934. customStream: constraints && constraints.customStream ? constraints.customStream : false,
  1935. videoContentHint: videoContentHint,
  1936. useControls: useControls,
  1937. logger: logger
  1938. }).then(function (newConnection) {
  1939. mediaConnection = newConnection;
  1940. return mediaConnection.createOffer({
  1941. stripCodecs: stripCodecs
  1942. });
  1943. }).then(function (offer) {
  1944. logger.debug(LOG_PREFIX, "Offer SDP:\n" + offer.sdp);
  1945. let publishStreamData = {
  1946. mediaSessionId: id_,
  1947. name: name_,
  1948. published: published_,
  1949. hasVideo: offer.hasVideo,
  1950. hasAudio: offer.hasAudio,
  1951. status: status_,
  1952. record: record_,
  1953. mediaProvider: mediaProvider,
  1954. sdp: offer.sdp,
  1955. custom: options.custom,
  1956. bitrate: bitrate,
  1957. minBitrate: minBitrate,
  1958. maxBitrate: maxBitrate,
  1959. rtmpUrl: rtmpUrl,
  1960. constraints: constraints,
  1961. transport: transportType,
  1962. cvoExtension: cvoExtension
  1963. };
  1964. // Get local media info to send in publishStream message
  1965. if (collectDeviceInfo) {
  1966. publishStreamData.localMediaInfo = collectLocalMediaInfo(MediaProvider[mediaProvider], display);
  1967. }
  1968. //publish stream with offer sdp to server
  1969. send("publishStream", publishStreamData);
  1970. });
  1971. }).catch(function (error) {
  1972. logger.warn(LOG_PREFIX, error);
  1973. info_ = ERROR_INFO.LOCAL_ERROR;
  1974. errorInfo_ = error.message;
  1975. status_ = STREAM_STATUS.FAILED;
  1976. //fire stream event
  1977. if (callbacks[status_]) {
  1978. callbacks[status_](stream);
  1979. }
  1980. });
  1981. };
  1982. /**
  1983. * Switch camera in real-time.
  1984. * Works only with WebRTC
  1985. *
  1986. * @memberOf Stream
  1987. * @inner
  1988. * @throws {Error} Error if stream status is not {@link Flashphoner.constants.STREAM_STATUS.PUBLISHING}
  1989. */
  1990. var switchCam = function(deviceId) {
  1991. if(status_ !== STREAM_STATUS.PUBLISHING){
  1992. throw new Error('Invalid stream state');
  1993. }
  1994. return mediaConnection.switchCam(deviceId);
  1995. };
  1996. /**
  1997. * Switch microphone in real-time.
  1998. * Works only with WebRTC
  1999. *
  2000. * @memberOf Stream
  2001. * @inner
  2002. * @throws {Error} Error if stream status is not {@link Flashphoner.constants.STREAM_STATUS.PUBLISHING}
  2003. */
  2004. var switchMic = function(deviceId) {
  2005. if(status_ !== STREAM_STATUS.PUBLISHING){
  2006. throw new Error('Invalid stream state');
  2007. }
  2008. return mediaConnection.switchMic(deviceId);
  2009. };
  2010. /**
  2011. * Switch to screen in real-time.
  2012. * Works only with WebRTC
  2013. *
  2014. * @param {String} source Screen sharing source (for firefox)
  2015. * @param {Boolean} woExtension Screen sharing without extension (for chrome)
  2016. * @memberOf Stream
  2017. * @inner
  2018. * @throws {Error} Error if stream status is not {@link Flashphoner.constants.STREAM_STATUS.PUBLISHING}
  2019. */
  2020. var switchToScreen = function (source, woExtension) {
  2021. if(status_ !== STREAM_STATUS.PUBLISHING){
  2022. throw new Error('Invalid stream state');
  2023. }
  2024. return mediaConnection.switchToScreen(source, woExtension);
  2025. };
  2026. /**
  2027. * Switch to cam in real-time.
  2028. * Works only with WebRTC
  2029. *
  2030. * @memberOf Stream
  2031. * @inner
  2032. * @throws {Error} Error if stream status is not {@link Flashphoner.constants.STREAM_STATUS.PUBLISHING}
  2033. */
  2034. var switchToCam = function () {
  2035. if(status_ !== STREAM_STATUS.PUBLISHING){
  2036. throw new Error('Invalid stream state');
  2037. }
  2038. mediaConnection.switchToCam();
  2039. };
  2040. /**
  2041. * Send data from published stream.
  2042. *
  2043. * @param {Object} payload Any object
  2044. * @throws {Error} Error if stream status is not {@link Flashphoner.constants.STREAM_STATUS.PUBLISHING}
  2045. * @memberof Stream
  2046. * @inner
  2047. */
  2048. var sendData = function (payload) {
  2049. if(status_ !== STREAM_STATUS.PUBLISHING){
  2050. throw new Error('Invalid stream state');
  2051. }
  2052. sendStreamEvent(STREAM_EVENT_TYPE.DATA, payload);
  2053. };
  2054. /**
  2055. * Unmute remote audio
  2056. *
  2057. * @memberOf Stream
  2058. * @inner
  2059. */
  2060. var unmuteRemoteAudio = function () {
  2061. if(mediaConnection && mediaProvider != 'Flash') {
  2062. mediaConnection.unmuteRemoteAudio();
  2063. }
  2064. };
  2065. /**
  2066. * Mute remote audio
  2067. *
  2068. * @memberOf Stream
  2069. * @inner
  2070. */
  2071. var muteRemoteAudio = function () {
  2072. if(mediaConnection && mediaProvider != 'Flash') {
  2073. mediaConnection.muteRemoteAudio();
  2074. }
  2075. };
  2076. /**
  2077. * Is remote audio muted
  2078. *
  2079. * @memberOf Stream
  2080. * @inner
  2081. */
  2082. var isRemoteAudioMuted = function () {
  2083. if(mediaConnection && mediaProvider != 'Flash') {
  2084. return mediaConnection.isRemoteAudioMuted();
  2085. }
  2086. return false;
  2087. };
  2088. /**
  2089. * Set Microphone Gain
  2090. *
  2091. * @memberOf Stream
  2092. * @inner
  2093. * @throws {Error} Error if stream status is not {@link Flashphoner.constants.STREAM_STATUS.PUBLISHING}
  2094. */
  2095. var setMicrophoneGain = function (volume) {
  2096. if(status_ !== STREAM_STATUS.PUBLISHING){
  2097. throw new Error('Invalid stream state');
  2098. }
  2099. mediaConnection.setMicrophoneGain(volume);
  2100. };
  2101. /**
  2102. * Stop stream.
  2103. *
  2104. * @memberof Stream
  2105. * @inner
  2106. */
  2107. var stop = function () {
  2108. logger.debug(LOG_PREFIX, "Stop stream " + name_);
  2109. if (status_ == STREAM_STATUS.NEW) {
  2110. //trigger FAILED status
  2111. streamRefreshHandlers[id_]({status: STREAM_STATUS.FAILED});
  2112. return;
  2113. } else if (status_ == STREAM_STATUS.PENDING) {
  2114. logger.warn(LOG_PREFIX, "Stopping stream before server response " + id_);
  2115. setTimeout(stop, 200);
  2116. return;
  2117. } else if (status_ == STREAM_STATUS.FAILED) {
  2118. logger.warn(LOG_PREFIX, "Stream status FAILED");
  2119. return;
  2120. }
  2121. if (published_) {
  2122. send("unPublishStream", {
  2123. mediaSessionId: id_,
  2124. name: name_,
  2125. published: published_,
  2126. hasVideo: true,
  2127. hasAudio: true,
  2128. status: status_,
  2129. record: false
  2130. });
  2131. } else {
  2132. send("stopStream", {
  2133. mediaSessionId: id_,
  2134. name: name_,
  2135. published: published_,
  2136. hasVideo: true,
  2137. hasAudio: true,
  2138. status: status_,
  2139. record: false
  2140. });
  2141. }
  2142. //free media provider
  2143. if (mediaConnection) {
  2144. mediaConnection.close(cacheLocalResources);
  2145. }
  2146. };
  2147. /**
  2148. * Request remote stream snapshot.
  2149. * @throws {Error} Error if stream status is not {@link Flashphoner.constants.STREAM_STATUS.NEW}
  2150. * @memberof Stream
  2151. * @inner
  2152. */
  2153. var snapshot = function () {
  2154. logger.debug(LOG_PREFIX, "Request snapshot, stream " + name_);
  2155. if (status_ !== STREAM_STATUS.NEW && status_ !== STREAM_STATUS.PLAYING && status_ !== STREAM_STATUS.PUBLISHING) {
  2156. throw new Error("Invalid stream state");
  2157. }
  2158. send("snapshot", {
  2159. name: name_,
  2160. mediaSessionId: id_
  2161. });
  2162. };
  2163. /**
  2164. * Get stream status.
  2165. *
  2166. * @returns {string} One of {@link Flashphoner.constants.STREAM_STATUS}
  2167. * @memberof Stream
  2168. * @inner
  2169. */
  2170. var status = function () {
  2171. return status_;
  2172. };
  2173. /**
  2174. * Get stream id.
  2175. *
  2176. * @returns {string} Stream id
  2177. * @memberof Stream
  2178. * @inner
  2179. */
  2180. var id = function () {
  2181. return id_;
  2182. };
  2183. /**
  2184. * Get stream name.
  2185. *
  2186. * @returns {string} Stream name
  2187. * @memberof Stream
  2188. * @inner
  2189. */
  2190. var name = function () {
  2191. return name_;
  2192. };
  2193. /**
  2194. * Is stream published.
  2195. *
  2196. * @returns {Boolean} True if stream published, otherwise false
  2197. * @memberof Stream
  2198. * @inner
  2199. */
  2200. var published = function () {
  2201. return published_;
  2202. };
  2203. /**
  2204. * Get record file name
  2205. * @returns {string} File name
  2206. * @memberof Stream
  2207. * @inner
  2208. */
  2209. var getRecordInfo = function () {
  2210. return recordFileName;
  2211. };
  2212. /**
  2213. * Get stream info
  2214. * @returns {string} Info
  2215. * @memberof Stream
  2216. * @inner
  2217. */
  2218. var getInfo = function () {
  2219. return info_;
  2220. };
  2221. /**
  2222. * Get stream error info
  2223. * @returns {string} Error info
  2224. * @memberof Stream
  2225. * @inner
  2226. */
  2227. var getErrorInfo = function () {
  2228. return errorInfo_;
  2229. };
  2230. /**
  2231. * Get stream video size
  2232. * @returns {Object} Video size
  2233. * @memberof Stream
  2234. * @inner
  2235. */
  2236. var videoResolution = function () {
  2237. if (!published_) {
  2238. return resolution;
  2239. } else {
  2240. throw new Error("This function available only on playing stream");
  2241. }
  2242. };
  2243. /**
  2244. * Media controls
  2245. */
  2246. /**
  2247. * Set other oupout audio device
  2248. *
  2249. * @param {string} id Id of output device
  2250. * @memberof Call
  2251. * @inner
  2252. */
  2253. var setAudioOutputId = function(id) {
  2254. audioOutputId = id;
  2255. if (mediaConnection && mediaConnection.setAudioOutputId) {
  2256. return mediaConnection.setAudioOutputId(id);
  2257. }
  2258. };
  2259. /**
  2260. * Set volume of remote media
  2261. *
  2262. * @param {number} volume Volume between 0 and 100
  2263. * @memberof Stream
  2264. * @inner
  2265. */
  2266. var setVolume = function (volume) {
  2267. if (mediaConnection) {
  2268. mediaConnection.setVolume(volume);
  2269. }
  2270. };
  2271. /**
  2272. * Get current volume
  2273. *
  2274. * @returns {number} Volume or -1 if audio is not available
  2275. * @memberof Stream
  2276. * @inner
  2277. */
  2278. var getVolume = function () {
  2279. if (mediaConnection) {
  2280. return mediaConnection.getVolume();
  2281. }
  2282. return -1;
  2283. };
  2284. function sendStreamEvent(type, payload) {
  2285. send("sendStreamEvent", {
  2286. mediaSessionId: id_,
  2287. type: type,
  2288. payload: payload
  2289. });
  2290. }
  2291. /**
  2292. * Mute outgoing audio
  2293. *
  2294. * @memberof Stream
  2295. * @inner
  2296. */
  2297. var muteAudio = function () {
  2298. if (mediaConnection) {
  2299. mediaConnection.muteAudio();
  2300. sendStreamEvent(STREAM_EVENT_TYPE.AUDIO_MUTED);
  2301. }
  2302. };
  2303. /**
  2304. * Unmute outgoing audio
  2305. *
  2306. * @memberof Stream
  2307. * @inner
  2308. */
  2309. var unmuteAudio = function () {
  2310. if (mediaConnection) {
  2311. mediaConnection.unmuteAudio();
  2312. sendStreamEvent(STREAM_EVENT_TYPE.AUDIO_UNMUTED);
  2313. }
  2314. };
  2315. /**
  2316. * Check outgoing audio mute state
  2317. *
  2318. * @returns {boolean} True if audio is muted or not available
  2319. * @memberof Stream
  2320. * @inner
  2321. */
  2322. var isAudioMuted = function () {
  2323. if (mediaConnection) {
  2324. return mediaConnection.isAudioMuted();
  2325. }
  2326. return true;
  2327. };
  2328. /**
  2329. * Mute outgoing video
  2330. *
  2331. * @memberof Stream
  2332. * @inner
  2333. */
  2334. var muteVideo = function () {
  2335. if (mediaConnection) {
  2336. mediaConnection.muteVideo();
  2337. sendStreamEvent(STREAM_EVENT_TYPE.VIDEO_MUTED);
  2338. }
  2339. };
  2340. /**
  2341. * Unmute outgoing video
  2342. *
  2343. * @memberof Stream
  2344. * @inner
  2345. */
  2346. var unmuteVideo = function () {
  2347. if (mediaConnection) {
  2348. mediaConnection.unmuteVideo();
  2349. sendStreamEvent(STREAM_EVENT_TYPE.VIDEO_UNMUTED);
  2350. }
  2351. };
  2352. /**
  2353. * Check outgoing video mute state
  2354. *
  2355. * @returns {boolean} True if video is muted or not available
  2356. * @memberof Stream
  2357. * @inner
  2358. */
  2359. var isVideoMuted = function () {
  2360. if (mediaConnection) {
  2361. return mediaConnection.isVideoMuted();
  2362. }
  2363. return true;
  2364. };
  2365. /**
  2366. * Get statistics
  2367. *
  2368. * @param {callbackFn} callbackFn The callback that handles response
  2369. * @param {Boolean} nativeStats If true, use native browser statistics
  2370. * @returns {Object} Stream audio\video statistics
  2371. * @memberof Stream
  2372. * @inner
  2373. */
  2374. var getStats = function (callbackFn, nativeStats) {
  2375. if (mediaConnection) {
  2376. mediaConnection.getStats(callbackFn, nativeStats);
  2377. }
  2378. };
  2379. /**
  2380. * Get remote bitrate reported by server, works only for subscribe Stream
  2381. *
  2382. * @returns {number} Remote bitrate in bps or -1
  2383. * @memberof Stream
  2384. * @inner
  2385. */
  2386. var getRemoteBitrate = function () {
  2387. return remoteBitrate;
  2388. };
  2389. /**
  2390. * Get network bandwidth reported by server, works only for subscribe Stream
  2391. *
  2392. * @returns {number} Network bandwidth in bps or -1
  2393. * @memberof Stream
  2394. * @inner
  2395. */
  2396. var getNetworkBandwidth = function () {
  2397. return networkBandwidth;
  2398. };
  2399. /**
  2400. * Get audio state (muted)
  2401. *
  2402. * @returns AudioState
  2403. * @memberof Stream
  2404. * @inner
  2405. */
  2406. var getAudioState = function () {
  2407. return audioState_;
  2408. };
  2409. /**
  2410. * Get video state (muted)
  2411. *
  2412. * @returns VideoState
  2413. * @memberof Stream
  2414. * @inner
  2415. */
  2416. var getVideoState = function () {
  2417. return videoState_;
  2418. };
  2419. /**
  2420. * Request full screen for player stream
  2421. * @memberof Stream
  2422. * @inner
  2423. */
  2424. var fullScreen = function () {
  2425. if (published()) {
  2426. logger.warn(LOG_PREFIX, "Full screen is allowed only for played streams");
  2427. } else {
  2428. if (mediaConnection)
  2429. mediaConnection.fullScreen();
  2430. }
  2431. };
  2432. /**
  2433. * Stream status event callback.
  2434. *
  2435. * @callback Stream~eventCallback
  2436. * @param {Stream} stream Stream that corresponds to the event
  2437. */
  2438. /**
  2439. * Add stream status event callback.
  2440. *
  2441. * @param {string} event One of {@link Flashphoner.constants.STREAM_STATUS} events
  2442. * @param {Stream~eventCallback} callback Callback function
  2443. * @returns {Stream} Stream callback was attached to
  2444. * @throws {TypeError} Error if event is not specified
  2445. * @throws {Error} Error if callback is not a valid function
  2446. * @memberof Stream
  2447. * @inner
  2448. */
  2449. var on = function (event, callback) {
  2450. if (!event) {
  2451. throw new TypeError("Event can't be null");
  2452. }
  2453. if (!callback || typeof callback !== 'function') {
  2454. throw new Error("Callback needs to be a valid function");
  2455. }
  2456. callbacks[event] = callback;
  2457. return stream;
  2458. };
  2459. /**
  2460. * Сhecks the availability of stream on the server
  2461. *
  2462. * @returns {Promise} Resolves if is stream available, otherwise rejects
  2463. * @memberof Stream
  2464. * @inner
  2465. */
  2466. var available = function(){
  2467. return new Promise(function(resolve, reject){
  2468. send("availableStream", {
  2469. mediaSessionId: id_,
  2470. name: name_
  2471. });
  2472. var promise = {};
  2473. promise.resolve = resolve;
  2474. promise.reject = reject;
  2475. availableCallbacks.push(promise);
  2476. });
  2477. };
  2478. /**
  2479. * Get stream logger
  2480. *
  2481. * @returns {Object} Logger
  2482. * @memberof Stream
  2483. */
  2484. var getLogger = function () {
  2485. return streamLogger;
  2486. };
  2487. var fireUnmuteEvent = function() {
  2488. if (isRemoteAudioMuted()) {
  2489. if (streamRefreshHandlers[id_] && typeof streamRefreshHandlers[id_] === 'function') {
  2490. streamRefreshHandlers[id_]({status: STREAM_EVENT, type: STREAM_EVENT_TYPE.UNMUTE_REQUIRED});
  2491. }
  2492. }
  2493. };
  2494. stream.play = play;
  2495. stream.publish = publish;
  2496. stream.stop = stop;
  2497. stream.id = id;
  2498. stream.status = status;
  2499. stream.name = name;
  2500. stream.published = published;
  2501. stream.getRecordInfo = getRecordInfo;
  2502. stream.getInfo = getInfo;
  2503. stream.getErrorInfo = getErrorInfo;
  2504. stream.videoResolution = videoResolution;
  2505. stream.setAudioOutputId = setAudioOutputId;
  2506. stream.setVolume = setVolume;
  2507. stream.unmuteRemoteAudio = unmuteRemoteAudio;
  2508. stream.muteRemoteAudio = muteRemoteAudio;
  2509. stream.isRemoteAudioMuted = isRemoteAudioMuted;
  2510. stream.setMicrophoneGain = setMicrophoneGain;
  2511. stream.getVolume = getVolume;
  2512. stream.muteAudio = muteAudio;
  2513. stream.unmuteAudio = unmuteAudio;
  2514. stream.isAudioMuted = isAudioMuted;
  2515. stream.muteVideo = muteVideo;
  2516. stream.unmuteVideo = unmuteVideo;
  2517. stream.isVideoMuted = isVideoMuted;
  2518. stream.getStats = getStats;
  2519. stream.snapshot = snapshot;
  2520. stream.getAudioState = getAudioState;
  2521. stream.getVideoState = getVideoState;
  2522. stream.getNetworkBandwidth = getNetworkBandwidth;
  2523. stream.getRemoteBitrate = getRemoteBitrate;
  2524. stream.fullScreen = fullScreen;
  2525. stream.on = on;
  2526. stream.available = available;
  2527. stream.switchCam = switchCam;
  2528. stream.switchMic = switchMic;
  2529. stream.switchToScreen = switchToScreen;
  2530. stream.switchToCam = switchToCam;
  2531. stream.sendData = sendData;
  2532. stream.getLogger = getLogger;
  2533. streams[id_] = stream;
  2534. return stream;
  2535. };
  2536. /**
  2537. * Disconnect session.
  2538. *
  2539. * @memberof Session
  2540. * @inner
  2541. */
  2542. var disconnect = function () {
  2543. if (wsConnection) {
  2544. wsConnection.close();
  2545. }
  2546. };
  2547. /**
  2548. * Get session id
  2549. *
  2550. * @returns {string} session id
  2551. * @memberof Session
  2552. * @inner
  2553. */
  2554. var id = function () {
  2555. return id_;
  2556. };
  2557. /**
  2558. * Get server address
  2559. *
  2560. * @returns {string} Server url
  2561. * @memberof Session
  2562. * @inner
  2563. */
  2564. var getServerUrl = function () {
  2565. return urlServer;
  2566. };
  2567. /**
  2568. * Get session status
  2569. *
  2570. * @returns {string} One of {@link Flashphoner.constants.SESSION_STATUS}
  2571. * @memberof Session
  2572. * @inner
  2573. */
  2574. var status = function () {
  2575. return sessionStatus;
  2576. };
  2577. /**
  2578. * Get stream by id.
  2579. *
  2580. * @param {string} streamId Stream id
  2581. * @returns {Stream} Stream
  2582. * @memberof Session
  2583. * @inner
  2584. */
  2585. var getStream = function (streamId) {
  2586. return streams[streamId];
  2587. };
  2588. /**
  2589. * Get streams.
  2590. *
  2591. * @returns {Array<Stream>} Streams
  2592. * @memberof Session
  2593. * @inner
  2594. */
  2595. var getStreams = function () {
  2596. return util.copyObjectToArray(streams);
  2597. };
  2598. /**
  2599. * Submit bug report.
  2600. *
  2601. * @param {Object} reportObject Report object
  2602. * @memberof Session
  2603. * @inner
  2604. */
  2605. var submitBugReport = function (reportObject) {
  2606. send("submitBugReport", reportObject);
  2607. }
  2608. /**
  2609. * Start session debug
  2610. * @memberof Session
  2611. * @inner
  2612. */
  2613. var startDebug = function () {
  2614. logger.setPushLogs(true);
  2615. logger.setLevel("DEBUG");
  2616. send("sessionDebug", {command: "start"});
  2617. }
  2618. /**
  2619. * Stop session debug
  2620. * @memberof Session
  2621. * @inner
  2622. */
  2623. var stopDebug = function () {
  2624. logger.setLevel("INFO");
  2625. send("sessionDebug", {command: "stop"});
  2626. }
  2627. /**
  2628. * Session event callback.
  2629. *
  2630. * @callback Session~eventCallback
  2631. * @param {Session} session Session that corresponds to the event
  2632. */
  2633. /**
  2634. * Add session event callback.
  2635. *
  2636. * @param {string} event One of {@link Flashphoner.constants.SESSION_STATUS} events
  2637. * @param {Session~eventCallback} callback Callback function
  2638. * @returns {Session} Session
  2639. * @throws {TypeError} Error if event is not specified
  2640. * @throws {Error} Error if callback is not a valid function
  2641. * @memberof Session
  2642. * @inner
  2643. */
  2644. var on = function (event, callback) {
  2645. if (!event) {
  2646. throw new Error("Event can't be null", "TypeError");
  2647. }
  2648. if (!callback || typeof callback !== 'function') {
  2649. throw new Error("Callback needs to be a valid function");
  2650. }
  2651. callbacks[event] = callback;
  2652. return session;
  2653. };
  2654. var restAppCommunicator = function () {
  2655. var pending = {};
  2656. var exports = {};
  2657. /**
  2658. * Send data to REST App
  2659. *
  2660. * @param {Object} data Object to send
  2661. * @returns {Promise} Resolves if data accepted, otherwise rejects
  2662. * @memberof Session
  2663. * @name sendData
  2664. * @method
  2665. * @inner
  2666. */
  2667. exports.sendData = function (data) {
  2668. return new Promise(function (resolve, reject) {
  2669. var obj = {
  2670. operationId: uuid_v1(),
  2671. payload: data
  2672. };
  2673. pending[obj.operationId] = {
  2674. FAILED: function (info) {
  2675. reject(info);
  2676. },
  2677. ACCEPTED: function (info) {
  2678. resolve(info);
  2679. }
  2680. };
  2681. send("sendData", obj);
  2682. });
  2683. };
  2684. exports.resolveData = function (data) {
  2685. if (pending[data.operationId]) {
  2686. var handler = pending[data.operationId];
  2687. delete pending[data.operationId];
  2688. delete data.operationId;
  2689. handler[data.status](data);
  2690. }
  2691. };
  2692. return exports;
  2693. }();
  2694. var sdpHookHandler = function(sdp, sdpHook){
  2695. if (sdpHook != undefined && typeof sdpHook == 'function') {
  2696. var sdpObject = {sdpString: sdp};
  2697. var newSdp = sdpHook(sdpObject);
  2698. if (newSdp != null && newSdp != "") {
  2699. return newSdp;
  2700. }
  2701. return sdp;
  2702. }
  2703. return sdp;
  2704. }
  2705. /**
  2706. * Get session logger
  2707. *
  2708. * @returns {Object} Logger
  2709. * @memberof Session
  2710. */
  2711. var getLogger = function () {
  2712. return sessionLogger;
  2713. };
  2714. const collectLocalMediaInfo = function (mediaProvider, display) {
  2715. // Get devices available
  2716. let videoCams = [];
  2717. let mics = [];
  2718. if (mediaProvider.videoCams) {
  2719. mediaProvider.videoCams.forEach((device) => {
  2720. videoCams.push({
  2721. id: device.id,
  2722. label: encodeURI(device.label),
  2723. type: device.type
  2724. });
  2725. });
  2726. }
  2727. if (mediaProvider.mics) {
  2728. mediaProvider.mics.forEach((device) => {
  2729. mics.push({
  2730. id: device.id,
  2731. label: encodeURI(device.label),
  2732. type: device.type
  2733. });
  2734. });
  2735. }
  2736. if (videoCams.length) {
  2737. logger.info(LOG_PREFIX, "Video inputs available: " + JSON.stringify(videoCams));
  2738. }
  2739. if (mics.length) {
  2740. logger.info(LOG_PREFIX, "Audio inputs available: " + JSON.stringify(mics));
  2741. }
  2742. // Get track labels to identify publishing device
  2743. let audioTracks = [];
  2744. let videoTracks = [];
  2745. let localVideo;
  2746. if (mediaProvider.getCacheInstance) {
  2747. localVideo = mediaProvider.getCacheInstance(display);
  2748. }
  2749. if (!localVideo && mediaProvider.getVideoElement) {
  2750. localVideo = mediaProvider.getVideoElement(display);
  2751. }
  2752. // PR #19 from GitHub to prevent Sentry issue #WCS-4239
  2753. if (localVideo && localVideo.srcObject) {
  2754. localVideo.srcObject.getAudioTracks().forEach((track) => {
  2755. let device = track.label;
  2756. if (device === "MediaStreamAudioDestinationNode" && mediaProvider.getAudioSourceDevice) {
  2757. device = mediaProvider.getAudioSourceDevice();
  2758. }
  2759. audioTracks.push({
  2760. trackId: track.id,
  2761. device: encodeURI(device)
  2762. });
  2763. });
  2764. localVideo.srcObject.getVideoTracks().forEach((track) => {
  2765. videoTracks.push({
  2766. trackId: track.id,
  2767. device: encodeURI(track.label)
  2768. });
  2769. });
  2770. }
  2771. if (videoTracks.length) {
  2772. logger.info(LOG_PREFIX, "Video tracks captured: " + JSON.stringify(videoTracks));
  2773. }
  2774. if (audioTracks.length) {
  2775. logger.info(LOG_PREFIX, "Audio tracks captured: " + JSON.stringify(audioTracks));
  2776. }
  2777. return {
  2778. devices: {
  2779. video: videoCams,
  2780. audio: mics
  2781. },
  2782. tracks: {
  2783. video: videoTracks,
  2784. audio: audioTracks
  2785. }
  2786. };
  2787. }
  2788. //export Session
  2789. session.id = id;
  2790. session.status = status;
  2791. session.getServerUrl = getServerUrl;
  2792. session.createStream = createStream;
  2793. session.createCall = createCall;
  2794. session.getStream = getStream;
  2795. session.getStreams = getStreams;
  2796. session.sendData = restAppCommunicator.sendData;
  2797. session.disconnect = disconnect;
  2798. session.submitBugReport = submitBugReport;
  2799. session.startDebug = startDebug;
  2800. session.stopDebug = stopDebug;
  2801. session.on = on;
  2802. session.getLogger = getLogger;
  2803. //save interface to global map
  2804. sessions[id_] = session;
  2805. return session;
  2806. };
  2807. var isUsingTemasys = function () {
  2808. return isUsingTemasysPlugin;
  2809. };
  2810. module.exports = {
  2811. init: init,
  2812. isUsingTemasys: isUsingTemasys,
  2813. getMediaProviders: getMediaProviders,
  2814. getMediaDevices: getMediaDevices,
  2815. getMediaAccess: getMediaAccess,
  2816. releaseLocalMedia: releaseLocalMedia,
  2817. getSessions: getSessions,
  2818. getSession: getSession,
  2819. createSession: createSession,
  2820. playFirstSound: playFirstSound,
  2821. playFirstVideo: playFirstVideo,
  2822. getLogger: getLogger,
  2823. constants: constants,
  2824. /**
  2825. * The Screensharing whitelist is no longer needed to share your screen or windows starting Firefox 52
  2826. * https://wiki.mozilla.org/Screensharing
  2827. */
  2828. firefoxScreenSharingExtensionInstalled: true,
  2829. Browser: util.Browser
  2830. };