mirror of https://github.com/OpenVidu/openvidu.git
1098 lines
32 KiB
JavaScript
1098 lines
32 KiB
JavaScript
![]() |
/*
|
||
![]() |
* (C) Copyright 2016 OpenVidu (http://kurento.org/)
|
||
![]() |
*
|
||
|
* 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.
|
||
|
*
|
||
|
*/
|
||
|
// Room --------------------------------
|
||
|
|
||
|
function jq(myid) {
|
||
|
|
||
|
return "#" + myid.replace(/(@|:|\.|\[|\]|,)/g, "\\$1");
|
||
|
|
||
|
}
|
||
|
|
||
|
function Room(kurento, options) {
|
||
|
|
||
|
var that = this;
|
||
|
|
||
|
that.name = options.room;
|
||
|
|
||
|
var ee = new EventEmitter();
|
||
|
var streams = {};
|
||
|
var participants = {};
|
||
|
var participantsSpeaking = [];
|
||
|
var connected = false;
|
||
|
var localParticipant;
|
||
|
var subscribeToStreams = options.subscribeToStreams || true;
|
||
|
var updateSpeakerInterval = options.updateSpeakerInterval || 1500;
|
||
|
var thresholdSpeaker = options.thresholdSpeaker || -50;
|
||
|
|
||
|
setInterval(updateMainSpeaker, updateSpeakerInterval);
|
||
|
|
||
|
function updateMainSpeaker() {
|
||
|
if (participantsSpeaking.length > 0) {
|
||
|
ee.emitEvent('update-main-speaker', [{
|
||
|
participantId: participantsSpeaking[participantsSpeaking.length - 1]
|
||
|
}]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
this.getLocalParticipant = function () {
|
||
|
return localParticipant;
|
||
|
}
|
||
|
|
||
|
this.addEventListener = function (eventName, listener) {
|
||
|
ee.addListener(eventName, listener);
|
||
|
}
|
||
|
|
||
|
this.emitEvent = function (eventName, eventsArray) {
|
||
|
ee.emitEvent(eventName, eventsArray);
|
||
|
}
|
||
|
|
||
|
this.connect = function () {
|
||
|
var joinParams = {
|
||
|
user: options.user,
|
||
|
room: options.room
|
||
|
};
|
||
|
if (localParticipant) {
|
||
|
if (Object.keys(localParticipant.getStreams()).some(function (streamId) {
|
||
|
return streams[streamId].isDataChannelEnabled();
|
||
|
})) {
|
||
|
joinParams.dataChannels = true;
|
||
|
}
|
||
|
}
|
||
|
kurento.sendRequest('joinRoom', joinParams, function (error, response) {
|
||
|
if (error) {
|
||
|
console.warn('Unable to join room', error);
|
||
|
ee.emitEvent('error-room', [{
|
||
|
error: error
|
||
|
}]);
|
||
|
} else {
|
||
|
|
||
|
connected = true;
|
||
|
|
||
|
var exParticipants = response.value;
|
||
|
|
||
|
var roomEvent = {
|
||
|
participants: [],
|
||
|
streams: []
|
||
|
}
|
||
|
|
||
|
var length = exParticipants.length;
|
||
|
for (var i = 0; i < length; i++) {
|
||
|
|
||
|
var participant = new Participant(kurento, false, that,
|
||
|
exParticipants[i]);
|
||
|
|
||
|
participants[participant.getID()] = participant;
|
||
|
|
||
|
roomEvent.participants.push(participant);
|
||
|
|
||
|
var streams = participant.getStreams();
|
||
|
for (var key in streams) {
|
||
|
roomEvent.streams.push(streams[key]);
|
||
|
if (subscribeToStreams) {
|
||
|
streams[key].subscribe();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ee.emitEvent('room-connected', [roomEvent]);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
|
||
|
this.subscribe = function (stream) {
|
||
|
stream.subscribe();
|
||
|
}
|
||
|
|
||
|
this.onParticipantPublished = function (options) {
|
||
|
|
||
|
var participant = new Participant(kurento, false, that, options);
|
||
|
|
||
|
var pid = participant.getID();
|
||
|
if (!(pid in participants)) {
|
||
|
console.info("Publisher not found in participants list by its id", pid);
|
||
|
} else {
|
||
|
console.log("Publisher found in participants list by its id", pid);
|
||
|
}
|
||
|
//replacing old participant (this one has streams)
|
||
|
participants[pid] = participant;
|
||
|
|
||
|
ee.emitEvent('participant-published', [{
|
||
|
participant: participant
|
||
|
}]);
|
||
|
|
||
|
var streams = participant.getStreams();
|
||
|
for (var key in streams) {
|
||
|
var stream = streams[key];
|
||
|
|
||
|
if (subscribeToStreams) {
|
||
|
stream.subscribe();
|
||
|
ee.emitEvent('stream-added', [{
|
||
|
stream: stream
|
||
|
}]);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
this.onParticipantJoined = function (msg) {
|
||
|
var participant = new Participant(kurento, false, that, msg);
|
||
|
var pid = participant.getID();
|
||
|
if (!(pid in participants)) {
|
||
|
console.log("New participant to participants list with id", pid);
|
||
|
participants[pid] = participant;
|
||
|
} else {
|
||
|
//use existing so that we don't lose streams info
|
||
|
console.info("Participant already exists in participants list with " +
|
||
|
"the same id, old:", participants[pid], ", joined now:", participant);
|
||
|
participant = participants[pid];
|
||
|
}
|
||
|
|
||
|
ee.emitEvent('participant-joined', [{
|
||
|
participant: participant
|
||
|
}]);
|
||
|
}
|
||
|
|
||
|
this.onParticipantLeft = function (msg) {
|
||
|
|
||
|
var participant = participants[msg.name];
|
||
|
|
||
|
if (participant !== undefined) {
|
||
|
delete participants[msg.name];
|
||
|
|
||
|
ee.emitEvent('participant-left', [{
|
||
|
participant: participant
|
||
|
}]);
|
||
|
|
||
|
var streams = participant.getStreams();
|
||
|
for (var key in streams) {
|
||
|
ee.emitEvent('stream-removed', [{
|
||
|
stream: streams[key]
|
||
|
}]);
|
||
|
}
|
||
|
|
||
|
participant.dispose();
|
||
|
} else {
|
||
|
console.warn("Participant " + msg.name
|
||
|
+ " unknown. Participants: "
|
||
|
+ JSON.stringify(participants));
|
||
|
}
|
||
|
};
|
||
|
|
||
|
this.onParticipantEvicted = function (msg) {
|
||
|
ee.emitEvent('participant-evicted', [{
|
||
|
localParticipant: localParticipant
|
||
|
}]);
|
||
|
};
|
||
|
|
||
|
this.onNewMessage = function (msg) {
|
||
|
console.log("New message: " + JSON.stringify(msg));
|
||
|
var room = msg.room;
|
||
|
var user = msg.user;
|
||
|
var message = msg.message;
|
||
|
|
||
|
if (user !== undefined) {
|
||
|
ee.emitEvent('newMessage', [{
|
||
|
room: room,
|
||
|
user: user,
|
||
|
message: message
|
||
|
}]);
|
||
|
} else {
|
||
|
console.error("User undefined in new message:", msg);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
this.recvIceCandidate = function (msg) {
|
||
|
var candidate = {
|
||
|
candidate: msg.candidate,
|
||
|
sdpMid: msg.sdpMid,
|
||
|
sdpMLineIndex: msg.sdpMLineIndex
|
||
|
}
|
||
|
var participant = participants[msg.endpointName];
|
||
|
if (!participant) {
|
||
|
console.error("Participant not found for endpoint " +
|
||
|
msg.endpointName + ". Ice candidate will be ignored.",
|
||
|
candidate);
|
||
|
return false;
|
||
|
}
|
||
|
var streams = participant.getStreams();
|
||
|
for (var key in streams) {
|
||
|
var stream = streams[key];
|
||
|
stream.getWebRtcPeer().addIceCandidate(candidate, function (error) {
|
||
|
if (error) {
|
||
|
console.error("Error adding candidate for " + key
|
||
|
+ " stream of endpoint " + msg.endpointName
|
||
|
+ ": " + error);
|
||
|
return;
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
this.onRoomClosed = function (msg) {
|
||
|
console.log("Room closed: " + JSON.stringify(msg));
|
||
|
var room = msg.room;
|
||
|
if (room !== undefined) {
|
||
|
ee.emitEvent('room-closed', [{
|
||
|
room: room
|
||
|
}]);
|
||
|
} else {
|
||
|
console.error("Room undefined in on room closed", msg);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
this.onLostConnection = function () {
|
||
|
|
||
|
if (!connected) {
|
||
|
console.warn('Not connected to room, ignoring lost connection notification');
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
console.log('Lost connection in room ' + that.name);
|
||
|
var room = that.name;
|
||
|
if (room !== undefined) {
|
||
|
ee.emitEvent('lost-connection', [{
|
||
|
room: room
|
||
|
}]);
|
||
|
} else {
|
||
|
console.error('Room undefined when lost connection');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
this.onMediaError = function (params) {
|
||
|
console.error("Media error: " + JSON.stringify(params));
|
||
|
var error = params.error;
|
||
|
if (error) {
|
||
|
ee.emitEvent('error-media', [{
|
||
|
error: error
|
||
|
}]);
|
||
|
} else {
|
||
|
console.error("Received undefined media error. Params:", params);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* forced means the user was evicted, no need to send the 'leaveRoom' request
|
||
|
*/
|
||
|
this.leave = function (forced, jsonRpcClient) {
|
||
|
forced = !!forced;
|
||
|
console.log("Leaving room (forced=" + forced + ")");
|
||
|
|
||
|
if (connected && !forced) {
|
||
|
kurento.sendRequest('leaveRoom', function (error, response) {
|
||
|
if (error) {
|
||
|
console.error(error);
|
||
|
}
|
||
|
jsonRpcClient.close();
|
||
|
});
|
||
|
} else {
|
||
|
jsonRpcClient.close();
|
||
|
}
|
||
|
connected = false;
|
||
|
if (participants) {
|
||
|
for (var pid in participants) {
|
||
|
participants[pid].dispose();
|
||
|
delete participants[pid];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
this.disconnect = function (stream) {
|
||
|
var participant = stream.getParticipant();
|
||
|
if (!participant) {
|
||
|
console.error("Stream to disconnect has no participant", stream);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
delete participants[participant.getID()];
|
||
|
participant.dispose();
|
||
|
|
||
|
if (participant === localParticipant) {
|
||
|
console.log("Unpublishing my media (I'm " + participant.getID() + ")");
|
||
|
delete localParticipant;
|
||
|
kurento.sendRequest('unpublishVideo', function (error, response) {
|
||
|
if (error) {
|
||
|
console.error(error);
|
||
|
} else {
|
||
|
console.info("Media unpublished correctly");
|
||
|
}
|
||
|
});
|
||
|
} else {
|
||
|
console.log("Unsubscribing from " + stream.getGlobalID());
|
||
|
kurento.sendRequest('unsubscribeFromVideo', {
|
||
|
sender: stream.getGlobalID()
|
||
|
},
|
||
|
function (error, response) {
|
||
|
if (error) {
|
||
|
console.error(error);
|
||
|
} else {
|
||
|
console.info("Unsubscribed correctly from " + stream.getGlobalID());
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
this.getStreams = function () {
|
||
|
return streams;
|
||
|
}
|
||
|
|
||
|
this.addParticipantSpeaking = function (participantId) {
|
||
|
participantsSpeaking.push(participantId);
|
||
|
}
|
||
|
|
||
|
this.removeParticipantSpeaking = function (participantId) {
|
||
|
var pos = -1;
|
||
|
for (var i = 0; i < participantsSpeaking.length; i++) {
|
||
|
if (participantsSpeaking[i] == participantId) {
|
||
|
pos = i;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (pos != -1) {
|
||
|
participantsSpeaking.splice(pos, 1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
localParticipant = new Participant(kurento, true, that, {id: options.user});
|
||
|
participants[options.user] = localParticipant;
|
||
|
}
|
||
|
|
||
|
// Participant --------------------------------
|
||
|
|
||
|
function Participant(kurento, local, room, options) {
|
||
|
|
||
|
var that = this;
|
||
|
var id = options.id;
|
||
|
|
||
|
var streams = {};
|
||
|
var streamsOpts = [];
|
||
|
|
||
|
if (options.streams) {
|
||
|
for (var i = 0; i < options.streams.length; i++) {
|
||
|
var streamOpts = {
|
||
|
id: options.streams[i].id,
|
||
|
participant: that,
|
||
|
recvVideo: (options.streams[i].recvVideo == undefined ? true : options.streams[i].recvVideo),
|
||
|
recvAudio: (options.streams[i].recvAudio == undefined ? true : options.streams[i].recvAudio)
|
||
|
}
|
||
|
var stream = new Stream(kurento, false, room, streamOpts);
|
||
|
addStream(stream);
|
||
|
streamsOpts.push(streamOpts);
|
||
|
}
|
||
|
}
|
||
|
console.log("New " + (local ? "local " : "remote ") + "participant " + id
|
||
|
+ ", streams opts: ", streamsOpts);
|
||
|
|
||
|
that.setId = function (newId) {
|
||
|
id = newId;
|
||
|
}
|
||
|
|
||
|
function addStream(stream) {
|
||
|
streams[stream.getID()] = stream;
|
||
|
room.getStreams()[stream.getID()] = stream;
|
||
|
}
|
||
|
|
||
|
that.addStream = addStream;
|
||
|
|
||
|
that.getStreams = function () {
|
||
|
return streams;
|
||
|
}
|
||
|
|
||
|
that.dispose = function () {
|
||
|
for (var key in streams) {
|
||
|
streams[key].dispose();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
that.getID = function () {
|
||
|
return id;
|
||
|
}
|
||
|
|
||
|
this.sendIceCandidate = function (candidate) {
|
||
|
console.debug((local ? "Local" : "Remote"), "candidate for",
|
||
|
that.getID(), JSON.stringify(candidate));
|
||
|
kurento.sendRequest("onIceCandidate", {
|
||
|
endpointName: that.getID(),
|
||
|
candidate: candidate.candidate,
|
||
|
sdpMid: candidate.sdpMid,
|
||
|
sdpMLineIndex: candidate.sdpMLineIndex
|
||
|
}, function (error, response) {
|
||
|
if (error) {
|
||
|
console.error("Error sending ICE candidate: "
|
||
|
+ JSON.stringify(error));
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Stream --------------------------------
|
||
|
|
||
|
/*
|
||
|
* options: name: XXX data: true (Maybe this is based on webrtc) audio: true,
|
||
|
* video: true, url: "file:///..." > Player screen: true > Desktop (implicit
|
||
|
* video:true, audio:false) audio: true, video: true > Webcam
|
||
|
*
|
||
|
* stream.hasAudio(); stream.hasVideo(); stream.hasData();
|
||
|
*/
|
||
|
function Stream(kurento, local, room, options) {
|
||
|
|
||
|
var that = this;
|
||
|
|
||
|
that.room = room;
|
||
|
|
||
|
var ee = new EventEmitter();
|
||
|
var sdpOffer;
|
||
|
var wrStream;
|
||
|
var wp;
|
||
|
var id;
|
||
![]() |
|
||
![]() |
if (options.id) {
|
||
|
id = options.id;
|
||
|
} else {
|
||
|
id = "webcam";
|
||
|
}
|
||
|
var video;
|
||
|
|
||
|
var videoElements = [];
|
||
|
var elements = [];
|
||
|
var participant = options.participant;
|
||
|
|
||
|
var speechEvent;
|
||
|
|
||
|
var recvVideo = options.recvVideo;
|
||
|
this.getRecvVideo = function () {
|
||
|
return recvVideo;
|
||
|
}
|
||
|
|
||
|
var recvAudio = options.recvAudio;
|
||
|
this.getRecvAudio = function () {
|
||
|
return recvAudio;
|
||
|
}
|
||
|
|
||
|
var showMyRemote = false;
|
||
|
this.subscribeToMyRemote = function () {
|
||
|
showMyRemote = true;
|
||
|
}
|
||
|
this.displayMyRemote = function () {
|
||
|
return showMyRemote;
|
||
|
}
|
||
|
|
||
|
var localMirrored = false;
|
||
|
this.mirrorLocalStream = function (wr) {
|
||
|
showMyRemote = true;
|
||
|
localMirrored = true;
|
||
|
if (wr)
|
||
|
wrStream = wr;
|
||
|
}
|
||
|
this.isLocalMirrored = function () {
|
||
|
return localMirrored;
|
||
|
}
|
||
|
|
||
|
var chanId = 0;
|
||
|
|
||
|
function getChannelName() {
|
||
|
return that.getGlobalID() + '_' + chanId++;
|
||
|
}
|
||
|
|
||
|
var dataChannel = options.data || false;
|
||
|
this.isDataChannelEnabled = function () {
|
||
|
return dataChannel;
|
||
|
}
|
||
|
|
||
|
var dataChannelOpened = false;
|
||
|
this.isDataChannelOpened = function () {
|
||
|
return dataChannelOpened;
|
||
|
}
|
||
|
|
||
|
function onDataChannelOpen(event) {
|
||
|
console.log('Data channel is opened');
|
||
|
dataChannelOpened = true;
|
||
|
}
|
||
|
|
||
|
function onDataChannelClosed(event) {
|
||
|
console.log('Data channel is closed');
|
||
|
dataChannelOpened = false;
|
||
|
}
|
||
|
|
||
|
this.sendData = function (data) {
|
||
|
if (wp === undefined) {
|
||
|
throw new Error('WebRTC peer has not been created yet');
|
||
|
}
|
||
|
if (!dataChannelOpened) {
|
||
|
throw new Error('Data channel is not opened');
|
||
|
}
|
||
|
console.log("Sending through data channel: " + data);
|
||
|
wp.send(data);
|
||
|
}
|
||
|
|
||
|
this.getWrStream = function () {
|
||
|
return wrStream;
|
||
|
}
|
||
|
|
||
|
this.getWebRtcPeer = function () {
|
||
|
return wp;
|
||
|
}
|
||
|
|
||
|
this.addEventListener = function (eventName, listener) {
|
||
|
ee.addListener(eventName, listener);
|
||
|
}
|
||
|
|
||
|
function showSpinner(spinnerParentId) {
|
||
|
var progress = document.createElement('div');
|
||
|
progress.id = 'progress-' + that.getGlobalID();
|
||
|
progress.style.background = "center transparent url('img/spinner.gif') no-repeat";
|
||
|
document.getElementById(spinnerParentId).appendChild(progress);
|
||
|
}
|
||
|
|
||
|
function hideSpinner(spinnerId) {
|
||
|
spinnerId = (typeof spinnerId === 'undefined') ? that.getGlobalID() : spinnerId;
|
||
|
$(jq('progress-' + spinnerId)).remove();
|
||
|
}
|
||
|
|
||
|
this.playOnlyVideo = function (parentElement, thumbnailId) {
|
||
|
video = document.createElement('video');
|
||
|
|
||
|
video.id = 'native-video-' + that.getGlobalID();
|
||
|
video.autoplay = true;
|
||
|
video.controls = false;
|
||
|
if (wrStream) {
|
||
|
video.src = URL.createObjectURL(wrStream);
|
||
|
$(jq(thumbnailId)).show();
|
||
|
hideSpinner();
|
||
|
} else
|
||
|
console.log("No wrStream yet for", that.getGlobalID());
|
||
|
|
||
|
videoElements.push({
|
||
|
thumb: thumbnailId,
|
||
|
video: video
|
||
|
});
|
||
|
|
||
|
if (local) {
|
||
|
video.muted = true;
|
||
|
}
|
||
|
|
||
|
if (typeof parentElement === "string") {
|
||
|
document.getElementById(parentElement).appendChild(video);
|
||
|
} else {
|
||
|
parentElement.appendChild(video);
|
||
|
}
|
||
|
|
||
|
return video;
|
||
|
}
|
||
|
|
||
|
this.playThumbnail = function (thumbnailId) {
|
||
|
|
||
|
var container = document.createElement('div');
|
||
|
container.className = "participant";
|
||
|
container.id = that.getGlobalID();
|
||
|
document.getElementById(thumbnailId).appendChild(container);
|
||
|
|
||
|
elements.push(container);
|
||
|
|
||
|
var name = document.createElement('div');
|
||
|
container.appendChild(name);
|
||
|
var userName = that.getGlobalID().replace('_webcam', '');
|
||
|
if (userName.length >= 16) {
|
||
|
userName = userName.substring(0, 16) + "...";
|
||
|
}
|
||
|
name.appendChild(document.createTextNode(userName));
|
||
|
name.id = "name-" + that.getGlobalID();
|
||
|
name.className = "name";
|
||
|
name.title = that.getGlobalID();
|
||
|
|
||
|
showSpinner(thumbnailId);
|
||
|
|
||
|
return that.playOnlyVideo(container, thumbnailId);
|
||
|
}
|
||
|
|
||
|
this.getID = function () {
|
||
|
return id;
|
||
|
}
|
||
|
|
||
|
this.getParticipant = function () {
|
||
|
return participant;
|
||
|
}
|
||
|
|
||
|
this.getGlobalID = function () {
|
||
|
if (participant) {
|
||
|
return participant.getID() + "_" + id;
|
||
|
} else {
|
||
|
return id + "_webcam";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
this.init = function () {
|
||
|
participant.addStream(that);
|
||
|
|
||
|
var constraints = {
|
||
|
audio: true,
|
||
|
video: {
|
||
|
width: {
|
||
|
ideal: 1280
|
||
|
},
|
||
|
frameRate: {
|
||
|
ideal: 15
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
getUserMedia(constraints, function (userStream) {
|
||
|
wrStream = userStream;
|
||
|
ee.emitEvent('access-accepted', null);
|
||
|
}, function (error) {
|
||
|
console.error("Access denied", error);
|
||
|
ee.emitEvent('access-denied', null);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
this.publishVideoCallback = function (error, sdpOfferParam, wp) {
|
||
|
if (error) {
|
||
|
return console.error("(publish) SDP offer error: "
|
||
|
+ JSON.stringify(error));
|
||
|
}
|
||
|
console.log("Sending SDP offer to publish as "
|
||
|
+ that.getGlobalID(), sdpOfferParam);
|
||
|
kurento.sendRequest("publishVideo", {
|
||
|
sdpOffer: sdpOfferParam,
|
||
|
doLoopback: that.displayMyRemote() || false
|
||
|
}, function (error, response) {
|
||
|
if (error) {
|
||
|
console.error("Error on publishVideo: " + JSON.stringify(error));
|
||
|
} else {
|
||
|
that.room.emitEvent('stream-published', [{
|
||
|
stream: that
|
||
|
}])
|
||
|
that.processSdpAnswer(response.sdpAnswer);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
this.startVideoCallback = function (error, sdpOfferParam, wp) {
|
||
|
if (error) {
|
||
|
return console.error("(subscribe) SDP offer error: "
|
||
|
+ JSON.stringify(error));
|
||
|
}
|
||
|
console.log("Sending SDP offer to subscribe to "
|
||
|
+ that.getGlobalID(), sdpOfferParam);
|
||
|
kurento.sendRequest("receiveVideoFrom", {
|
||
|
sender: that.getGlobalID(),
|
||
|
sdpOffer: sdpOfferParam
|
||
|
}, function (error, response) {
|
||
|
if (error) {
|
||
|
console.error("Error on recvVideoFrom: " + JSON.stringify(error));
|
||
|
} else {
|
||
|
that.processSdpAnswer(response.sdpAnswer);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
function initWebRtcPeer(sdpOfferCallback) {
|
||
|
if (local) {
|
||
|
var options = {
|
||
|
videoStream: wrStream,
|
||
|
onicecandidate: participant.sendIceCandidate.bind(participant),
|
||
|
}
|
||
|
if (dataChannel) {
|
||
|
options.dataChannelConfig = {
|
||
|
id: getChannelName(),
|
||
|
onopen: onDataChannelOpen,
|
||
|
onclose: onDataChannelClosed
|
||
|
};
|
||
|
options.dataChannels = true;
|
||
|
}
|
||
|
if (that.displayMyRemote()) {
|
||
![]() |
this.wp = new kurentoUtils.WebRtcPeer.WebRtcPeerSendrecv(options, function (error) {
|
||
![]() |
if (error) {
|
||
|
return console.error(error);
|
||
|
}
|
||
![]() |
this.wp.generateOffer(sdpOfferCallback.bind(that));
|
||
![]() |
});
|
||
|
} else {
|
||
![]() |
this.wp = new kurentoUtils.WebRtcPeer.WebRtcPeerSendonly(options, function (error) {
|
||
![]() |
if (error) {
|
||
|
return console.error(error);
|
||
|
}
|
||
![]() |
this.wp.generateOffer(sdpOfferCallback.bind(that));
|
||
![]() |
});
|
||
|
}
|
||
|
} else {
|
||
|
var offerConstraints = {
|
||
|
mandatory: {
|
||
|
OfferToReceiveVideo: recvVideo,
|
||
|
OfferToReceiveAudio: recvAudio
|
||
|
}
|
||
|
};
|
||
|
console.log("Constraints of generate SDP offer (subscribing)",
|
||
|
offerConstraints);
|
||
|
var options = {
|
||
|
onicecandidate: participant.sendIceCandidate.bind(participant),
|
||
|
connectionConstraints: offerConstraints
|
||
|
}
|
||
|
wp = new kurentoUtils.WebRtcPeer.WebRtcPeerRecvonly(options, function (error) {
|
||
|
if (error) {
|
||
|
return console.error(error);
|
||
|
}
|
||
|
this.generateOffer(sdpOfferCallback.bind(that));
|
||
|
});
|
||
|
}
|
||
|
console.log("Waiting for SDP offer to be generated ("
|
||
|
+ (local ? "local" : "remote") + " peer: " + that.getGlobalID() + ")");
|
||
|
}
|
||
|
|
||
|
this.publish = function () {
|
||
|
|
||
|
// FIXME: Throw error when stream is not local
|
||
|
|
||
|
initWebRtcPeer(that.publishVideoCallback);
|
||
|
|
||
|
// FIXME: Now we have coupled connecting to a room and adding a
|
||
|
// stream to this room. But in the new API, there are two steps.
|
||
|
// This is the second step. For now, it do nothing.
|
||
|
|
||
|
}
|
||
|
|
||
|
this.subscribe = function () {
|
||
|
|
||
|
// FIXME: In the current implementation all participants are subscribed
|
||
|
// automatically to all other participants. We use this method only to
|
||
|
// negotiate SDP
|
||
|
|
||
|
initWebRtcPeer(that.startVideoCallback);
|
||
|
}
|
||
|
|
||
|
this.processSdpAnswer = function (sdpAnswer) {
|
||
|
var answer = new RTCSessionDescription({
|
||
|
type: 'answer',
|
||
|
sdp: sdpAnswer,
|
||
|
});
|
||
|
console.log(that.getGlobalID() + ": set peer connection with recvd SDP answer",
|
||
|
sdpAnswer);
|
||
|
var participantId = that.getGlobalID();
|
||
|
var pc = wp.peerConnection;
|
||
|
pc.setRemoteDescription(answer, function () {
|
||
|
// Avoids to subscribe to your own stream remotely
|
||
|
// except when showMyRemote is true
|
||
|
if (!local || that.displayMyRemote()) {
|
||
|
wrStream = pc.getRemoteStreams()[0];
|
||
|
console.log("Peer remote stream", wrStream);
|
||
|
if (wrStream != undefined) {
|
||
|
speechEvent = kurentoUtils.WebRtcPeer.hark(wrStream, {threshold: that.room.thresholdSpeaker});
|
||
|
speechEvent.on('speaking', function () {
|
||
|
that.room.addParticipantSpeaking(participantId);
|
||
|
that.room.emitEvent('stream-speaking', [{
|
||
|
participantId: participantId
|
||
|
}]);
|
||
|
});
|
||
|
speechEvent.on('stopped_speaking', function () {
|
||
|
that.room.removeParticipantSpeaking(participantId);
|
||
|
that.room.emitEvent('stream-stopped-speaking', [{
|
||
|
participantId: participantId
|
||
|
}]);
|
||
|
});
|
||
|
}
|
||
|
for (i = 0; i < videoElements.length; i++) {
|
||
|
var thumbnailId = videoElements[i].thumb;
|
||
|
var video = videoElements[i].video;
|
||
|
video.src = URL.createObjectURL(wrStream);
|
||
|
video.onplay = function () {
|
||
|
console.log(that.getGlobalID() + ': ' + 'Video playing');
|
||
|
$(jq(thumbnailId)).show();
|
||
|
hideSpinner(that.getGlobalID());
|
||
|
};
|
||
|
}
|
||
|
that.room.emitEvent('stream-subscribed', [{
|
||
|
stream: that
|
||
|
}]);
|
||
|
}
|
||
|
}, function (error) {
|
||
|
console.error(that.getGlobalID() + ": Error setting SDP to the peer connection: "
|
||
|
+ JSON.stringify(error));
|
||
|
});
|
||
|
}
|
||
|
|
||
|
this.unpublish = function () {
|
||
|
if (wp) {
|
||
|
wp.dispose();
|
||
|
} else {
|
||
|
if (wrStream) {
|
||
|
wrStream.getAudioTracks().forEach(function (track) {
|
||
|
track.stop && track.stop()
|
||
|
})
|
||
|
wrStream.getVideoTracks().forEach(function (track) {
|
||
|
track.stop && track.stop()
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (speechEvent) {
|
||
|
speechEvent.stop();
|
||
|
}
|
||
|
|
||
|
console.log(that.getGlobalID() + ": Stream '" + id + "' unpublished");
|
||
|
}
|
||
|
|
||
|
this.dispose = function () {
|
||
|
|
||
|
function disposeElement(element) {
|
||
|
if (element && element.parentNode) {
|
||
|
element.parentNode.removeChild(element);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for (i = 0; i < elements.length; i++) {
|
||
|
disposeElement(elements[i]);
|
||
|
}
|
||
|
|
||
|
for (i = 0; i < videoElements.length; i++) {
|
||
|
disposeElement(videoElements[i].video);
|
||
|
}
|
||
|
|
||
|
disposeElement("progress-" + that.getGlobalID());
|
||
|
|
||
|
if (wp) {
|
||
|
wp.dispose();
|
||
|
} else {
|
||
|
if (wrStream) {
|
||
|
wrStream.getAudioTracks().forEach(function (track) {
|
||
|
track.stop && track.stop()
|
||
|
})
|
||
|
wrStream.getVideoTracks().forEach(function (track) {
|
||
|
track.stop && track.stop()
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (speechEvent) {
|
||
|
speechEvent.stop();
|
||
|
}
|
||
|
|
||
|
console.log(that.getGlobalID() + ": Stream '" + id + "' disposed");
|
||
|
}
|
||
|
}
|
||
|
|
||
![]() |
// OpenVidu --------------------------------
|
||
![]() |
|
||
![]() |
function OpenVidu(wsUri, callback) {
|
||
|
if (!(this instanceof OpenVidu))
|
||
|
return new OpenVidu(wsUri, callback);
|
||
![]() |
|
||
|
var that = this;
|
||
|
|
||
|
var room;
|
||
|
|
||
|
var userName;
|
||
|
|
||
|
var jsonRpcClient;
|
||
|
|
||
|
function initJsonRpcClient() {
|
||
|
|
||
|
var config = {
|
||
|
heartbeat: 3000,
|
||
|
sendCloseMessage: false,
|
||
|
ws: {
|
||
|
uri: wsUri,
|
||
|
useSockJS: false,
|
||
|
onconnected: connectCallback,
|
||
|
ondisconnect: disconnectCallback,
|
||
|
onreconnecting: reconnectingCallback,
|
||
|
onreconnected: reconnectedCallback
|
||
|
},
|
||
|
rpc: {
|
||
|
requestTimeout: 15000,
|
||
|
//notifications
|
||
|
participantJoined: onParticipantJoined,
|
||
|
participantPublished: onParticipantPublished,
|
||
|
participantUnpublished: onParticipantLeft,
|
||
|
participantLeft: onParticipantLeft,
|
||
|
participantEvicted: onParticipantEvicted,
|
||
|
sendMessage: onNewMessage,
|
||
|
iceCandidate: iceCandidateEvent,
|
||
|
mediaError: onMediaError,
|
||
|
custonNotification: customNotification
|
||
|
}
|
||
|
};
|
||
|
|
||
|
jsonRpcClient = new RpcBuilder.clients.JsonRpcClient(config);
|
||
|
}
|
||
|
|
||
|
function customNotification(params) {
|
||
|
if (isRoomAvailable()) {
|
||
|
room.emitEvent("custom-message-received", [{params: params}]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function connectCallback(error) {
|
||
|
if (error) {
|
||
|
callback(error);
|
||
|
} else {
|
||
|
callback(null, that);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function isRoomAvailable() {
|
||
|
if (room !== undefined && room instanceof Room) {
|
||
|
return true;
|
||
|
} else {
|
||
|
console.warn('Room instance not found');
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function disconnectCallback() {
|
||
|
console.log('Websocket connection lost');
|
||
|
if (isRoomAvailable()) {
|
||
|
room.onLostConnection();
|
||
|
} else {
|
||
|
alert('Connection error. Please reload page.');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function reconnectingCallback() {
|
||
|
console.log('Websocket connection lost (reconnecting)');
|
||
|
if (isRoomAvailable()) {
|
||
|
room.onLostConnection();
|
||
|
} else {
|
||
|
alert('Connection error. Please reload page.');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function reconnectedCallback() {
|
||
|
console.log('Websocket reconnected');
|
||
|
}
|
||
|
|
||
|
function onParticipantJoined(params) {
|
||
|
if (isRoomAvailable()) {
|
||
|
room.onParticipantJoined(params);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function onParticipantPublished(params) {
|
||
|
if (isRoomAvailable()) {
|
||
|
room.onParticipantPublished(params);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function onParticipantLeft(params) {
|
||
|
if (isRoomAvailable()) {
|
||
|
room.onParticipantLeft(params);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function onParticipantEvicted(params) {
|
||
|
if (isRoomAvailable()) {
|
||
|
room.onParticipantEvicted(params);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function onNewMessage(params) {
|
||
|
if (isRoomAvailable()) {
|
||
|
room.onNewMessage(params);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function iceCandidateEvent(params) {
|
||
|
if (isRoomAvailable()) {
|
||
|
room.recvIceCandidate(params);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function onRoomClosed(params) {
|
||
|
if (isRoomAvailable()) {
|
||
|
room.onRoomClosed(params);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function onMediaError(params) {
|
||
|
if (isRoomAvailable()) {
|
||
|
room.onMediaError(params);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var rpcParams;
|
||
|
|
||
|
this.setRpcParams = function (params) {
|
||
|
rpcParams = params;
|
||
|
}
|
||
|
|
||
|
this.sendRequest = function (method, params, callback) {
|
||
|
if (params && params instanceof Function) {
|
||
|
callback = params;
|
||
|
params = undefined;
|
||
|
}
|
||
|
params = params || {};
|
||
|
|
||
|
if (rpcParams && rpcParams !== "null" && rpcParams !== "undefined") {
|
||
|
for (var index in rpcParams) {
|
||
|
if (rpcParams.hasOwnProperty(index)) {
|
||
|
params[index] = rpcParams[index];
|
||
|
console.log('RPC param added to request {' + index + ': ' + rpcParams[index] + '}');
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
console.log('Sending request: { method:"' + method + '", params: ' + JSON.stringify(params) + ' }');
|
||
|
jsonRpcClient.send(method, params, callback);
|
||
|
};
|
||
|
|
||
|
this.close = function (forced) {
|
||
|
if (isRoomAvailable()) {
|
||
|
room.leave(forced, jsonRpcClient);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
this.disconnectParticipant = function (stream) {
|
||
|
if (isRoomAvailable()) {
|
||
|
room.disconnect(stream);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
this.Stream = function (room, options) {
|
||
![]() |
|
||
|
options = options || {
|
||
|
audio : true,
|
||
|
video : true,
|
||
|
data : true
|
||
|
}
|
||
|
|
||
![]() |
options.participant = room.getLocalParticipant();
|
||
|
return new Stream(that, true, room, options);
|
||
|
};
|
||
|
|
||
|
this.Room = function (options) {
|
||
|
room = new Room(that, options);
|
||
|
return room;
|
||
|
};
|
||
|
|
||
|
//CHAT
|
||
|
this.sendMessage = function (room, user, message) {
|
||
|
this.sendRequest('sendMessage', {
|
||
|
message: message,
|
||
|
userMessage: user,
|
||
|
roomMessage: room
|
||
|
}, function (error, response) {
|
||
|
if (error) {
|
||
|
console.error(error);
|
||
|
}
|
||
|
});
|
||
|
};
|
||
|
|
||
|
this.sendCustomRequest = function (params, callback) {
|
||
|
this.sendRequest('customRequest', params, callback);
|
||
|
};
|
||
|
|
||
|
initJsonRpcClient();
|
||
|
|
||
|
}
|