mirror of https://github.com/OpenVidu/openvidu.git
openvidu-browser: LEGACY support for openvidu-server 2.15.0
parent
ce488b2884
commit
daf80c0412
|
@ -17,6 +17,7 @@
|
|||
|
||||
import { Session } from './Session';
|
||||
import { Stream } from './Stream';
|
||||
import { StreamLEGACY } from './StreamLEGACY';
|
||||
import { ConnectionOptions } from '../OpenViduInternal/Interfaces/Private/ConnectionOptions';
|
||||
import { InboundStreamOptions } from '../OpenViduInternal/Interfaces/Private/InboundStreamOptions';
|
||||
import { StreamOptionsServer } from '../OpenViduInternal/Interfaces/Private/StreamOptionsServer';
|
||||
|
@ -139,7 +140,11 @@ export class Connection {
|
|||
videoDimensions: !!opts.videoDimensions ? JSON.parse(opts.videoDimensions) : undefined,
|
||||
filter: !!opts.filter ? opts.filter : undefined
|
||||
};
|
||||
const stream = new Stream(this.session, streamOptions);
|
||||
// TODO: CLEAN 2.15.0 LEGACY CODE
|
||||
// THIS LINE:
|
||||
const stream = this.session.openvidu.openviduServerVersion.startsWith('2.16') ? new Stream(this.session, streamOptions) : new StreamLEGACY(this.session, streamOptions);
|
||||
// SHOULD GET BACK TO:
|
||||
// const stream = new Stream(this.session, streamOptions);
|
||||
|
||||
this.addStream(stream);
|
||||
});
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
import { OpenVidu } from './OpenVidu';
|
||||
import { Session } from './Session';
|
||||
import { Stream } from './Stream';
|
||||
import { StreamLEGACY } from './StreamLEGACY';
|
||||
import { StreamManager } from './StreamManager';
|
||||
import { EventDispatcher } from './EventDispatcher';
|
||||
import { PublisherProperties } from '../OpenViduInternal/Interfaces/Public/PublisherProperties';
|
||||
|
@ -91,7 +92,13 @@ export class Publisher extends StreamManager {
|
|||
* @hidden
|
||||
*/
|
||||
constructor(targEl: string | HTMLElement, properties: PublisherProperties, openvidu: OpenVidu) {
|
||||
super(new Stream((!!openvidu.session) ? openvidu.session : new Session(openvidu), { publisherProperties: properties, mediaConstraints: {} }), targEl);
|
||||
|
||||
// TODO: CLEAN 2.15.0 LEGACY CODE
|
||||
// THIS LINE:
|
||||
super(openvidu.openviduServerVersion.startsWith('2.16') ? new Stream((!!openvidu.session) ? openvidu.session : new Session(openvidu), { publisherProperties: properties, mediaConstraints: {} }) : new StreamLEGACY((!!openvidu.session) ? openvidu.session : new Session(openvidu), { publisherProperties: properties, mediaConstraints: {} }), targEl);
|
||||
// SHOULD GET BACK TO:
|
||||
// super(new Stream((!!openvidu.session) ? openvidu.session : new Session(openvidu), { publisherProperties: properties, mediaConstraints: {} }), targEl);
|
||||
|
||||
this.properties = properties;
|
||||
this.openvidu = openvidu;
|
||||
|
||||
|
|
|
@ -128,6 +128,19 @@ export class Session extends EventDispatcher {
|
|||
*/
|
||||
stopSpeakingEventsEnabledOnce = false;
|
||||
|
||||
|
||||
// TODO: CLEAN 2.15.0 LEGACY CODE
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
isFirstIonicIosSubscriber = true;
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
countDownForIonicIosSubscribersActive = true;
|
||||
// END LEGACY CODE
|
||||
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
|
@ -716,6 +729,16 @@ export class Session extends EventDispatcher {
|
|||
streamEvent.callDefaultBehavior();
|
||||
|
||||
delete this.remoteStreamsCreated[stream.streamId];
|
||||
|
||||
|
||||
// TODO: CLEAN 2.15.0 LEGACY CODE
|
||||
if (Object.keys(this.remoteStreamsCreated).length === 0) {
|
||||
this.isFirstIonicIosSubscriber = true;
|
||||
this.countDownForIonicIosSubscribersActive = true;
|
||||
}
|
||||
// END LEGACY CODE
|
||||
|
||||
|
||||
}
|
||||
delete this.remoteConnections[connection.connectionId];
|
||||
this.ee.emitEvent('connectionDestroyed', [new ConnectionEvent(false, this, 'connectionDestroyed', connection, msg.reason)]);
|
||||
|
@ -785,6 +808,16 @@ export class Session extends EventDispatcher {
|
|||
// Deleting the remote stream
|
||||
const streamId: string = connection.stream.streamId;
|
||||
delete this.remoteStreamsCreated[streamId];
|
||||
|
||||
|
||||
// TODO: CLEAN 2.15.0 LEGACY CODE
|
||||
if (Object.keys(this.remoteStreamsCreated).length === 0) {
|
||||
this.isFirstIonicIosSubscriber = true;
|
||||
this.countDownForIonicIosSubscribersActive = true;
|
||||
}
|
||||
// END LEGACY CODE
|
||||
|
||||
|
||||
connection.removeStream(streamId);
|
||||
})
|
||||
.catch(openViduError => {
|
||||
|
|
|
@ -0,0 +1,243 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 OpenVidu (https://openvidu.io)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import { Stream } from './Stream';
|
||||
import { Subscriber } from './Subscriber';
|
||||
import { WebRtcPeerLEGACY, WebRtcPeerSendrecvLEGACY, WebRtcPeerSendonlyLEGACY, WebRtcPeerRecvonlyLEGACY } from '../OpenViduInternal/WebRtcPeer/WebRtcPeerLEGACY';
|
||||
import { OpenViduError, OpenViduErrorName } from '../OpenViduInternal/Enums/OpenViduError';
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
import { OpenViduLogger } from '../OpenViduInternal/Logger/OpenViduLogger';
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
const logger: OpenViduLogger = OpenViduLogger.getInstance();
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Represents each one of the media streams available in OpenVidu Server for certain session.
|
||||
* Each [[Publisher]] and [[Subscriber]] has an attribute of type Stream, as they give access
|
||||
* to one of them (sending and receiving it, respectively)
|
||||
*/
|
||||
export class StreamLEGACY extends Stream {
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
initWebRtcPeerSend(reconnect: boolean): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
if (!reconnect) {
|
||||
this.initHarkEvents(); // Init hark events for the local stream
|
||||
}
|
||||
|
||||
const userMediaConstraints = {
|
||||
audio: this.isSendAudio(),
|
||||
video: this.isSendVideo()
|
||||
};
|
||||
|
||||
const options = {
|
||||
mediaStream: this.mediaStream,
|
||||
mediaConstraints: userMediaConstraints,
|
||||
onicecandidate: this.connection.sendIceCandidate.bind(this.connection),
|
||||
iceServers: this.getIceServersConf(),
|
||||
simulcast: false
|
||||
};
|
||||
|
||||
const successCallback = (sdpOfferParam) => {
|
||||
logger.debug('Sending SDP offer to publish as '
|
||||
+ this.streamId, sdpOfferParam);
|
||||
|
||||
const method = reconnect ? 'reconnectStream' : 'publishVideo';
|
||||
let params;
|
||||
if (reconnect) {
|
||||
params = {
|
||||
stream: this.streamId
|
||||
}
|
||||
} else {
|
||||
let typeOfVideo = '';
|
||||
if (this.isSendVideo()) {
|
||||
typeOfVideo = (typeof MediaStreamTrack !== 'undefined' && this.outboundStreamOpts.publisherProperties.videoSource instanceof MediaStreamTrack) ? 'CUSTOM' : (this.isSendScreen() ? 'SCREEN' : 'CAMERA');
|
||||
}
|
||||
params = {
|
||||
doLoopback: this.displayMyRemote() || false,
|
||||
hasAudio: this.isSendAudio(),
|
||||
hasVideo: this.isSendVideo(),
|
||||
audioActive: this.audioActive,
|
||||
videoActive: this.videoActive,
|
||||
typeOfVideo,
|
||||
frameRate: !!this.frameRate ? this.frameRate : -1,
|
||||
videoDimensions: JSON.stringify(this.videoDimensions),
|
||||
filter: this.outboundStreamOpts.publisherProperties.filter
|
||||
}
|
||||
}
|
||||
params['sdpOffer'] = sdpOfferParam;
|
||||
|
||||
this.session.openvidu.sendRequest(method, params, (error, response) => {
|
||||
if (error) {
|
||||
if (error.code === 401) {
|
||||
reject(new OpenViduError(OpenViduErrorName.OPENVIDU_PERMISSION_DENIED, "You don't have permissions to publish"));
|
||||
} else {
|
||||
reject('Error on publishVideo: ' + JSON.stringify(error));
|
||||
}
|
||||
} else {
|
||||
(<WebRtcPeerLEGACY>this.webRtcPeer).processAnswer(response.sdpAnswer, false)
|
||||
.then(() => {
|
||||
this.streamId = response.id;
|
||||
this.creationTime = response.createdAt;
|
||||
this.isLocalStreamPublished = true;
|
||||
this.publishedOnce = true;
|
||||
if (this.displayMyRemote()) {
|
||||
this.localMediaStreamWhenSubscribedToRemote = this.mediaStream;
|
||||
this.remotePeerSuccessfullyEstablished();
|
||||
}
|
||||
if (reconnect) {
|
||||
this.ee.emitEvent('stream-reconnected-by-publisher', []);
|
||||
} else {
|
||||
this.ee.emitEvent('stream-created-by-publisher', []);
|
||||
}
|
||||
this.initWebRtcStats();
|
||||
logger.info("'Publisher' (" + this.streamId + ") successfully " + (reconnect ? "reconnected" : "published") + " to session");
|
||||
resolve();
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (reconnect) {
|
||||
this.disposeWebRtcPeer();
|
||||
}
|
||||
if (this.displayMyRemote()) {
|
||||
this.webRtcPeer = new WebRtcPeerSendrecvLEGACY(options);
|
||||
} else {
|
||||
this.webRtcPeer = new WebRtcPeerSendonlyLEGACY(options);
|
||||
}
|
||||
this.webRtcPeer.addIceConnectionStateChangeListener('publisher of ' + this.connection.connectionId);
|
||||
(<WebRtcPeerLEGACY>this.webRtcPeer).generateOffer().then(sdpOffer => {
|
||||
successCallback(sdpOffer);
|
||||
}).catch(error => {
|
||||
reject(new Error('(publish) SDP offer error: ' + JSON.stringify(error)));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
initWebRtcPeerReceive(reconnect: boolean): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
const offerConstraints = {
|
||||
audio: this.inboundStreamOpts.hasAudio,
|
||||
video: this.inboundStreamOpts.hasVideo
|
||||
};
|
||||
logger.debug("'Session.subscribe(Stream)' called. Constraints of generate SDP offer",
|
||||
offerConstraints);
|
||||
const options = {
|
||||
onicecandidate: this.connection.sendIceCandidate.bind(this.connection),
|
||||
mediaConstraints: offerConstraints,
|
||||
iceServers: this.getIceServersConf(),
|
||||
simulcast: false
|
||||
};
|
||||
|
||||
const successCallback = (sdpOfferParam) => {
|
||||
logger.debug('Sending SDP offer to subscribe to '
|
||||
+ this.streamId, sdpOfferParam);
|
||||
|
||||
const method = reconnect ? 'reconnectStream' : 'receiveVideoFrom';
|
||||
const params = { sdpOffer: sdpOfferParam };
|
||||
params[reconnect ? 'stream' : 'sender'] = this.streamId;
|
||||
|
||||
this.session.openvidu.sendRequest(method, params, (error, response) => {
|
||||
if (error) {
|
||||
reject(new Error('Error on recvVideoFrom: ' + JSON.stringify(error)));
|
||||
} else {
|
||||
// Ios Ionic. Limitation: some bug in iosrtc cordova plugin makes it necessary
|
||||
// to add a timeout before calling PeerConnection#setRemoteDescription during
|
||||
// some time (400 ms) from the moment first subscriber stream is received
|
||||
if (this.session.isFirstIonicIosSubscriber) {
|
||||
this.session.isFirstIonicIosSubscriber = false;
|
||||
setTimeout(() => {
|
||||
// After 400 ms Ionic iOS subscribers won't need to run
|
||||
// PeerConnection#setRemoteDescription after 250 ms timeout anymore
|
||||
this.session.countDownForIonicIosSubscribersActive = false;
|
||||
}, 400);
|
||||
}
|
||||
const needsTimeoutOnProcessAnswer = this.session.countDownForIonicIosSubscribersActive;
|
||||
(<WebRtcPeerLEGACY>this.webRtcPeer).processAnswer(response.sdpAnswer, needsTimeoutOnProcessAnswer).then(() => {
|
||||
logger.info("'Subscriber' (" + this.streamId + ") successfully " + (reconnect ? "reconnected" : "subscribed"));
|
||||
this.remotePeerSuccessfullyEstablished();
|
||||
this.initWebRtcStats();
|
||||
resolve();
|
||||
}).catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
this.webRtcPeer = new WebRtcPeerRecvonlyLEGACY(options);
|
||||
this.webRtcPeer.addIceConnectionStateChangeListener(this.streamId);
|
||||
(<WebRtcPeerLEGACY>this.webRtcPeer).generateOffer()
|
||||
.then(sdpOffer => {
|
||||
successCallback(sdpOffer);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(new Error('(subscribe) SDP offer error: ' + JSON.stringify(error)));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
remotePeerSuccessfullyEstablished(): void {
|
||||
this.mediaStream = new MediaStream();
|
||||
let receiver: RTCRtpReceiver;
|
||||
for (receiver of this.webRtcPeer.pc.getReceivers()) {
|
||||
if (!!receiver.track) {
|
||||
this.mediaStream.addTrack(receiver.track);
|
||||
}
|
||||
}
|
||||
logger.debug('Peer remote stream', this.mediaStream);
|
||||
|
||||
if (!!this.mediaStream) {
|
||||
|
||||
if (this.streamManager instanceof Subscriber) {
|
||||
// Apply SubscriberProperties.subscribeToAudio and SubscriberProperties.subscribeToVideo
|
||||
if (!!this.mediaStream.getAudioTracks()[0]) {
|
||||
const enabled = !!((<Subscriber>this.streamManager).properties.subscribeToAudio);
|
||||
this.mediaStream.getAudioTracks()[0].enabled = enabled;
|
||||
}
|
||||
if (!!this.mediaStream.getVideoTracks()[0]) {
|
||||
const enabled = !!((<Subscriber>this.streamManager).properties.subscribeToVideo);
|
||||
this.mediaStream.getVideoTracks()[0].enabled = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
this.updateMediaStreamInVideos();
|
||||
this.initHarkEvents(); // Init hark events for the remote stream
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,174 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 OpenVidu (https://openvidu.io)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import platform = require('platform');
|
||||
import { OpenViduLogger } from '../Logger/OpenViduLogger';
|
||||
import { WebRtcPeerConfiguration } from './WebRtcPeer';
|
||||
import { WebRtcPeer } from './WebRtcPeer';
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
const logger: OpenViduLogger = OpenViduLogger.getInstance();
|
||||
|
||||
export class WebRtcPeerLEGACY extends WebRtcPeer {
|
||||
|
||||
constructor(protected configuration: WebRtcPeerConfiguration) {
|
||||
super(configuration);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
*/
|
||||
generateOffer(): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
let offerAudio, offerVideo = true;
|
||||
|
||||
// Constraints must have both blocks
|
||||
if (!!this.configuration.mediaConstraints) {
|
||||
offerAudio = (typeof this.configuration.mediaConstraints.audio === 'boolean') ?
|
||||
this.configuration.mediaConstraints.audio : true;
|
||||
offerVideo = (typeof this.configuration.mediaConstraints.video === 'boolean') ?
|
||||
this.configuration.mediaConstraints.video : true;
|
||||
}
|
||||
|
||||
const constraints: RTCOfferOptions = {
|
||||
offerToReceiveAudio: (this.configuration.mode !== 'sendonly' && offerAudio),
|
||||
offerToReceiveVideo: (this.configuration.mode !== 'sendonly' && offerVideo)
|
||||
};
|
||||
|
||||
logger.debug('RTCPeerConnection constraints: ' + JSON.stringify(constraints));
|
||||
|
||||
if (platform.name === 'Safari' && platform.ua!!.indexOf('Safari') !== -1) {
|
||||
// Safari (excluding Ionic), at least on iOS just seems to support unified plan, whereas in other browsers is not yet ready and considered experimental
|
||||
if (offerAudio) {
|
||||
this.pc.addTransceiver('audio', {
|
||||
direction: this.configuration.mode,
|
||||
});
|
||||
}
|
||||
|
||||
if (offerVideo) {
|
||||
this.pc.addTransceiver('video', {
|
||||
direction: this.configuration.mode,
|
||||
});
|
||||
}
|
||||
|
||||
this.pc
|
||||
.createOffer()
|
||||
.then(offer => {
|
||||
logger.debug('Created SDP offer');
|
||||
return this.pc.setLocalDescription(offer);
|
||||
})
|
||||
.then(() => {
|
||||
const localDescription = this.pc.localDescription;
|
||||
|
||||
if (!!localDescription) {
|
||||
logger.debug('Local description set', localDescription.sdp);
|
||||
resolve(localDescription.sdp);
|
||||
} else {
|
||||
reject('Local description is not defined');
|
||||
}
|
||||
})
|
||||
.catch(error => reject(error));
|
||||
|
||||
} else {
|
||||
|
||||
// Rest of platforms
|
||||
this.pc.createOffer(constraints).then(offer => {
|
||||
logger.debug('Created SDP offer');
|
||||
return this.pc.setLocalDescription(offer);
|
||||
})
|
||||
.then(() => {
|
||||
const localDescription = this.pc.localDescription;
|
||||
if (!!localDescription) {
|
||||
logger.debug('Local description set', localDescription.sdp);
|
||||
resolve(localDescription.sdp);
|
||||
} else {
|
||||
reject('Local description is not defined');
|
||||
}
|
||||
})
|
||||
.catch(error => reject(error));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
processAnswer(sdpAnswer: string, needsTimeoutOnProcessAnswer: boolean): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const answer: RTCSessionDescriptionInit = {
|
||||
type: 'answer',
|
||||
sdp: sdpAnswer
|
||||
};
|
||||
logger.debug('SDP answer received, setting remote description');
|
||||
|
||||
if (this.pc.signalingState === 'closed') {
|
||||
reject('RTCPeerConnection is closed');
|
||||
}
|
||||
|
||||
this.setRemoteDescriptionLEGACY(answer, needsTimeoutOnProcessAnswer, resolve, reject);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
private setRemoteDescriptionLEGACY(answer: RTCSessionDescriptionInit, needsTimeoutOnProcessAnswer: boolean, resolve: (value?: string | PromiseLike<string> | 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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export class WebRtcPeerRecvonlyLEGACY extends WebRtcPeerLEGACY {
|
||||
constructor(configuration: WebRtcPeerConfiguration) {
|
||||
configuration.mode = 'recvonly';
|
||||
super(configuration);
|
||||
}
|
||||
}
|
||||
|
||||
export class WebRtcPeerSendonlyLEGACY extends WebRtcPeerLEGACY {
|
||||
constructor(configuration: WebRtcPeerConfiguration) {
|
||||
configuration.mode = 'sendonly';
|
||||
super(configuration);
|
||||
}
|
||||
}
|
||||
|
||||
export class WebRtcPeerSendrecvLEGACY extends WebRtcPeerLEGACY {
|
||||
constructor(configuration: WebRtcPeerConfiguration) {
|
||||
configuration.mode = 'sendrecv';
|
||||
super(configuration);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue