openvidu-browser: IExplorer support

pull/255/head
pabloFuente 2019-05-10 10:36:10 +02:00
parent 89db47dd37
commit 8f25b937e2
7 changed files with 258 additions and 94 deletions

View File

@ -96,8 +96,8 @@ export class Connection {
*/ */
sendIceCandidate(candidate: RTCIceCandidate): void { sendIceCandidate(candidate: RTCIceCandidate): void {
console.debug((!!this.stream.outboundStreamOpts ? 'Local' : 'Remote'), 'candidate for', console.debug((!!this.stream.outboundStreamOpts ? 'Local' : 'Remote') + 'candidate for' +
this.connectionId, JSON.stringify(candidate)); this.connectionId, candidate);
this.session.openvidu.sendRequest('onIceCandidate', { this.session.openvidu.sendRequest('onIceCandidate', {
endpointName: this.connectionId, endpointName: this.connectionId,

View File

@ -32,6 +32,9 @@ import * as screenSharing from '../OpenViduInternal/ScreenSharing/Screen-Capturi
import RpcBuilder = require('../OpenViduInternal/KurentoUtils/kurento-jsonrpc'); import RpcBuilder = require('../OpenViduInternal/KurentoUtils/kurento-jsonrpc');
import platform = require('platform'); import platform = require('platform');
platform['isIonicIos'] = (platform.product === 'iPhone' || platform.product === 'iPad') && platform.ua!!.indexOf('Safari') === -1; platform['isIonicIos'] = (platform.product === 'iPhone' || platform.product === 'iPad') && platform.ua!!.indexOf('Safari') === -1;
platform['isInternetExplorer'] = platform.name === 'IE' && platform.version !== undefined && parseInt(platform.version) >= 11;
platform['isReactNative'] = navigator.product === 'ReactNative';
declare const AdapterJS: any;
/** /**
* @hidden * @hidden
@ -97,6 +100,10 @@ export class OpenVidu {
console.info("'OpenVidu' initialized"); console.info("'OpenVidu' initialized");
console.info("openvidu-browser version: " + this.libraryVersion); console.info("openvidu-browser version: " + this.libraryVersion);
if (platform['isInternetExplorer']) {
this.importIEAdapterJS();
}
if (platform.os!!.family === 'iOS' || platform.os!!.family === 'Android') { if (platform.os!!.family === 'iOS' || platform.os!!.family === 'Android') {
// Listen to orientationchange only on mobile devices // Listen to orientationchange only on mobile devices
(<any>window).addEventListener('orientationchange', () => { (<any>window).addEventListener('orientationchange', () => {
@ -216,12 +223,12 @@ export class OpenVidu {
properties = { properties = {
audioSource: (typeof properties.audioSource !== 'undefined') ? properties.audioSource : undefined, audioSource: (typeof properties.audioSource !== 'undefined') ? properties.audioSource : undefined,
frameRate: (properties.videoSource instanceof MediaStreamTrack) ? undefined : ((typeof properties.frameRate !== 'undefined') ? properties.frameRate : undefined), frameRate: (typeof MediaStreamTrack !== 'undefined' && properties.videoSource instanceof MediaStreamTrack) ? undefined : ((typeof properties.frameRate !== 'undefined') ? properties.frameRate : undefined),
insertMode: (typeof properties.insertMode !== 'undefined') ? ((typeof properties.insertMode === 'string') ? VideoInsertMode[properties.insertMode] : properties.insertMode) : VideoInsertMode.APPEND, insertMode: (typeof properties.insertMode !== 'undefined') ? ((typeof properties.insertMode === 'string') ? VideoInsertMode[properties.insertMode] : properties.insertMode) : VideoInsertMode.APPEND,
mirror: (typeof properties.mirror !== 'undefined') ? properties.mirror : true, mirror: (typeof properties.mirror !== 'undefined') ? properties.mirror : true,
publishAudio: (typeof properties.publishAudio !== 'undefined') ? properties.publishAudio : true, publishAudio: (typeof properties.publishAudio !== 'undefined') ? properties.publishAudio : true,
publishVideo: (typeof properties.publishVideo !== 'undefined') ? properties.publishVideo : true, publishVideo: (typeof properties.publishVideo !== 'undefined') ? properties.publishVideo : true,
resolution: (properties.videoSource instanceof MediaStreamTrack) ? undefined : ((typeof properties.resolution !== 'undefined') ? properties.resolution : '640x480'), resolution: (typeof MediaStreamTrack !== 'undefined' && properties.videoSource instanceof MediaStreamTrack) ? undefined : ((typeof properties.resolution !== 'undefined') ? properties.resolution : '640x480'),
videoSource: (typeof properties.videoSource !== 'undefined') ? properties.videoSource : undefined, videoSource: (typeof properties.videoSource !== 'undefined') ? properties.videoSource : undefined,
filter: properties.filter filter: properties.filter
}; };
@ -325,7 +332,8 @@ export class OpenVidu {
(browser !== 'Chrome') && (browser !== 'Chrome Mobile') && (browser !== 'Chrome') && (browser !== 'Chrome Mobile') &&
(browser !== 'Firefox') && (browser !== 'Firefox Mobile') && (browser !== 'Firefox') && (browser !== 'Firefox Mobile') &&
(browser !== 'Opera') && (browser !== 'Opera Mobile') && (browser !== 'Opera') && (browser !== 'Opera Mobile') &&
(browser !== 'Android Browser') (browser !== 'Android Browser') &&
(browser !== 'IE' || platform.version !== undefined && parseInt(platform.version) < 11)
) { ) {
return 0; return 0;
} else { } else {
@ -431,6 +439,8 @@ export class OpenVidu {
return new Promise<MediaStream>((resolve, reject) => { return new Promise<MediaStream>((resolve, reject) => {
this.generateMediaConstraints(options) this.generateMediaConstraints(options)
.then(constraints => { .then(constraints => {
let userMediaFunc = () => {
navigator.mediaDevices.getUserMedia(constraints) navigator.mediaDevices.getUserMedia(constraints)
.then(mediaStream => { .then(mediaStream => {
resolve(mediaStream); resolve(mediaStream);
@ -445,6 +455,15 @@ export class OpenVidu {
} }
reject(new OpenViduError(errorName, errorMessage)); reject(new OpenViduError(errorName, errorMessage));
}); });
}
if (platform['isInternetExplorer']) {
AdapterJS.webRTCReady(isUsingPlugin => {
userMediaFunc();
});
} else {
userMediaFunc();
}
}) })
.catch((error: OpenViduError) => { .catch((error: OpenViduError) => {
reject(error); reject(error);
@ -742,4 +761,19 @@ export class OpenVidu {
} }
} }
private importIEAdapterJS(): void {
const moduleSpecifier = 'https://cdn.temasys.io/adapterjs/0.15.x/adapter.screenshare.min.js';
//Create a script tag
var script = document.createElement('script');
// Assign a URL to the script element
script.src = moduleSpecifier;
// Get the first script tag on the page (we'll insert our new one before it)
var ref = document.querySelector('script');
// Insert the new node before the reference node
if (ref && ref.parentNode) {
ref.parentNode.insertBefore(script, ref);
console.info("Detected IE Explorer " + platform.version + ". IEAdapter imported");
}
}
} }

View File

@ -30,6 +30,9 @@ import { VideoInsertMode } from '../OpenViduInternal/Enums/VideoInsertMode';
import platform = require('platform'); import platform = require('platform');
platform['isIonicIos'] = (platform.product === 'iPhone' || platform.product === 'iPad') && platform.ua!!.indexOf('Safari') === -1; platform['isIonicIos'] = (platform.product === 'iPhone' || platform.product === 'iPad') && platform.ua!!.indexOf('Safari') === -1;
platform['isInternetExplorer'] = platform.name === 'IE' && platform.version !== undefined && parseInt(platform.version) >= 11;
platform['isReactNative'] = navigator.product === 'ReactNative';
declare const AdapterJS: any, attachMediaStream;
/** /**
* Packs local media streams. Participants can publish it to a session. Initialized with [[OpenVidu.initPublisher]] method * Packs local media streams. Participants can publish it to a session. Initialized with [[OpenVidu.initPublisher]] method
@ -67,6 +70,10 @@ export class Publisher extends StreamManager {
* @hidden * @hidden
*/ */
screenShareResizeInterval: NodeJS.Timer; screenShareResizeInterval: NodeJS.Timer;
/**
* @hidden
*/
IEAdapter: any;
/** /**
* @hidden * @hidden
@ -284,12 +291,12 @@ export class Publisher extends StreamManager {
this.accessAllowed = true; this.accessAllowed = true;
this.accessDenied = false; this.accessDenied = false;
if (this.properties.audioSource instanceof MediaStreamTrack) { if (typeof MediaStreamTrack !== 'undefined' && this.properties.audioSource instanceof MediaStreamTrack) {
mediaStream.removeTrack(mediaStream.getAudioTracks()[0]); mediaStream.removeTrack(mediaStream.getAudioTracks()[0]);
mediaStream.addTrack((<MediaStreamTrack>this.properties.audioSource)); mediaStream.addTrack((<MediaStreamTrack>this.properties.audioSource));
} }
if (this.properties.videoSource instanceof MediaStreamTrack) { if (typeof MediaStreamTrack !== 'undefined' && this.properties.videoSource instanceof MediaStreamTrack) {
mediaStream.removeTrack(mediaStream.getVideoTracks()[0]); mediaStream.removeTrack(mediaStream.getVideoTracks()[0]);
mediaStream.addTrack((<MediaStreamTrack>this.properties.videoSource)); mediaStream.addTrack((<MediaStreamTrack>this.properties.videoSource));
} }
@ -310,18 +317,61 @@ export class Publisher extends StreamManager {
this.videoReference.setAttribute('playsinline', 'true'); this.videoReference.setAttribute('playsinline', 'true');
} }
this.videoReference.srcObject = mediaStream; if (!!this.firstVideoElement) {
let video = this.createVideoElement(this.firstVideoElement.targetElement, <VideoInsertMode>this.properties.insertMode);
if (platform['isInternetExplorer']) {
this.videoReference = video;
}
}
this.stream.setMediaStream(mediaStream); this.stream.setMediaStream(mediaStream);
if (platform['isInternetExplorer']) {
AdapterJS.webRTCReady(isUsingPlugin => {
this.videoReference = this.customAttachMediaStreamIE(this.videoReference, mediaStream);
if (this.stream.isSendVideo()) {
if (!this.stream.isSendScreen()) {
/*this.videoReference.onloadedmetadata = () => {
this.stream.videoDimensions = {
width: this.videoReference.videoWidth,
height: this.videoReference.videoHeight
};
// TODO: if screen-share, set this.screenShareResizeInterval
console.warn(this.stream.videoDimensions);
this.stream.isLocalStreamReadyToPublish = true;
this.stream.ee.emitEvent('stream-ready-to-publish', []);
}*/
this.stream.videoDimensions = {
width: this.videoReference.videoWidth,
height: this.videoReference.videoHeight
};
// TODO: if screen-share, set this.screenShareResizeInterval
console.warn(this.stream.videoDimensions);
this.stream.isLocalStreamReadyToPublish = true;
this.stream.ee.emitEvent('stream-ready-to-publish', []);
this.videoReference.onplaying = () => {
console.warn("PLAYINNNGNGNGNGNGNG!!!");
}
}
}
});
} else {
this.videoReference.srcObject = mediaStream;
}
if (!this.stream.displayMyRemote()) { if (!this.stream.displayMyRemote()) {
// When we are subscribed to our remote we don't still set the MediaStream object in the video elements to // When we are subscribed to our remote we don't still set the MediaStream object in the video elements to
// avoid early 'streamPlaying' event // avoid early 'streamPlaying' event
this.stream.updateMediaStreamInVideos(); this.stream.updateMediaStreamInVideos();
} }
if (!!this.firstVideoElement) {
this.createVideoElement(this.firstVideoElement.targetElement, <VideoInsertMode>this.properties.insertMode);
}
delete this.firstVideoElement; delete this.firstVideoElement;
if (this.stream.isSendVideo()) { if (this.stream.isSendVideo()) {
@ -357,8 +407,8 @@ export class Publisher extends StreamManager {
videoDimensionsSet(); videoDimensionsSet();
} }
}; };
} else { } else if (platform.name !== 'IE') {
// Rest of platforms // Rest of platforms except IE
// With no screen share, video dimension can be set directly from MediaStream (getSettings) // With no screen share, video dimension can be set directly from MediaStream (getSettings)
// Orientation must be checked for mobile devices (width and height are reversed) // Orientation must be checked for mobile devices (width and height are reversed)
const { width, height } = mediaStream.getVideoTracks()[0].getSettings(); const { width, height } = mediaStream.getVideoTracks()[0].getSettings();
@ -480,14 +530,14 @@ export class Publisher extends StreamManager {
// - video track is given and no audio // - video track is given and no audio
// - audio track is given and no video // - audio track is given and no video
// - both video and audio tracks are given // - both video and audio tracks are given
if ((this.properties.videoSource instanceof MediaStreamTrack && !this.properties.audioSource) if ((typeof MediaStreamTrack !== 'undefined' && this.properties.videoSource instanceof MediaStreamTrack && !this.properties.audioSource)
|| (this.properties.audioSource instanceof MediaStreamTrack && !this.properties.videoSource) || (typeof MediaStreamTrack !== 'undefined' && this.properties.audioSource instanceof MediaStreamTrack && !this.properties.videoSource)
|| (this.properties.videoSource instanceof MediaStreamTrack && this.properties.audioSource instanceof MediaStreamTrack)) { || (typeof MediaStreamTrack !== 'undefined' && this.properties.videoSource instanceof MediaStreamTrack && this.properties.audioSource instanceof MediaStreamTrack)) {
const mediaStream = new MediaStream(); const mediaStream = new MediaStream();
if (this.properties.videoSource instanceof MediaStreamTrack) { if (typeof MediaStreamTrack !== 'undefined' && this.properties.videoSource instanceof MediaStreamTrack) {
mediaStream.addTrack((<MediaStreamTrack>this.properties.videoSource)); mediaStream.addTrack((<MediaStreamTrack>this.properties.videoSource));
} }
if (this.properties.audioSource instanceof MediaStreamTrack) { if (typeof MediaStreamTrack !== 'undefined' && this.properties.audioSource instanceof MediaStreamTrack) {
mediaStream.addTrack((<MediaStreamTrack>this.properties.audioSource)); mediaStream.addTrack((<MediaStreamTrack>this.properties.audioSource));
} }
// MediaStreamTracks are handled within callback - just call callback with new MediaStream() and let it handle the sources // MediaStreamTracks are handled within callback - just call callback with new MediaStream() and let it handle the sources
@ -521,11 +571,16 @@ export class Publisher extends StreamManager {
afterGetMedia(mediaStream, definedAudioConstraint); afterGetMedia(mediaStream, definedAudioConstraint);
}); });
} else { } else {
let userMediaFunc = () => {
navigator.mediaDevices.getUserMedia(constraintsAux) navigator.mediaDevices.getUserMedia(constraintsAux)
.then(mediaStream => { .then(mediaStream => {
afterGetMedia(mediaStream, definedAudioConstraint); afterGetMedia(mediaStream, definedAudioConstraint);
}) })
.catch(error => { .catch(error => {
console.error(error);
this.clearPermissionDialogTimer(startTime, timeForDialogEvent); this.clearPermissionDialogTimer(startTime, timeForDialogEvent);
if (error.name === 'Error') { if (error.name === 'Error') {
// Safari OverConstrainedError has as name property 'Error' instead of 'OverConstrainedError' // Safari OverConstrainedError has as name property 'Error' instead of 'OverConstrainedError'
@ -599,6 +654,16 @@ export class Publisher extends StreamManager {
} }
}); });
} }
if (platform['isInternetExplorer']) {
AdapterJS.webRTCReady(isUsingPlugin => {
userMediaFunc();
})
} else {
userMediaFunc();
}
}
} else { } else {
reject(new OpenViduError(OpenViduErrorName.NO_INPUT_SOURCE_SET, reject(new OpenViduError(OpenViduErrorName.NO_INPUT_SOURCE_SET,
"Properties 'audioSource' and 'videoSource' cannot be set to false or null at the same time when calling 'OpenVidu.initPublisher'")); "Properties 'audioSource' and 'videoSource' cannot be set to false or null at the same time when calling 'OpenVidu.initPublisher'"));

View File

@ -160,7 +160,7 @@ export class Session implements EventDispatcher {
reject(error); reject(error);
}); });
} else { } else {
reject(new OpenViduError(OpenViduErrorName.BROWSER_NOT_SUPPORTED, 'Browser ' + platform.name + ' for ' + platform.os!!.family + ' is not supported in OpenVidu')); reject(new OpenViduError(OpenViduErrorName.BROWSER_NOT_SUPPORTED, 'Browser ' + platform.name + ' (version ' + platform.version + ') for ' + platform.os!!.family + ' is not supported in OpenVidu'));
} }
}); });
} }
@ -882,10 +882,17 @@ export class Session implements EventDispatcher {
this.getConnection(msg.senderConnectionId, 'Connection not found for connectionId ' + msg.senderConnectionId + ' owning endpoint ' + msg.endpointName + '. Ice candidate will be ignored: ' + candidate) this.getConnection(msg.senderConnectionId, 'Connection not found for connectionId ' + msg.senderConnectionId + ' owning endpoint ' + msg.endpointName + '. Ice candidate will be ignored: ' + candidate)
.then(connection => { .then(connection => {
const stream = connection.stream; const stream = connection.stream;
if (platform['isInternetExplorer']) {
(<any>stream.getWebRtcPeer()).addIceCandidate(candidate, () => {}, error => {
console.error('Error adding candidate for ' + stream.streamId
+ ' stream of endpoint ' + msg.endpointName + ': ' + error);
});
} else {
stream.getWebRtcPeer().addIceCandidate(candidate).catch(error => { stream.getWebRtcPeer().addIceCandidate(candidate).catch(error => {
console.error('Error adding candidate for ' + stream.streamId console.error('Error adding candidate for ' + stream.streamId
+ ' stream of endpoint ' + msg.endpointName + ': ' + error); + ' stream of endpoint ' + msg.endpointName + ': ' + error);
}); });
}
}) })
.catch(openViduError => { .catch(openViduError => {
console.error(openViduError); console.error(openViduError);
@ -1023,10 +1030,10 @@ export class Session implements EventDispatcher {
const joinParams = { const joinParams = {
token: (!!token) ? token : '', token: (!!token) ? token : '',
session: this.sessionId, session: this.sessionId,
platform: platform.description, platform: !!platform.description ? platform.description : 'unknown',
metadata: !!this.options.metadata ? this.options.metadata : '', metadata: !!this.options.metadata ? this.options.metadata : '',
secret: this.openvidu.getSecret(), secret: this.openvidu.getSecret(),
recorder: this.openvidu.getRecorder(), recorder: this.openvidu.getRecorder()
}; };
this.openvidu.sendRequest('joinRoom', joinParams, (error, response) => { this.openvidu.sendRequest('joinRoom', joinParams, (error, response) => {

View File

@ -35,6 +35,9 @@ import EventEmitter = require('wolfy87-eventemitter');
import hark = require('hark'); import hark = require('hark');
import platform = require('platform'); import platform = require('platform');
platform['isIonicIos'] = (platform.product === 'iPhone' || platform.product === 'iPad') && platform.ua!!.indexOf('Safari') === -1; platform['isIonicIos'] = (platform.product === 'iPhone' || platform.product === 'iPad') && platform.ua!!.indexOf('Safari') === -1;
platform['isInternetExplorer'] = platform.name === 'IE' && platform.version !== undefined && parseInt(platform.version) >= 11;
platform['isReactNative'] = navigator.product === 'ReactNative';
declare const AdapterJS: any;
/** /**
@ -221,7 +224,7 @@ export class Stream implements EventDispatcher {
if (this.hasVideo) { if (this.hasVideo) {
this.videoActive = !!this.outboundStreamOpts.publisherProperties.publishVideo; this.videoActive = !!this.outboundStreamOpts.publisherProperties.publishVideo;
this.frameRate = this.outboundStreamOpts.publisherProperties.frameRate; this.frameRate = this.outboundStreamOpts.publisherProperties.frameRate;
if (this.outboundStreamOpts.publisherProperties.videoSource instanceof MediaStreamTrack) { if (typeof MediaStreamTrack !== 'undefined' && this.outboundStreamOpts.publisherProperties.videoSource instanceof MediaStreamTrack) {
this.typeOfVideo = 'CUSTOM'; this.typeOfVideo = 'CUSTOM';
} else { } else {
this.typeOfVideo = this.isSendScreen() ? 'SCREEN' : 'CAMERA'; this.typeOfVideo = this.isSendScreen() ? 'SCREEN' : 'CAMERA';
@ -455,7 +458,7 @@ export class Stream implements EventDispatcher {
disposeWebRtcPeer(): void { disposeWebRtcPeer(): void {
if (this.webRtcPeer) { if (this.webRtcPeer) {
const isSenderAndCustomTrack: boolean = !!this.outboundStreamOpts && const isSenderAndCustomTrack: boolean = !!this.outboundStreamOpts &&
this.outboundStreamOpts.publisherProperties.videoSource instanceof MediaStreamTrack; typeof MediaStreamTrack !== 'undefined' && this.outboundStreamOpts.publisherProperties.videoSource instanceof MediaStreamTrack;
this.webRtcPeer.dispose(isSenderAndCustomTrack); this.webRtcPeer.dispose(isSenderAndCustomTrack);
} }
if (this.speechEvent) { if (this.speechEvent) {
@ -693,7 +696,7 @@ export class Stream implements EventDispatcher {
let typeOfVideo = ''; let typeOfVideo = '';
if (this.isSendVideo()) { if (this.isSendVideo()) {
typeOfVideo = this.outboundStreamOpts.publisherProperties.videoSource instanceof MediaStreamTrack ? 'CUSTOM' : (this.isSendScreen() ? 'SCREEN' : 'CAMERA'); typeOfVideo = (typeof MediaStreamTrack !== 'undefined' && this.outboundStreamOpts.publisherProperties.videoSource instanceof MediaStreamTrack) ? 'CUSTOM' : (this.isSendScreen() ? 'SCREEN' : 'CAMERA');
} }
this.session.openvidu.sendRequest('publishVideo', { this.session.openvidu.sendRequest('publishVideo', {
@ -795,6 +798,7 @@ export class Stream implements EventDispatcher {
}); });
}; };
const initWebRtcPeer = () => {
this.webRtcPeer = new WebRtcPeerRecvonly(options); this.webRtcPeer = new WebRtcPeerRecvonly(options);
this.webRtcPeer.generateOffer() this.webRtcPeer.generateOffer()
.then(offer => { .then(offer => {
@ -803,12 +807,21 @@ export class Stream implements EventDispatcher {
.catch(error => { .catch(error => {
reject(new Error('(subscribe) SDP offer error: ' + JSON.stringify(error))); reject(new Error('(subscribe) SDP offer error: ' + JSON.stringify(error)));
}); });
};
if (platform['isInternetExplorer']) {
AdapterJS.webRTCReady(isUsingPlugin => {
initWebRtcPeer();
});
} else {
initWebRtcPeer();
}
}); });
} }
private remotePeerSuccessfullyEstablished(): void { private remotePeerSuccessfullyEstablished(): void {
if (platform['isIonicIos']) { if (platform['isIonicIos'] || platform['isInternetExplorer']) {
// iOS Ionic. LIMITATION: must use deprecated WebRTC API // iOS Ionic or IExplorer. LIMITATION: must use deprecated WebRTC API
const pc1: any = this.webRtcPeer.pc; const pc1: any = this.webRtcPeer.pc;
this.mediaStream = pc1.getRemoteStreams()[0]; this.mediaStream = pc1.getRemoteStreams()[0];
} else { } else {
@ -836,7 +849,7 @@ export class Stream implements EventDispatcher {
} }
} }
this.ee.emitEvent('mediastream-updated', []); this.updateMediaStreamInVideos();
if (!this.displayMyRemote() && !!this.mediaStream.getAudioTracks()[0] && this.session.speakingEventsEnabled) { if (!this.displayMyRemote() && !!this.mediaStream.getAudioTracks()[0] && this.session.speakingEventsEnabled) {
this.enableSpeakingEvents(); this.enableSpeakingEvents();
} }

View File

@ -26,6 +26,9 @@ import { VideoInsertMode } from '../OpenViduInternal/Enums/VideoInsertMode';
import EventEmitter = require('wolfy87-eventemitter'); import EventEmitter = require('wolfy87-eventemitter');
import platform = require('platform'); import platform = require('platform');
platform['isIonicIos'] = (platform.product === 'iPhone' || platform.product === 'iPad') && platform.ua!!.indexOf('Safari') === -1; platform['isIonicIos'] = (platform.product === 'iPhone' || platform.product === 'iPad') && platform.ua!!.indexOf('Safari') === -1;
platform['isInternetExplorer'] = platform.name === 'IE' && platform.version !== undefined && parseInt(platform.version) >= 11;
platform['isReactNative'] = navigator.product === 'ReactNative';
declare const attachMediaStream;
/** /**
* Interface in charge of displaying the media streams in the HTML DOM. This wraps any [[Publisher]] and [[Subscriber]] object. * Interface in charge of displaying the media streams in the HTML DOM. This wraps any [[Publisher]] and [[Subscriber]] object.
@ -240,6 +243,10 @@ export class StreamManager implements EventDispatcher {
video.srcObject = this.stream.getMediaStream(); video.srcObject = this.stream.getMediaStream();
} }
if (platform['isInternetExplorer'] && !!this.stream.getMediaStream()) {
video = this.customAttachMediaStreamIE(video, this.stream.getMediaStream());
}
// If the video element is already part of this StreamManager do nothing // If the video element is already part of this StreamManager do nothing
for (const v of this.videos) { for (const v of this.videos) {
if (v.video === video) { if (v.video === video) {
@ -293,7 +300,7 @@ export class StreamManager implements EventDispatcher {
throw new Error("The provided 'targetElement' couldn't be resolved to any HTML element: " + targetElement); throw new Error("The provided 'targetElement' couldn't be resolved to any HTML element: " + targetElement);
} }
const video = document.createElement('video'); let video = document.createElement('video');
this.initializeVideoProperties(video); this.initializeVideoProperties(video);
let insMode = !!insertMode ? insertMode : VideoInsertMode.APPEND; let insMode = !!insertMode ? insertMode : VideoInsertMode.APPEND;
@ -319,6 +326,10 @@ export class StreamManager implements EventDispatcher {
break; break;
} }
if (platform['isInternetExplorer'] && !!this.stream.getMediaStream()) {
video = this.customAttachMediaStreamIE(video, this.stream.getMediaStream());
}
const v: StreamManagerVideo = { const v: StreamManagerVideo = {
targetElement: targEl, targetElement: targEl,
video, video,
@ -327,9 +338,12 @@ export class StreamManager implements EventDispatcher {
}; };
this.pushNewStreamManagerVideo(v); this.pushNewStreamManagerVideo(v);
let launchVideoCreatedEvent = !platform['isInternetExplorer'];
if (launchVideoCreatedEvent) {
// For IE the event is called in this.customAttachMediaStreamIE
this.ee.emitEvent('videoElementCreated', [new VideoElementEvent(v.video, this, 'videoElementCreated')]); this.ee.emitEvent('videoElementCreated', [new VideoElementEvent(v.video, this, 'videoElementCreated')]);
}
this.lazyLaunchVideoElementCreatedEvent = !!this.firstVideoElement; this.lazyLaunchVideoElementCreatedEvent = !!this.firstVideoElement && launchVideoCreatedEvent;
return video; return video;
} }
@ -432,6 +446,9 @@ export class StreamManager implements EventDispatcher {
vParent!!.replaceChild(newVideo, streamManagerVideo.video); vParent!!.replaceChild(newVideo, streamManagerVideo.video);
streamManagerVideo.video = newVideo; streamManagerVideo.video = newVideo;
} }
if (platform['isInternetExplorer']) {
this.customAttachMediaStreamIE(streamManagerVideo.video, mediaStream);
}
}); });
} }
@ -462,4 +479,21 @@ export class StreamManager implements EventDispatcher {
video.style.webkitTransform = 'unset'; video.style.webkitTransform = 'unset';
} }
protected customAttachMediaStreamIE(video: HTMLVideoElement, mediaStream: MediaStream): HTMLVideoElement {
var simVideo = attachMediaStream(video, mediaStream);
// Replace HTMLVideoElemet (if exists) with new HTMLObjectElement returned by IE plugin
for (let i = 0; i < this.videos.length; i++) {
if (this.videos[i].video === video) {
this.videos[i].video = simVideo;
break;
}
}
// Always launch videoElementCreated event after IE plugin has inserted simulated video into DOM
this.ee.emitEvent('videoElementCreated', [new VideoElementEvent(simVideo, this, 'videoElementCreated')]);
this.addPlayEventToFirstVideo();
return simVideo;
}
} }

View File

@ -19,6 +19,8 @@ import freeice = require('freeice');
import uuid = require('uuid'); import uuid = require('uuid');
import platform = require('platform'); import platform = require('platform');
platform['isIonicIos'] = (platform.product === 'iPhone' || platform.product === 'iPad') && platform.ua!!.indexOf('Safari') === -1; platform['isIonicIos'] = (platform.product === 'iPhone' || platform.product === 'iPad') && platform.ua!!.indexOf('Safari') === -1;
platform['isInternetExplorer'] = platform.name === 'IE' && platform.version !== undefined && parseInt(platform.version) >= 11;
platform['isReactNative'] = navigator.product === 'ReactNative';
export interface WebRtcPeerConfiguration { export interface WebRtcPeerConfiguration {
mediaConstraints: { mediaConstraints: {
@ -66,9 +68,13 @@ export class WebRtcPeer {
this.pc.onsignalingstatechange = () => { this.pc.onsignalingstatechange = () => {
if (this.pc.signalingState === 'stable') { if (this.pc.signalingState === 'stable') {
while (this.iceCandidateList.length > 0) { while (this.iceCandidateList.length > 0) {
if (platform['isInternetExplorer']) {
(<any>this.pc).addIceCandidate(<RTCIceCandidate>this.iceCandidateList.shift(), () => {}, () => {});
} else {
this.pc.addIceCandidate(<RTCIceCandidate>this.iceCandidateList.shift()); this.pc.addIceCandidate(<RTCIceCandidate>this.iceCandidateList.shift());
} }
} }
}
}; };
this.start(); this.start();
@ -87,8 +93,8 @@ export class WebRtcPeer {
reject('The peer connection object is in "closed" state. This is most likely due to an invocation of the dispose method before accepting in the dialogue'); reject('The peer connection object is in "closed" state. This is most likely due to an invocation of the dispose method before accepting in the dialogue');
} }
if (!!this.configuration.mediaStream) { if (!!this.configuration.mediaStream) {
if (platform['isIonicIos']) { if (platform['isIonicIos'] || platform['isInternetExplorer']) {
// iOS Ionic. LIMITATION: must use deprecated WebRTC API // iOS Ionic and IExplorer. LIMITATION: must use deprecated WebRTC API
const pc2: any = this.pc; const pc2: any = this.pc;
pc2.addStream(this.configuration.mediaStream); pc2.addStream(this.configuration.mediaStream);
} else { } else {
@ -114,8 +120,8 @@ export class WebRtcPeer {
this.remoteCandidatesQueue = []; this.remoteCandidatesQueue = [];
this.localCandidatesQueue = []; this.localCandidatesQueue = [];
if (platform['isIonicIos']) { if (platform['isIonicIos'] || platform['isInternetExplorer']) {
// iOS Ionic. LIMITATION: must use deprecated WebRTC API // iOS Ionic or IExplorer. LIMITATION: must use deprecated WebRTC API
// Stop senders deprecated // Stop senders deprecated
const pc1: any = this.pc; const pc1: any = this.pc;
for (const sender of pc1.getLocalStreams()) { for (const sender of pc1.getLocalStreams()) {
@ -156,7 +162,7 @@ export class WebRtcPeer {
} }
/** /**
* 1) Function that creates an offer, sets it as local description and returns the offer param * Function that creates an offer, sets it as local description and returns the offer param
* to send to OpenVidu Server (will be the remote description of other peer) * to send to OpenVidu Server (will be the remote description of other peer)
*/ */
generateOffer(): Promise<string> { generateOffer(): Promise<string> {
@ -209,11 +215,43 @@ export class WebRtcPeer {
} }
}) })
.catch(error => reject(error)); .catch(error => reject(error));
} else if (platform['isInternetExplorer']) {
// IE Explorer cannot use Promise base API
let setLocalDescriptionOnSuccess = () => {
const localDescription = this.pc.localDescription;
if (!!localDescription) {
console.debug('Local description set', localDescription.sdp);
resolve(localDescription.sdp);
} else { } else {
reject('Local description is not defined');
}
}
let setLocalDescriptionOnError = error => {
reject(error);
}
let createOfferOnSuccess = offer => {
console.debug('Created SDP offer');
(<any>this.pc).setLocalDescription(offer, setLocalDescriptionOnSuccess, setLocalDescriptionOnError);
};
let createOfferOnError = error => {
reject(error);
};
// FIX: for IExplorer WebRTC peer connections must be negotiated to receive video and audio
constraints.offerToReceiveAudio = true;
constraints.offerToReceiveVideo = true;
(<any>this.pc).createOffer(createOfferOnSuccess, createOfferOnError, constraints);
} else {
// Rest of platforms
this.pc.createOffer(constraints).then(offer => { this.pc.createOffer(constraints).then(offer => {
console.debug('Created SDP offer'); console.debug('Created SDP offer');
return this.pc.setLocalDescription(offer); return this.pc.setLocalDescription(offer);
}).then(() => { })
.then(() => {
const localDescription = this.pc.localDescription; const localDescription = this.pc.localDescription;
if (!!localDescription) { if (!!localDescription) {
console.debug('Local description set', localDescription.sdp); console.debug('Local description set', localDescription.sdp);
@ -228,52 +266,15 @@ export class WebRtcPeer {
} }
/** /**
* 2) Function to invoke when a SDP offer is received. Sets it as remote description, * Function invoked when a SDP answer is received. Final step in SDP negotiation, the peer
* generates and answer and returns it to send it to OpenVidu Server
*/
processOffer(sdpOffer: string): Promise<ConstrainDOMString> {
return new Promise((resolve, reject) => {
const offer: RTCSessionDescriptionInit = {
type: 'offer',
sdp: sdpOffer
};
console.debug('SDP offer received, setting remote description');
if (this.pc.signalingState === 'closed') {
reject('PeerConnection is closed');
}
this.pc.setRemoteDescription(offer)
.then(() => {
return this.pc.createAnswer();
}).then(answer => {
console.debug('Created SDP answer');
return this.pc.setLocalDescription(answer);
}).then(() => {
const localDescription = this.pc.localDescription;
if (!!localDescription) {
console.debug('Local description set', localDescription.sdp);
resolve(<string>localDescription.sdp);
} else {
reject('Local description is not defined');
}
}).catch(error => reject(error));
});
}
/**
* 3) Function invoked when a SDP answer is received. Final step in SDP negotiation, the peer
* just needs to set the answer as its remote description * just needs to set the answer as its remote description
*/ */
processAnswer(sdpAnswer: string, needsTimeoutOnProcessAswer: boolean): Promise<string> { processAnswer(sdpAnswer: string, needsTimeoutOnProcessAswer: boolean): Promise<string> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const answer: RTCSessionDescriptionInit = { const answer: RTCSessionDescriptionInit = {
type: 'answer', type: 'answer',
sdp: sdpAnswer sdp: sdpAnswer
}; };
console.debug('SDP answer received, setting remote description'); console.debug('SDP answer received, setting remote description');
if (this.pc.signalingState === 'closed') { if (this.pc.signalingState === 'closed') {
@ -285,8 +286,14 @@ export class WebRtcPeer {
this.pc.setRemoteDescription(answer).then(() => resolve()).catch(error => reject(error)); this.pc.setRemoteDescription(answer).then(() => resolve()).catch(error => reject(error));
}, 250); }, 250);
} else { } else {
if (platform['isInternetExplorer']) {
// IE Explorer cannot use Promise base API
(<any>this.pc).setRemoteDescription(answer, resolve(), error => reject(error));
} else {
// Rest of platforms
this.pc.setRemoteDescription(answer).then(() => resolve()).catch(error => reject(error)); this.pc.setRemoteDescription(answer).then(() => resolve()).catch(error => reject(error));
} }
}
}); });
} }
@ -303,8 +310,12 @@ export class WebRtcPeer {
break; break;
case 'stable': case 'stable':
if (!!this.pc.remoteDescription) { if (!!this.pc.remoteDescription) {
if (platform['isInternetExplorer']) {
(<any>this.pc).addIceCandidate(iceCandidate, () => resolve(), error => reject(error));
} else {
this.pc.addIceCandidate(iceCandidate).then(() => resolve()).catch(error => reject(error)); this.pc.addIceCandidate(iceCandidate).then(() => resolve()).catch(error => reject(error));
} }
}
break; break;
default: default:
this.iceCandidateList.push(iceCandidate); this.iceCandidateList.push(iceCandidate);