diff --git a/openvidu-browser/src/OpenVidu/OpenVidu.ts b/openvidu-browser/src/OpenVidu/OpenVidu.ts index e7b247af..807871f1 100644 --- a/openvidu-browser/src/OpenVidu/OpenVidu.ts +++ b/openvidu-browser/src/OpenVidu/OpenVidu.ts @@ -107,11 +107,11 @@ export class OpenVidu { console.info("'OpenVidu' initialized"); console.info("openvidu-browser version: " + this.libraryVersion); - + if (platform['isInternetExplorer']) { - console.info("Detected IE Explorer " + platform.version); - this.importIEAdapterJS(); - this.setGlobalIEFunctions(); + console.info("Detected IE Explorer " + platform.version); + this.importIEAdapterJS(); + this.setGlobalIEFunctions(); } if (platform.os!!.family === 'iOS' || platform.os!!.family === 'Android') { @@ -378,7 +378,7 @@ export class OpenVidu { */ getDevices(): Promise { return new Promise((resolve, reject) => { - navigator.mediaDevices.enumerateDevices().then((deviceInfos) => { + navigator.mediaDevices.enumerateDevices().then(deviceInfos => { const devices: Device[] = []; deviceInfos.forEach(deviceInfo => { if (deviceInfo.kind === 'audioinput' || deviceInfo.kind === 'videoinput') { @@ -452,24 +452,24 @@ export class OpenVidu { let userMediaFunc = () => { navigator.mediaDevices.getUserMedia(constraints) - .then(mediaStream => { - resolve(mediaStream); - }) - .catch(error => { - let errorName: OpenViduErrorName; - const errorMessage = error.toString(); - if (!(options.videoSource === 'screen')) { - errorName = OpenViduErrorName.DEVICE_ACCESS_DENIED; - } else { - errorName = OpenViduErrorName.SCREEN_CAPTURE_DENIED; - } - reject(new OpenViduError(errorName, errorMessage)); - }); + .then(mediaStream => { + resolve(mediaStream); + }) + .catch(error => { + let errorName: OpenViduErrorName; + const errorMessage = error.toString(); + if (!(options.videoSource === 'screen')) { + errorName = OpenViduErrorName.DEVICE_ACCESS_DENIED; + } else { + errorName = OpenViduErrorName.SCREEN_CAPTURE_DENIED; + } + reject(new OpenViduError(errorName, errorMessage)); + }); } if (platform['isInternetExplorer']) { AdapterJS.webRTCReady(isUsingPlugin => { - userMediaFunc(); + userMediaFunc(); }); } else { userMediaFunc(); @@ -776,51 +776,51 @@ export class OpenVidu { var script = document.createElement('script'); script.src = moduleSpecifier; var ref = document.querySelector('script'); - + if (ref && ref.parentNode) { - ref.parentNode.insertBefore(script, ref); - console.info("IE AdapterJS imported"); + ref.parentNode.insertBefore(script, ref); + console.info("IE AdapterJS imported"); } } private setGlobalIEFunctions(): void { - // FIX: the IE plugin seems to require the handler functions to be globally accessible. Store the functions with unique streamId + // FIX: the IE plugin seems to require the handler functions to be globally accessible. Store the functions with unique streamId // Global handler for onloadedmetadata (window).IEOnLoadedMetadata = (simVideo: HTMLVideoElement, str: Stream) => { const videoDimensionsSet = () => { - str.videoDimensions = { - width: simVideo.videoWidth, - height: simVideo.videoHeight - }; - str.isLocalStreamReadyToPublish = true; - str.ee.emitEvent('stream-ready-to-publish', []); + str.videoDimensions = { + width: simVideo.videoWidth, + height: simVideo.videoHeight + }; + str.isLocalStreamReadyToPublish = true; + str.ee.emitEvent('stream-ready-to-publish', []); }; let interval; if (simVideo.videoWidth === 0) { - interval = setInterval(() => { - if (simVideo.videoWidth !== 0) { - clearInterval(interval); - videoDimensionsSet(); - } - }, 40); + interval = setInterval(() => { + if (simVideo.videoWidth !== 0) { + clearInterval(interval); + videoDimensionsSet(); + } + }, 40); } else { - videoDimensionsSet(); + videoDimensionsSet(); } }; // Global handler for oncanplay (window).IEOnCanPlay = (strManager: StreamManager) => { if (strManager.stream.isLocal()) { - if (!strManager.stream.displayMyRemote()) { - console.info("Your local 'Stream' with id [" + strManager.stream.streamId + '] video is now playing'); - strManager.ee.emitEvent('videoPlaying', [new VideoElementEvent(strManager.videos[0].video, strManager, 'videoPlaying')]); - } else { - console.info("Your own remote 'Stream' with id [" + strManager.stream.streamId + '] video is now playing'); - strManager.ee.emitEvent('remoteVideoPlaying', [new VideoElementEvent(strManager.videos[0].video, strManager, 'remoteVideoPlaying')]); - } - } else { - console.info("Remote 'Stream' with id [" + strManager.stream.streamId + '] video is now playing'); + if (!strManager.stream.displayMyRemote()) { + console.info("Your local 'Stream' with id [" + strManager.stream.streamId + '] video is now playing'); strManager.ee.emitEvent('videoPlaying', [new VideoElementEvent(strManager.videos[0].video, strManager, 'videoPlaying')]); + } else { + console.info("Your own remote 'Stream' with id [" + strManager.stream.streamId + '] video is now playing'); + strManager.ee.emitEvent('remoteVideoPlaying', [new VideoElementEvent(strManager.videos[0].video, strManager, 'remoteVideoPlaying')]); + } + } else { + console.info("Remote 'Stream' with id [" + strManager.stream.streamId + '] video is now playing'); + strManager.ee.emitEvent('videoPlaying', [new VideoElementEvent(strManager.videos[0].video, strManager, 'videoPlaying')]); } strManager.ee.emitEvent('streamPlaying', [new StreamManagerEvent(strManager, 'streamPlaying', undefined)]); }; diff --git a/openvidu-browser/src/OpenVidu/Publisher.ts b/openvidu-browser/src/OpenVidu/Publisher.ts index 8768d6f6..2f994803 100644 --- a/openvidu-browser/src/OpenVidu/Publisher.ts +++ b/openvidu-browser/src/OpenVidu/Publisher.ts @@ -325,10 +325,10 @@ export class Publisher extends StreamManager { if (platform['isInternetExplorer']) { // IE cannot have a video reference not inserted into DOM // Pick up the first video element of videos array - this.videoReference = this.videos[0].video; + this.videoReference = this.videos[0].video; if (!this.videoReference) { console.warn('IE requires the video element to be defined when initializing a Publisher. ' + - 'Be sure to initialize the publisher passing a pre-existing targetElement') + 'Be sure to initialize the publisher passing a pre-existing targetElement') } } } @@ -553,85 +553,85 @@ export class Publisher extends StreamManager { let userMediaFunc = () => { navigator.mediaDevices.getUserMedia(constraintsAux) - .then(mediaStream => { - afterGetMedia(mediaStream, definedAudioConstraint); - }) - .catch(error => { + .then(mediaStream => { + afterGetMedia(mediaStream, definedAudioConstraint); + }) + .catch(error => { - console.error(error); + console.error(error); - this.clearPermissionDialogTimer(startTime, timeForDialogEvent); - if (error.name === 'Error') { - // Safari OverConstrainedError has as name property 'Error' instead of 'OverConstrainedError' - error.name = error.constructor.name; - } - let errorName, errorMessage; - switch (error.name.toLowerCase()) { - case 'notfounderror': - navigator.mediaDevices.getUserMedia({ - audio: false, - video: constraints.video - }) - .then(mediaStream => { - mediaStream.getVideoTracks().forEach((track) => { - track.stop(); - }); - errorName = OpenViduErrorName.INPUT_AUDIO_DEVICE_NOT_FOUND; - errorMessage = error.toString(); - errorCallback(new OpenViduError(errorName, errorMessage)); - }).catch(e => { - errorName = OpenViduErrorName.INPUT_VIDEO_DEVICE_NOT_FOUND; - errorMessage = error.toString(); - errorCallback(new OpenViduError(errorName, errorMessage)); - }); - break; - case 'notallowederror': - errorName = this.stream.isSendScreen() ? OpenViduErrorName.SCREEN_CAPTURE_DENIED : OpenViduErrorName.DEVICE_ACCESS_DENIED; - errorMessage = error.toString(); - errorCallback(new OpenViduError(errorName, errorMessage)); - break; - case 'overconstrainederror': - navigator.mediaDevices.getUserMedia({ - audio: false, - video: constraints.video - }) - .then(mediaStream => { - mediaStream.getVideoTracks().forEach((track) => { - track.stop(); - }); - if (error.constraint.toLowerCase() === 'deviceid') { + this.clearPermissionDialogTimer(startTime, timeForDialogEvent); + if (error.name === 'Error') { + // Safari OverConstrainedError has as name property 'Error' instead of 'OverConstrainedError' + error.name = error.constructor.name; + } + let errorName, errorMessage; + switch (error.name.toLowerCase()) { + case 'notfounderror': + navigator.mediaDevices.getUserMedia({ + audio: false, + video: constraints.video + }) + .then(mediaStream => { + mediaStream.getVideoTracks().forEach((track) => { + track.stop(); + }); errorName = OpenViduErrorName.INPUT_AUDIO_DEVICE_NOT_FOUND; - errorMessage = "Audio input device with deviceId '" + ((constraints.audio).deviceId!!).exact + "' not found"; - } else { - errorName = OpenViduErrorName.PUBLISHER_PROPERTIES_ERROR; - errorMessage = "Audio input device doesn't support the value passed for constraint '" + error.constraint + "'"; - } - errorCallback(new OpenViduError(errorName, errorMessage)); - }).catch(e => { - if (error.constraint.toLowerCase() === 'deviceid') { + errorMessage = error.toString(); + errorCallback(new OpenViduError(errorName, errorMessage)); + }).catch(e => { errorName = OpenViduErrorName.INPUT_VIDEO_DEVICE_NOT_FOUND; - errorMessage = "Video input device with deviceId '" + ((constraints.video).deviceId!!).exact + "' not found"; - } else { - errorName = OpenViduErrorName.PUBLISHER_PROPERTIES_ERROR; - errorMessage = "Video input device doesn't support the value passed for constraint '" + error.constraint + "'"; - } - errorCallback(new OpenViduError(errorName, errorMessage)); - }); - break; - case 'aborterror': - case 'notreadableerror': - errorName = OpenViduErrorName.DEVICE_ALREADY_IN_USE; - errorMessage = error.toString(); - errorCallback(new OpenViduError(errorName, errorMessage)); - break; - default: - errorName = OpenViduErrorName.GENERIC_ERROR; - errorMessage = error.toString(); - errorCallback(new OpenViduError(errorName, errorMessage)); - break; + errorMessage = error.toString(); + errorCallback(new OpenViduError(errorName, errorMessage)); + }); + break; + case 'notallowederror': + errorName = this.stream.isSendScreen() ? OpenViduErrorName.SCREEN_CAPTURE_DENIED : OpenViduErrorName.DEVICE_ACCESS_DENIED; + errorMessage = error.toString(); + errorCallback(new OpenViduError(errorName, errorMessage)); + break; + case 'overconstrainederror': + navigator.mediaDevices.getUserMedia({ + audio: false, + video: constraints.video + }) + .then(mediaStream => { + mediaStream.getVideoTracks().forEach((track) => { + track.stop(); + }); + if (error.constraint.toLowerCase() === 'deviceid') { + errorName = OpenViduErrorName.INPUT_AUDIO_DEVICE_NOT_FOUND; + errorMessage = "Audio input device with deviceId '" + ((constraints.audio).deviceId!!).exact + "' not found"; + } else { + errorName = OpenViduErrorName.PUBLISHER_PROPERTIES_ERROR; + errorMessage = "Audio input device doesn't support the value passed for constraint '" + error.constraint + "'"; + } + errorCallback(new OpenViduError(errorName, errorMessage)); + }).catch(e => { + if (error.constraint.toLowerCase() === 'deviceid') { + errorName = OpenViduErrorName.INPUT_VIDEO_DEVICE_NOT_FOUND; + errorMessage = "Video input device with deviceId '" + ((constraints.video).deviceId!!).exact + "' not found"; + } else { + errorName = OpenViduErrorName.PUBLISHER_PROPERTIES_ERROR; + errorMessage = "Video input device doesn't support the value passed for constraint '" + error.constraint + "'"; + } + errorCallback(new OpenViduError(errorName, errorMessage)); + }); + break; + case 'aborterror': + case 'notreadableerror': + errorName = OpenViduErrorName.DEVICE_ALREADY_IN_USE; + errorMessage = error.toString(); + errorCallback(new OpenViduError(errorName, errorMessage)); + break; + default: + errorName = OpenViduErrorName.GENERIC_ERROR; + errorMessage = error.toString(); + errorCallback(new OpenViduError(errorName, errorMessage)); + break; - } - }); + } + }); } if (platform['isInternetExplorer']) { diff --git a/openvidu-browser/src/OpenVidu/Stream.ts b/openvidu-browser/src/OpenVidu/Stream.ts index 0d28844d..027868c4 100644 --- a/openvidu-browser/src/OpenVidu/Stream.ts +++ b/openvidu-browser/src/OpenVidu/Stream.ts @@ -455,7 +455,7 @@ export class Stream implements EventDispatcher { disposeWebRtcPeer(): void { if (this.webRtcPeer) { const isSenderAndCustomTrack: boolean = !!this.outboundStreamOpts && - typeof MediaStreamTrack !== 'undefined' && this.outboundStreamOpts.publisherProperties.videoSource instanceof MediaStreamTrack; + typeof MediaStreamTrack !== 'undefined' && this.outboundStreamOpts.publisherProperties.videoSource instanceof MediaStreamTrack; this.webRtcPeer.dispose(isSenderAndCustomTrack); } if (this.speechEvent) { @@ -856,7 +856,7 @@ export class Stream implements EventDispatcher { private initWebRtcStats(): void { this.webRtcStats = new WebRtcStats(this); this.webRtcStats.initWebRtcStats(); - + //TODO: send common webrtc stats from client to openvidu-server /*if (this.session.openvidu.webrtcStatsInterval > 0) { setInterval(() => { diff --git a/openvidu-browser/src/OpenVidu/StreamManager.ts b/openvidu-browser/src/OpenVidu/StreamManager.ts index b1bb12e8..d37486af 100644 --- a/openvidu-browser/src/OpenVidu/StreamManager.ts +++ b/openvidu-browser/src/OpenVidu/StreamManager.ts @@ -111,7 +111,8 @@ export class StreamManager implements EventDispatcher { this.firstVideoElement = { targetElement: targEl, video: document.createElement('video'), - id: '' + id: '', + canplayListenerAdded: false }; if (platform.name === 'Safari') { this.firstVideoElement.video.setAttribute('playsinline', 'true'); @@ -239,7 +240,9 @@ export class StreamManager implements EventDispatcher { this.initializeVideoProperties(video); if (this.stream.isLocal() && this.stream.displayMyRemote()) { - video.srcObject = this.stream.getMediaStream(); + if (video.srcObject !== this.stream.getMediaStream()) { + video.srcObject = this.stream.getMediaStream(); + } } if (platform['isInternetExplorer'] && !!this.stream.getMediaStream()) { @@ -268,7 +271,8 @@ export class StreamManager implements EventDispatcher { this.pushNewStreamManagerVideo({ video, - id: video.id + id: video.id, + canplayListenerAdded: false }); console.info('New video element associated to ', this); @@ -285,6 +289,8 @@ export class StreamManager implements EventDispatcher { * * @param targetElement HTML DOM element (or its `id` attribute) in which the video element of the Publisher/Subscriber will be inserted * @param insertMode How the video element will be inserted accordingly to `targetElemet` + * + * @returns The created HTMLVideoElement */ createVideoElement(targetElement?: string | HTMLElement, insertMode?: VideoInsertMode): HTMLVideoElement { let targEl; @@ -333,7 +339,8 @@ export class StreamManager implements EventDispatcher { targetElement: targEl, video, insertMode: insMode, - id: video.id + id: video.id, + canplayListenerAdded: false }; this.pushNewStreamManagerVideo(v); @@ -353,7 +360,10 @@ export class StreamManager implements EventDispatcher { initializeVideoProperties(video: HTMLVideoElement): void { if (!(this.stream.isLocal() && this.stream.displayMyRemote())) { // Avoid setting the MediaStream into the srcObject if remote subscription before publishing - video.srcObject = this.stream.getMediaStream(); + if (video.srcObject !== this.stream.getMediaStream()) { + // If srcObject already set don't do it again + video.srcObject = this.stream.getMediaStream(); + } } video.autoplay = true; video.controls = false; @@ -362,12 +372,12 @@ export class StreamManager implements EventDispatcher { video.setAttribute('playsinline', 'true'); } - if (!video.id && !platform['isInternetExplorer']) { - video.id = (this.remote ? 'remote-' : 'local-') + 'video-' + this.stream.streamId; + if (!video.id && !platform['isInternetExplorer']) { + video.id = (this.remote ? 'remote-' : 'local-') + 'video-' + this.stream.streamId; // DEPRECATED property: assign once the property id if the user provided a valid targetElement - if (!this.id && !!this.targetElement) { - this.id = video.id; - } + if (!this.id && !!this.targetElement) { + this.id = video.id; + } } if (!this.remote && !this.stream.displayMyRemote()) { @@ -392,8 +402,9 @@ export class StreamManager implements EventDispatcher { } this.videos.forEach(streamManagerVideo => { - // Remove oncanplay event listener (only OpenVidu browser one, not the user ones) + // Remove oncanplay event listener (only OpenVidu browser listener, not the user ones) streamManagerVideo.video.removeEventListener('canplay', platform['isInternetExplorer'] ? (window).IEOnCanPlay : this.canPlayListener); + streamManagerVideo.canplayListenerAdded = false; if (!!streamManagerVideo.targetElement) { // Only remove from DOM videos created by OpenVidu Browser (those generated by passing a valid targetElement in OpenVidu.initPublisher // and Session.subscribe or those created by StreamManager.createVideoElement). All this videos triggered a videoElementCreated event @@ -414,6 +425,7 @@ export class StreamManager implements EventDispatcher { let disassociated = false; for (let i = 0; i < this.videos.length; i++) { if (this.videos[i].video === video) { + this.videos[i].video.removeEventListener('canplay', platform['isInternetExplorer'] ? (window).IEOnCanPlay : this.canPlayListener); this.videos.splice(i, 1); disassociated = true; console.info('Video element disassociated from ', this); @@ -427,7 +439,7 @@ export class StreamManager implements EventDispatcher { * @hidden */ addPlayEventToFirstVideo() { - if ((!!this.videos[0]) && (!!this.videos[0].video) && (this.videos[0].video.oncanplay === null)) { + if ((!!this.videos[0]) && (!!this.videos[0].video) && (!this.videos[0].canplayListenerAdded)) { if (platform['isInternetExplorer']) { if (!(this.videos[0].video instanceof HTMLVideoElement)) { // Add canplay event listener only after plugin has inserted custom video element (not a DOM HTMLVideoElement) @@ -436,6 +448,7 @@ export class StreamManager implements EventDispatcher { } else { this.videos[0].video.addEventListener('canplay', this.canPlayListener); } + this.videos[0].canplayListenerAdded = true; } } diff --git a/openvidu-browser/src/OpenViduInternal/Events/StreamManagerEvent.ts b/openvidu-browser/src/OpenViduInternal/Events/StreamManagerEvent.ts index c5880968..a905cf58 100644 --- a/openvidu-browser/src/OpenViduInternal/Events/StreamManagerEvent.ts +++ b/openvidu-browser/src/OpenViduInternal/Events/StreamManagerEvent.ts @@ -21,7 +21,7 @@ import { StreamManager } from '../../OpenVidu/StreamManager'; /** * Defines the following events: * - `streamPlaying`: dispatched by [[StreamManager]] ([[Publisher]] and [[Subscriber]]) whenever its media stream starts playing (one of its videos has media - * and has begun to play) + * and has begun to play). This event will be dispatched when these 3 conditions are met 1) The StreamManager has no video associated in the DOM 2) It is associated to one video 3) That video starts playing * - `streamAudioVolumeChange`: dispatched by [[StreamManager]] ([[Publisher]] and [[Subscriber]]) when the volume of its Stream's audio track * changes. Only applies if [[Stream.hasAudio]] is `true`. The frequency this event is fired with is defined by property `interval` of * [[OpenViduAdvancedConfiguration.publisherSpeakingEventsOptions]] (default 50ms) diff --git a/openvidu-browser/src/OpenViduInternal/Interfaces/Public/StreamManagerVideo.ts b/openvidu-browser/src/OpenViduInternal/Interfaces/Public/StreamManagerVideo.ts index 1b4c5ab0..9c0c235e 100644 --- a/openvidu-browser/src/OpenViduInternal/Interfaces/Public/StreamManagerVideo.ts +++ b/openvidu-browser/src/OpenViduInternal/Interfaces/Public/StreamManagerVideo.ts @@ -15,8 +15,6 @@ * */ -import { Connection } from '../../../OpenVidu/Connection'; -import { StreamManager } from '../../../OpenVidu/StreamManager'; import { VideoInsertMode } from '../../Enums/VideoInsertMode'; @@ -54,4 +52,10 @@ export interface StreamManagerVideo { */ insertMode?: VideoInsertMode; + /** + * @hidden + */ + canplayListenerAdded: boolean; + + } \ No newline at end of file diff --git a/openvidu-browser/src/OpenViduInternal/WebRtcPeer/WebRtcPeer.ts b/openvidu-browser/src/OpenViduInternal/WebRtcPeer/WebRtcPeer.ts index cdbdc22b..d003b72d 100644 --- a/openvidu-browser/src/OpenViduInternal/WebRtcPeer/WebRtcPeer.ts +++ b/openvidu-browser/src/OpenViduInternal/WebRtcPeer/WebRtcPeer.ts @@ -74,7 +74,7 @@ export class WebRtcPeer { sdpMLineIndex: iceCandidate.sdpMLineIndex }; const finalIECandidate = new RTCIceCandidate(iceCandidateAuxIE); - (this.pc).addIceCandidate(finalIECandidate, () => {}, () => {}); + (this.pc).addIceCandidate(finalIECandidate, () => { }, () => { }); } } else { this.pc.addIceCandidate(this.iceCandidateList.shift()); @@ -223,7 +223,7 @@ export class WebRtcPeer { .catch(error => reject(error)); } else if (platform['isInternetExplorer']) { - + // IE Explorer cannot use Promise base API let setLocalDescriptionOnSuccess = () => { const localDescription = this.pc.localDescription; @@ -257,16 +257,16 @@ export class WebRtcPeer { console.debug('Created SDP offer'); return this.pc.setLocalDescription(offer); }) - .then(() => { - const localDescription = this.pc.localDescription; - if (!!localDescription) { - console.debug('Local description set', localDescription.sdp); - resolve(localDescription.sdp); - } else { - reject('Local description is not defined'); - } - }) - .catch(error => reject(error)); + .then(() => { + const localDescription = this.pc.localDescription; + if (!!localDescription) { + console.debug('Local description set', localDescription.sdp); + resolve(localDescription.sdp); + } else { + reject('Local description is not defined'); + } + }) + .catch(error => reject(error)); } }); }