twilio-video
Version:
Twilio Video JavaScript Library
285 lines • 12.7 kB
JavaScript
'use strict';
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var __read = (this && this.__read) || function (o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
};
var __spreadArray = (this && this.__spreadArray) || function (to, from) {
for (var i = 0, il = from.length, j = to.length; i < il; i++, j++)
to[j] = from[i];
return to;
};
var _a = require('../../util/constants'), E = _a.typeErrors, trackPriority = _a.trackPriority;
var isIOS = require('../../util/browserdetection').isIOS;
var documentVisibilityMonitor = require('../../util/documentvisibilitymonitor.js');
function mixinRemoteMediaTrack(AudioOrVideoTrack) {
/**
* A {@link RemoteMediaTrack} represents a {@link MediaTrack} published to a
* {@link Room} by a {@link RemoteParticipant}.
* @property {boolean} isEnabled - Whether the {@link RemoteMediaTrack} is enabled
* @property {boolean} isSwitchedOff - Whether the {@link RemoteMediaTrack} is switched off
* @property {Track.SID} sid - The SID assigned to the {@link RemoteMediaTrack}
* @property {?Track.Priority} priority - The subscribe priority of the {@link RemoteMediaTrack}
* @emits RemoteMediaTrack#disabled
* @emits RemoteMediaTrack#enabled
* @emits RemoteMediaTrack#switchedOff
* @emits RemoteMediaTrack#switchedOn
*/
return /** @class */ (function (_super) {
__extends(RemoteMediaTrack, _super);
/**
* Construct a {@link RemoteMediaTrack}.
* @param {Track.SID} sid
* @param {MediaTrackReceiver} mediaTrackReceiver
* @param {boolean} isEnabled
@param {boolean} isSwitchedOff
* @param {function(?Track.Priority): void} setPriority - Set or clear the subscribe
* {@link Track.Priority} of the {@link RemoteMediaTrack}
* @param {function(ClientRenderHint): void} setRenderHint - Set render hints.
* @param {{log: Log, name: ?string}} options
*/
function RemoteMediaTrack(sid, mediaTrackReceiver, isEnabled, isSwitchedOff, setPriority, setRenderHint, options) {
var _this = this;
options = Object.assign({
// NOTE(mpatwardhan): WebKit bug: 212780 sometimes causes the audio/video elements to stay paused when safari
// regains foreground. To workaround it, when safari gains foreground - we will play any elements that were
// playing before safari lost foreground.
workaroundWebKitBug212780: isIOS()
&& typeof document === 'object'
&& typeof document.addEventListener === 'function'
&& typeof document.visibilityState === 'string'
}, options);
_this = _super.call(this, mediaTrackReceiver, options) || this;
Object.defineProperties(_this, {
_isEnabled: {
value: isEnabled,
writable: true
},
_isSwitchedOff: {
value: isSwitchedOff,
writable: true
},
_priority: {
value: null,
writable: true
},
_setPriority: {
value: setPriority
},
_setRenderHint: {
value: function (renderHint) {
_this._log.debug('updating render hint:', renderHint);
setRenderHint(renderHint);
}
},
isEnabled: {
enumerable: true,
get: function () {
return this._isEnabled;
}
},
isSwitchedOff: {
enumerable: true,
get: function () {
return this._isSwitchedOff;
}
},
priority: {
enumerable: true,
get: function () {
return this._priority;
}
},
sid: {
enumerable: true,
value: sid
},
_workaroundWebKitBug212780: {
value: options.workaroundWebKitBug212780
},
_workaroundWebKitBug212780Cleanup: {
value: null,
writable: true
}
});
return _this;
}
/**
* Update the subscribe {@link Track.Priority} of the {@link RemoteMediaTrack}.
* @param {?Track.Priority} priority - the new subscribe {@link Track.Priority};
* If <code>null</code>, then the subscribe {@link Track.Priority} is cleared, which
* means the {@link Track.Priority} set by the publisher is now the effective priority.
* @returns {this}
* @throws {RangeError}
*/
RemoteMediaTrack.prototype.setPriority = function (priority) {
var priorityValues = __spreadArray([null], __read(Object.values(trackPriority)));
if (!priorityValues.includes(priority)) {
// eslint-disable-next-line new-cap
throw E.INVALID_VALUE('priority', priorityValues);
}
if (this._priority !== priority) {
this._priority = priority;
this._setPriority(priority);
}
return this;
};
/**
* @private
* @param {boolean} isEnabled
*/
RemoteMediaTrack.prototype._setEnabled = function (isEnabled) {
if (this._isEnabled !== isEnabled) {
this._isEnabled = isEnabled;
this.emit(this._isEnabled ? 'enabled' : 'disabled', this);
}
};
/**
* @private
* @param {boolean} isSwitchedOff
*/
RemoteMediaTrack.prototype._setSwitchedOff = function (isSwitchedOff) {
if (this._isSwitchedOff !== isSwitchedOff) {
this._isSwitchedOff = isSwitchedOff;
this.emit(isSwitchedOff ? 'switchedOff' : 'switchedOn', this);
}
};
RemoteMediaTrack.prototype.attach = function (el) {
var result = _super.prototype.attach.call(this, el);
if (this.mediaStreamTrack.enabled !== true) {
// NOTE(mpatwardhan): we disable mediaStreamTrack when there
// are no attachments to it (see notes below). Now that there
// are attachments re-enable the track.
this.mediaStreamTrack.enabled = true;
if (this.processedTrack) {
this.processedTrack.enabled = true;
}
// NOTE(csantos): since remote tracks disables/enables the mediaStreamTrack,
// captureFrames stops along with it. We need to start it again after re-enabling.
// See attach/detach methods in this class and in VideoTrack class.
if (this.processor) {
this._captureFrames();
}
}
if (this._workaroundWebKitBug212780) {
this._workaroundWebKitBug212780Cleanup = this._workaroundWebKitBug212780Cleanup
|| playIfPausedWhileInBackground(this);
}
return result;
};
RemoteMediaTrack.prototype.detach = function (el) {
var result = _super.prototype.detach.call(this, el);
if (this._attachments.size === 0) {
// NOTE(mpatwardhan): chrome continues playing webrtc audio
// track even after audio element is removed from the DOM.
// https://bugs.chromium.org/p/chromium/issues/detail?id=749928
// to workaround: here disable the track when
// there are no elements attached to it.
this.mediaStreamTrack.enabled = false;
if (this.processedTrack) {
this.processedTrack.enabled = false;
}
if (this._workaroundWebKitBug212780Cleanup) {
// unhook visibility change
this._workaroundWebKitBug212780Cleanup();
this._workaroundWebKitBug212780Cleanup = null;
}
}
return result;
};
return RemoteMediaTrack;
}(AudioOrVideoTrack));
}
function playIfPausedWhileInBackground(remoteMediaTrack) {
var log = remoteMediaTrack._log, kind = remoteMediaTrack.kind;
function onVisibilityChanged(isVisible) {
if (!isVisible) {
return;
}
remoteMediaTrack._attachments.forEach(function (el) {
var shim = remoteMediaTrack._elShims.get(el);
var isInadvertentlyPaused = el.paused && shim && !shim.pausedIntentionally();
if (isInadvertentlyPaused) {
log.info("Playing inadvertently paused <" + kind + "> element");
log.debug('Element:', el);
log.debug('RemoteMediaTrack:', remoteMediaTrack);
el.play().then(function () {
log.info("Successfully played inadvertently paused <" + kind + "> element");
log.debug('Element:', el);
log.debug('RemoteMediaTrack:', remoteMediaTrack);
}).catch(function (err) {
log.warn("Error while playing inadvertently paused <" + kind + "> element:", { err: err, el: el, remoteMediaTrack: remoteMediaTrack });
});
}
});
}
// NOTE(mpatwardhan): listen for document visibility callback on phase 2.
// this ensures that any LocalMediaTrack's restart (which listen on phase 1) gets executed
// first. This order is important because we `play` tracks in the callback, and
// play can fail on safari if audio is not being captured.
documentVisibilityMonitor.onVisibilityChange(2, onVisibilityChanged);
return function () {
documentVisibilityMonitor.offVisibilityChange(2, onVisibilityChanged);
};
}
/**
* A {@link RemoteMediaTrack} was disabled.
* @param {RemoteMediaTrack} track - The {@link RemoteMediaTrack} that was
* disabled
* @event RemoteMediaTrack#disabled
*/
/**
* A {@link RemoteMediaTrack} was enabled.
* @param {RemoteMediaTrack} track - The {@link RemoteMediaTrack} that was
* enabled
* @event RemoteMediaTrack#enabled
*/
/**
* A {@link RemoteMediaTrack} was switched off.
* @param {RemoteMediaTrack} track - The {@link RemoteMediaTrack} that was
* switched off
* @event RemoteMediaTrack#switchedOff
*/
/**
* A {@link RemoteMediaTrack} was switched on.
* @param {RemoteMediaTrack} track - The {@link RemoteMediaTrack} that was
* switched on
* @event RemoteMediaTrack#switchedOn
*/
/**
* A {@link ClientRenderHint} object specifies track dimensions and /enabled disable state.
* This state will be used by the server(SFU) to determine bandwidth allocation for the track,
* and turn it on or off as needed.
* @typedef {object} ClientRenderHint
* @property {boolean} [enabled] - track is enabled or disabled. defaults to disabled.
* @property {VideoTrack.Dimensions} [renderDimensions] - Optional parameter to specify the desired
* render dimensions of {@link RemoteVideoTrack}s. This property must be specified if enabled=true
*/
module.exports = mixinRemoteMediaTrack;
//# sourceMappingURL=remotemediatrack.js.map