diff --git a/openvidu-browser/reactnative.sh b/openvidu-browser/reactnative.sh new file mode 100755 index 00000000..6da39f95 --- /dev/null +++ b/openvidu-browser/reactnative.sh @@ -0,0 +1,4 @@ +npm run build +npm pack +cp openvidu-browser-2.14.0.tgz ../../openvidu-tutorials/openvidu-react-native +cp openvidu-browser-2.14.0.tgz ../../openvidu-react-native-adapter/ diff --git a/openvidu-browser/src/OpenVidu/OpenVidu.ts b/openvidu-browser/src/OpenVidu/OpenVidu.ts index 1eac7b8b..02a67671 100644 --- a/openvidu-browser/src/OpenVidu/OpenVidu.ts +++ b/openvidu-browser/src/OpenVidu/OpenVidu.ts @@ -30,7 +30,6 @@ import { OpenViduLogger } from '../OpenViduInternal/Logger/OpenViduLogger'; import * as screenSharingAuto from '../OpenViduInternal/ScreenSharing/Screen-Capturing-Auto'; import * as screenSharing from '../OpenViduInternal/ScreenSharing/Screen-Capturing'; - /** * @hidden */ @@ -748,115 +747,7 @@ export class OpenVidu { } // CASE 4: deviceId or screen sharing - if (typeof audioSource === 'string') { - myConstraints.constraints!.audio = { deviceId: { exact: audioSource } }; - } - if (typeof videoSource === 'string') { - - if (!this.isScreenShare(videoSource)) { - if (!myConstraints.constraints!.video) { - myConstraints.constraints!.video = {}; - } - (myConstraints.constraints!.video)['deviceId'] = { exact: videoSource }; - } else { - - // Screen sharing - - if (!this.checkScreenSharingCapabilities()) { - const error = new OpenViduError(OpenViduErrorName.SCREEN_SHARING_NOT_SUPPORTED, 'You can only screen share in desktop Chrome, Firefox, Opera, Safari (>=13.0) or Electron. Detected client: ' + platform.name); - logger.error(error); - reject(error); - } else { - - if (platform.name === 'Electron') { - const prefix = "screen:"; - const videoSourceString: string = videoSource; - const electronScreenId = videoSourceString.substr(videoSourceString.indexOf(prefix) + prefix.length); - (myConstraints.constraints!.video) = { - mandatory: { - chromeMediaSource: 'desktop', - chromeMediaSourceId: electronScreenId - } - }; - resolve(myConstraints); - - } else { - - if (!!this.advancedConfiguration.screenShareChromeExtension && !(platform.name!.indexOf('Firefox') !== -1) && !navigator.mediaDevices['getDisplayMedia']) { - - // Custom screen sharing extension for Chrome (and Opera) and no support for MediaDevices.getDisplayMedia() - - screenSharing.getScreenConstraints((error, screenConstraints) => { - if (!!error || !!screenConstraints.mandatory && screenConstraints.mandatory.chromeMediaSource === 'screen') { - if (error === 'permission-denied' || error === 'PermissionDeniedError') { - const error = new OpenViduError(OpenViduErrorName.SCREEN_CAPTURE_DENIED, 'You must allow access to one window of your desktop'); - logger.error(error); - reject(error); - } else { - const extensionId = this.advancedConfiguration.screenShareChromeExtension!.split('/').pop()!!.trim(); - screenSharing.getChromeExtensionStatus(extensionId, status => { - if (status === 'installed-disabled') { - const error = new OpenViduError(OpenViduErrorName.SCREEN_EXTENSION_DISABLED, 'You must enable the screen extension'); - logger.error(error); - reject(error); - } - if (status === 'not-installed') { - const error = new OpenViduError(OpenViduErrorName.SCREEN_EXTENSION_NOT_INSTALLED, (this.advancedConfiguration.screenShareChromeExtension)); - logger.error(error); - reject(error); - } - }); - return; - } - } else { - myConstraints.constraints!.video = screenConstraints; - resolve(myConstraints); - } - }); - return; - } else { - - if (navigator.mediaDevices['getDisplayMedia']) { - // getDisplayMedia support (Chrome >= 72, Firefox >= 66, Safari >= 13) - resolve(myConstraints); - } else { - // Default screen sharing extension for Chrome/Opera, or is Firefox < 66 - const firefoxString = platform.name!.indexOf('Firefox') !== -1 ? publisherProperties.videoSource : undefined; - - screenSharingAuto.getScreenId(firefoxString, (error, sourceId, screenConstraints) => { - if (!!error) { - if (error === 'not-installed') { - const extensionUrl = !!this.advancedConfiguration.screenShareChromeExtension ? this.advancedConfiguration.screenShareChromeExtension : - 'https://chrome.google.com/webstore/detail/openvidu-screensharing/lfcgfepafnobdloecchnfaclibenjold'; - const err = new OpenViduError(OpenViduErrorName.SCREEN_EXTENSION_NOT_INSTALLED, extensionUrl); - logger.error(err); - reject(err); - } else if (error === 'installed-disabled') { - const err = new OpenViduError(OpenViduErrorName.SCREEN_EXTENSION_DISABLED, 'You must enable the screen extension'); - logger.error(err); - reject(err); - } else if (error === 'permission-denied') { - const err = new OpenViduError(OpenViduErrorName.SCREEN_CAPTURE_DENIED, 'You must allow access to one window of your desktop'); - logger.error(err); - reject(err); - } else { - const err = new OpenViduError(OpenViduErrorName.GENERIC_ERROR, 'Unknown error when accessing screen share'); - logger.error(err); - logger.error(error); - reject(err); - } - } else { - myConstraints.constraints!.video = screenConstraints.video; - resolve(myConstraints); - } - }); - return; - } - } - } - } - } - } + this.configureDeviceIdOrScreensharing(myConstraints, publisherProperties, resolve, reject); resolve(myConstraints); }); @@ -984,6 +875,133 @@ export class OpenVidu { return mediaStream; } + /** + * @hidden + */ + protected configureDeviceIdOrScreensharing(myConstraints: CustomMediaStreamConstraints, publisherProperties: PublisherProperties, resolve, reject) { + const audioSource = publisherProperties.audioSource; + const videoSource = publisherProperties.videoSource; + if (typeof audioSource === 'string') { + myConstraints.constraints!.audio = { deviceId: { exact: audioSource } }; + } + + if (typeof videoSource === 'string') { + + if (!this.isScreenShare(videoSource)) { + this.setVideoSource(myConstraints, videoSource); + + } else { + + // Screen sharing + + if (!this.checkScreenSharingCapabilities()) { + const error = new OpenViduError(OpenViduErrorName.SCREEN_SHARING_NOT_SUPPORTED, 'You can only screen share in desktop Chrome, Firefox, Opera, Safari (>=13.0) or Electron. Detected client: ' + platform.name); + logger.error(error); + reject(error); + } else { + + if (platform.name === 'Electron') { + const prefix = "screen:"; + const videoSourceString: string = videoSource; + const electronScreenId = videoSourceString.substr(videoSourceString.indexOf(prefix) + prefix.length); + (myConstraints.constraints!.video) = { + mandatory: { + chromeMediaSource: 'desktop', + chromeMediaSourceId: electronScreenId + } + }; + resolve(myConstraints); + + } else { + + if (!!this.advancedConfiguration.screenShareChromeExtension && !(platform.name!.indexOf('Firefox') !== -1) && !navigator.mediaDevices['getDisplayMedia']) { + + // Custom screen sharing extension for Chrome (and Opera) and no support for MediaDevices.getDisplayMedia() + + screenSharing.getScreenConstraints((error, screenConstraints) => { + if (!!error || !!screenConstraints.mandatory && screenConstraints.mandatory.chromeMediaSource === 'screen') { + if (error === 'permission-denied' || error === 'PermissionDeniedError') { + const error = new OpenViduError(OpenViduErrorName.SCREEN_CAPTURE_DENIED, 'You must allow access to one window of your desktop'); + logger.error(error); + reject(error); + } else { + const extensionId = this.advancedConfiguration.screenShareChromeExtension!.split('/').pop()!!.trim(); + screenSharing.getChromeExtensionStatus(extensionId, status => { + if (status === 'installed-disabled') { + const error = new OpenViduError(OpenViduErrorName.SCREEN_EXTENSION_DISABLED, 'You must enable the screen extension'); + logger.error(error); + reject(error); + } + if (status === 'not-installed') { + const error = new OpenViduError(OpenViduErrorName.SCREEN_EXTENSION_NOT_INSTALLED, (this.advancedConfiguration.screenShareChromeExtension)); + logger.error(error); + reject(error); + } + }); + return; + } + } else { + myConstraints.constraints!.video = screenConstraints; + resolve(myConstraints); + } + }); + return; + } else { + + if (navigator.mediaDevices['getDisplayMedia']) { + // getDisplayMedia support (Chrome >= 72, Firefox >= 66, Safari >= 13) + resolve(myConstraints); + } else { + // Default screen sharing extension for Chrome/Opera, or is Firefox < 66 + const firefoxString = platform.name!.indexOf('Firefox') !== -1 ? publisherProperties.videoSource : undefined; + + screenSharingAuto.getScreenId(firefoxString, (error, sourceId, screenConstraints) => { + if (!!error) { + if (error === 'not-installed') { + const extensionUrl = !!this.advancedConfiguration.screenShareChromeExtension ? this.advancedConfiguration.screenShareChromeExtension : + 'https://chrome.google.com/webstore/detail/openvidu-screensharing/lfcgfepafnobdloecchnfaclibenjold'; + const err = new OpenViduError(OpenViduErrorName.SCREEN_EXTENSION_NOT_INSTALLED, extensionUrl); + logger.error(err); + reject(err); + } else if (error === 'installed-disabled') { + const err = new OpenViduError(OpenViduErrorName.SCREEN_EXTENSION_DISABLED, 'You must enable the screen extension'); + logger.error(err); + reject(err); + } else if (error === 'permission-denied') { + const err = new OpenViduError(OpenViduErrorName.SCREEN_CAPTURE_DENIED, 'You must allow access to one window of your desktop'); + logger.error(err); + reject(err); + } else { + const err = new OpenViduError(OpenViduErrorName.GENERIC_ERROR, 'Unknown error when accessing screen share'); + logger.error(err); + logger.error(error); + reject(err); + } + } else { + myConstraints.constraints!.video = screenConstraints.video; + resolve(myConstraints); + } + }); + return; + } + } + } + } + } + } + } + + /** + * @hidden + */ + protected setVideoSource(myConstraints: CustomMediaStreamConstraints, videoSource: string) { + if (!myConstraints.constraints!.video) { + myConstraints.constraints!.video = {}; + } + (myConstraints.constraints!.video)['deviceId'] = { exact: videoSource }; + } + + /* Private methods */ private disconnectCallback(): void { diff --git a/openvidu-browser/src/OpenVidu/Publisher.ts b/openvidu-browser/src/OpenVidu/Publisher.ts index b7988606..60151ca6 100644 --- a/openvidu-browser/src/OpenVidu/Publisher.ts +++ b/openvidu-browser/src/OpenVidu/Publisher.ts @@ -67,7 +67,7 @@ export class Publisher extends StreamManager { session: Session; // Initialized by Session.publish(Publisher) private accessDenied = false; - private properties: PublisherProperties; + protected properties: PublisherProperties; private permissionDialogTimeout: NodeJS.Timer; /** @@ -385,19 +385,7 @@ export class Publisher extends StreamManager { mediaStream.getVideoTracks()[0].enabled = enabled; } - this.videoReference = document.createElement('video'); - - if (platform.name === 'Safari') { - this.videoReference.setAttribute('playsinline', 'true'); - } - - this.stream.setMediaStream(mediaStream); - - if (!!this.firstVideoElement) { - this.createVideoElement(this.firstVideoElement.targetElement, this.properties.insertMode); - } - - this.videoReference.srcObject = mediaStream; + this.initializeVideoReference(mediaStream); if (!this.stream.displayMyRemote()) { // When we are subscribed to our remote we don't still set the MediaStream object in the video elements to @@ -443,7 +431,7 @@ export class Publisher extends StreamManager { // Rest of platforms // 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) - const { width, height } = mediaStream.getVideoTracks()[0].getSettings(); + const { width, height } = this.getVideoDimensions(mediaStream); if ((platform.os!!.family === 'iOS' || platform.os!!.family === 'Android') && (window.innerHeight > window.innerWidth)) { // Mobile portrait mode @@ -666,6 +654,13 @@ export class Publisher extends StreamManager { }); } + /** + * @hidden + */ + getVideoDimensions(mediaStream: MediaStream): MediaTrackSettings { + return mediaStream.getVideoTracks()[0].getSettings(); + } + /** * @hidden */ @@ -675,6 +670,25 @@ export class Publisher extends StreamManager { } } + /** + * @hidden + */ + initializeVideoReference(mediaStream: MediaStream) { + this.videoReference = document.createElement('video'); + + if (platform.name === 'Safari') { + this.videoReference.setAttribute('playsinline', 'true'); + } + + this.stream.setMediaStream(mediaStream); + + if (!!this.firstVideoElement) { + this.createVideoElement(this.firstVideoElement.targetElement, this.properties.insertMode); + } + + this.videoReference.srcObject = mediaStream; + } + /* Private methods */ diff --git a/openvidu-browser/src/OpenVidu/Session.ts b/openvidu-browser/src/OpenVidu/Session.ts index a5cc5240..042ba4fb 100644 --- a/openvidu-browser/src/OpenVidu/Session.ts +++ b/openvidu-browser/src/OpenVidu/Session.ts @@ -1098,6 +1098,21 @@ export class Session extends EventDispatcher { } } + /** + * @hidden + */ + initializeParams(token: string) { + const joinParams = { + token: (!!token) ? token : '', + session: this.sessionId, + platform: !!platform.description ? platform.description : 'unknown', + metadata: !!this.options.metadata ? this.options.metadata : '', + secret: this.openvidu.getSecret(), + recorder: this.openvidu.getRecorder() + }; + return joinParams; + } + /* Private methods */ @@ -1108,14 +1123,7 @@ export class Session extends EventDispatcher { reject(error); } else { - const joinParams = { - token: (!!token) ? token : '', - session: this.sessionId, - platform: !!platform.description ? platform.description : 'unknown', - metadata: !!this.options.metadata ? this.options.metadata : '', - secret: this.openvidu.getSecret(), - recorder: this.openvidu.getRecorder() - }; + const joinParams = this.initializeParams(token); this.openvidu.sendRequest('joinRoom', joinParams, (error, response) => { if (!!error) { @@ -1193,7 +1201,7 @@ export class Session extends EventDispatcher { } } - private getConnection(connectionId: string, errorMessage: string): Promise { + protected getConnection(connectionId: string, errorMessage: string): Promise { return new Promise((resolve, reject) => { const connection = this.remoteConnections[connectionId]; if (!!connection) { diff --git a/openvidu-browser/src/OpenVidu/Stream.ts b/openvidu-browser/src/OpenVidu/Stream.ts index dbbbf729..e814f5e0 100644 --- a/openvidu-browser/src/OpenVidu/Stream.ts +++ b/openvidu-browser/src/OpenVidu/Stream.ts @@ -133,8 +133,8 @@ export class Stream extends EventDispatcher { */ filter: Filter; - private webRtcPeer: WebRtcPeer; - private mediaStream: MediaStream; + protected webRtcPeer: WebRtcPeer; + protected mediaStream: MediaStream; private webRtcStats: WebRtcStats; private isSubscribeToRemote = false; @@ -964,7 +964,10 @@ export class Stream extends EventDispatcher { }); } - private remotePeerSuccessfullyEstablished(): void { + /** + * @hidden + */ + remotePeerSuccessfullyEstablished(): void { this.mediaStream = new MediaStream(); let receiver: RTCRtpReceiver; for (receiver of this.webRtcPeer.pc.getReceivers()) { diff --git a/openvidu-browser/src/OpenVidu/StreamManager.ts b/openvidu-browser/src/OpenVidu/StreamManager.ts index e9f588b2..2025d69b 100644 --- a/openvidu-browser/src/OpenVidu/StreamManager.ts +++ b/openvidu-browser/src/OpenVidu/StreamManager.ts @@ -297,7 +297,7 @@ export class StreamManager extends EventDispatcher { throw new Error("The provided 'targetElement' couldn't be resolved to any HTML element: " + targetElement); } - const video = document.createElement('video'); + const video = this.createVideo(); this.initializeVideoProperties(video); let insMode = !!insertMode ? insertMode : VideoInsertMode.APPEND; @@ -414,8 +414,9 @@ export class StreamManager extends EventDispatcher { this.videos.forEach(streamManagerVideo => { // Remove oncanplay event listener (only OpenVidu browser listener, not the user ones) - streamManagerVideo.video.removeEventListener('canplay', this.canPlayListener); - streamManagerVideo.canplayListenerAdded = false; + if(!!streamManagerVideo.video && !!streamManagerVideo.video.removeEventListener){ + streamManagerVideo.video.removeEventListener('canplay', 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 @@ -423,7 +424,7 @@ export class StreamManager extends EventDispatcher { this.ee.emitEvent('videoElementDestroyed', [new VideoElementEvent(streamManagerVideo.video, this, 'videoElementDestroyed')]); } // Remove srcObject from the video - streamManagerVideo.video.srcObject = null; + this.removeSrcObject(streamManagerVideo); // Remove from collection of videos every video managed by OpenVidu Browser this.videos.filter(v => !v.targetElement); }); @@ -480,9 +481,23 @@ export class StreamManager extends EventDispatcher { this.ee.emitEvent(type, eventArray); } + /** + * @hidden + */ + createVideo(): HTMLVideoElement { + return document.createElement('video'); + } + + /** + * @hidden + */ + removeSrcObject(streamManagerVideo: StreamManagerVideo) { + streamManagerVideo.video.srcObject = null; + } + /* Private methods */ - private pushNewStreamManagerVideo(streamManagerVideo: StreamManagerVideo) { + protected pushNewStreamManagerVideo(streamManagerVideo: StreamManagerVideo) { this.videos.push(streamManagerVideo); this.addPlayEventToFirstVideo(); if (this.stream.session.streamManagers.indexOf(this) === -1) { diff --git a/openvidu-browser/src/OpenViduInternal/ScreenSharing/Screen-Capturing.js b/openvidu-browser/src/OpenViduInternal/ScreenSharing/Screen-Capturing.js index 4aeaaf2e..49b5a442 100644 --- a/openvidu-browser/src/OpenViduInternal/ScreenSharing/Screen-Capturing.js +++ b/openvidu-browser/src/OpenViduInternal/ScreenSharing/Screen-Capturing.js @@ -2,16 +2,19 @@ var chromeMediaSource = 'screen'; var sourceId; var screenCallback; -var isFirefox = typeof window.InstallTrigger !== 'undefined'; -var isOpera = !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0; -var isChrome = !!window.chrome && !isOpera; -window.addEventListener('message', function (event) { - if (event.origin != window.location.origin) { - return; - } - onMessageCallback(event.data); -}); +if(typeof window !== 'undefined' && typeof navigator !== 'undefined' && typeof navigator.userAgent !== 'undefined'){ + var isFirefox = typeof window.InstallTrigger !== 'undefined'; + var isOpera = !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0; + var isChrome = !!window.chrome && !isOpera; + + window.addEventListener('message', function (event) { + if (event.origin != window.location.origin) { + return; + } + onMessageCallback(event.data); + }); +} // and the function that handles received messages function onMessageCallback(data) { @@ -148,7 +151,7 @@ function getScreenConstraints(callback, captureSourceIdWithAudio) { return; } - // this statement sets gets 'sourceId" and sets "chromeMediaSourceId" + // this statement sets gets 'sourceId" and sets "chromeMediaSourceId" if (chromeMediaSource == 'desktop') { screen_constraints.mandatory.chromeMediaSourceId = sourceId; } diff --git a/openvidu-browser/src/OpenViduInternal/WebRtcPeer/WebRtcPeer.ts b/openvidu-browser/src/OpenViduInternal/WebRtcPeer/WebRtcPeer.ts index 43e4bb26..42e161ae 100644 --- a/openvidu-browser/src/OpenViduInternal/WebRtcPeer/WebRtcPeer.ts +++ b/openvidu-browser/src/OpenViduInternal/WebRtcPeer/WebRtcPeer.ts @@ -49,11 +49,11 @@ export class WebRtcPeer { private candidategatheringdone = false; - constructor(private configuration: WebRtcPeerConfiguration) { + constructor(protected configuration: WebRtcPeerConfiguration) { this.configuration.iceServers = (!!this.configuration.iceServers && this.configuration.iceServers.length > 0) ? this.configuration.iceServers : freeice(); this.pc = new RTCPeerConnection({ iceServers: this.configuration.iceServers }); - this.id = !!configuration.id ? configuration.id : uuid.v4(); + this.id = !!configuration.id ? configuration.id : this.generateUniqueId(); this.pc.onicecandidate = event => { if (!!event.candidate) { @@ -71,7 +71,8 @@ export class WebRtcPeer { this.pc.onsignalingstatechange = () => { if (this.pc.signalingState === 'stable') { while (this.iceCandidateList.length > 0) { - this.pc.addIceCandidate(this.iceCandidateList.shift()); + let candidate = this.iceCandidateList.shift(); + this.pc.addIceCandidate(candidate); } } }; @@ -206,25 +207,34 @@ export class WebRtcPeer { if (this.pc.signalingState === 'closed') { reject('RTCPeerConnection is closed'); } - if (platform['isIonicIos']) { - // Ionic iOS platform - if (needsTimeoutOnProcessAnswer) { - // 400 ms have not elapsed yet since first remote stream triggered Stream#initWebRtcPeerReceive - setTimeout(() => { - logger.info('setRemoteDescription run after timeout for Ionic iOS device'); - this.pc.setRemoteDescription(new RTCSessionDescription(answer)).then(() => resolve()).catch(error => reject(error)); - }, 250); - } else { - // 400 ms have elapsed - this.pc.setRemoteDescription(new RTCSessionDescription(answer)).then(() => resolve()).catch(error => reject(error)); - } - } else { - // Rest of platforms - this.pc.setRemoteDescription(answer).then(() => resolve()).catch(error => reject(error)); - } + + this.setRemoteDescription(answer, needsTimeoutOnProcessAnswer, resolve, reject); + }); } + /** + * @hidden + */ + setRemoteDescription(answer: RTCSessionDescriptionInit, needsTimeoutOnProcessAnswer: boolean, resolve: (value?: string | PromiseLike | undefined) => void, reject: (reason?: any) => void) { + if (platform['isIonicIos']) { + // Ionic iOS platform + if (needsTimeoutOnProcessAnswer) { + // 400 ms have not elapsed yet since first remote stream triggered Stream#initWebRtcPeerReceive + setTimeout(() => { + logger.info('setRemoteDescription run after timeout for Ionic iOS device'); + this.pc.setRemoteDescription(new RTCSessionDescription(answer)).then(() => resolve()).catch(error => reject(error)); + }, 250); + } else { + // 400 ms have elapsed + this.pc.setRemoteDescription(new RTCSessionDescription(answer)).then(() => resolve()).catch(error => reject(error)); + } + } else { + // Rest of platforms + this.pc.setRemoteDescription(answer).then(() => resolve()).catch(error => reject(error)); + } + } + /** * Callback function invoked when an ICE candidate is received */ @@ -281,6 +291,13 @@ export class WebRtcPeer { } } + /** + * @hidden + */ + generateUniqueId(): string { + return uuid.v4(); + } + }