openvidu-browser refactoring: only one simple version exposed

pull/20/head
pabloFuente 2017-05-10 10:55:31 +02:00
parent 1a3eec6a48
commit af3be37d4c
26 changed files with 3284 additions and 20022 deletions

View File

@ -155,13 +155,7 @@ We recommend testing different scenarios to find the best approach for your part
### Client: Non-secure OpenVidu ###
> _openvidu-browser_ can be used in two different ways: by importing
> **OpenVidu** or **OpenViduTokBox** . OpenViduTokBox is being built to be compatible with
> [TokBox](https://tokbox.com) platform. It provides the highest level
> of abstraction in the use of the client side, so if you just want an
> easy way to get started it is the best approach, and it is the strategy that will be explained in the sections below.
For plain JavaScript, include this file ([OpenViduTokBox.js](https://github.com/OpenVidu/openvidu/blob/master/openvidu-browser/src/main/resources/static/js/OpenViduTokBox.js)) in your frontend app.
For plain JavaScript, include this file ([OpenVidu.js](https://github.com/OpenVidu/openvidu/blob/master/openvidu-browser/src/main/resources/static/js/OpenVidu.js)) in your frontend app.
We recommend trying [this sample app](#basic-plain-javascript-app).
For npm projects, you have an [openvidu-browser](https://www.npmjs.com/package/openvidu-browser) package ready to be added to your _package.json_.
@ -169,10 +163,10 @@ We recommend trying [this sample Angular app](#basic-angular-app).
#### ***Step by step*** ####
1. Get an *OpenViduTokBox* object and initialize a session with a *sessionId*. Remember this is the field that defines which video call to connect.
1. Get an *OpenVidu* object and initialize a session with a *sessionId*. Remember this is the field that defines which video call to connect.
```javascript
var OV = new OpenViduTokBox("wss://" + OPENVIDU_SERVER_IP + ":8443/");
var OV = new OpenVidu("wss://" + OPENVIDU_SERVER_IP + ":8443/");
var session = OV.initSession(sessionId);
```
2. Set the events to be listened by your session. For example, this snippet below will automatically append the new participants videos to HTML element with 'subscriber' id. Available events are detailed in [API section](#api-reference).
@ -186,7 +180,7 @@ We recommend trying [this sample Angular app](#basic-angular-app).
});
});
```
3. Connect to the session. For a non-secure approach, the value of *token* parameter is irrelevant. You can pass as second parameter a callback to be executed after connection is stablished. A common use-case for users that want to stream their own video is the following one: if the connection to the session has been succesful, get a PublisherTokBox object (appended to HTML element with id 'publisher') and publish it. The rest of participants will receive the stream.
3. Connect to the session. For a non-secure approach, the value of *token* parameter is irrelevant. You can pass as second parameter a callback to be executed after connection is stablished. A common use-case for users that want to stream their own video is the following one: if the connection to the session has been succesful, get a Publisher object (appended to HTML element with id 'publisher') and publish it. The rest of participants will receive the stream.
```javascript
session.connect(token, function (error) {
@ -210,7 +204,7 @@ We recommend trying [this sample Angular app](#basic-angular-app).
### Client: Secure OpenVidu ###
Your fronted will have to include plain JavaScript or openvidu-browser dependency just as in the non-secure architecture.
And here is the good part: there's really no difference between a secure and a non-secure client. You just need to get a valid **sessionId** and a valid **token** from your backend to pass as parameters in `OpenViduTokBox.initSession(sessionId)` and `SessionTokBox.connect(token, callback)` methods. And it is here where _openvidu-backend-client_ comes into play.
And here is the good part: there's really no difference between a secure and a non-secure client. You just need to get a valid **sessionId** and a valid **token** from your backend to pass as parameters in `OpenVidu.initSession(sessionId)` and `Session.connect(token, callback)` methods. And it is here where _openvidu-backend-client_ comes into play.
Your backend will need _openvidu-backend-client_ dependency. Easy maven integration is provided by the following dependency:
@ -254,44 +248,44 @@ API reference
| Class | Description |
| --------- | ---------------------------------------------------------- |
| [OpenViduTokBox](#openvidutokbox) | Use it to initialize your sessions and publishers |
| [SessionTokBox](#sessiontokbox) | Represents a video call. It can also be seen as a room where multiple users can connect. Participants who publish their videos to a session will be seen by the rest of users connected to that specific session |
| [PublisherTokBox](#publishertokbox) | Packs local media streams. Users can publish it to a session |
| [SubscriberTokBox](#subscribertokbox) | Packs remote media streams. Users automatically receive them when others publish their streams|
| [Stream](#stream) | Represents each of the videos send and receive by a user in a session. Therefore each PublisherTokBox and SubscriberTokBox has an attribute of type Stream |
| [OpenVidu](#openvidu) | Use it to initialize your sessions and publishers |
| [Session](#session) | Represents a video call. It can also be seen as a room where multiple users can connect. Participants who publish their videos to a session will be seen by the rest of users connected to that specific session |
| [Publisher](#publisher) | Packs local media streams. Users can publish it to a session |
| [Subscriber](#subscriber) | Packs remote media streams. Users automatically receive them when others publish their streams|
| [Stream](#stream) | Represents each of the videos send and receive by a user in a session. Therefore each Publisher and Subscriber has an attribute of type Stream |
#### **OpenViduTokBox**
#### **OpenVidu**
| Method | Returns | Parameters (show in order, optional italic) | Description |
| ---------------- | ------- | ------------------------------------------- | ----------- |
| `initSession` | [SessionTokBox](#sessiontokbox) | _`apikey:string`_<br/>`sessionId:string` | Returns a session with id **sessionId** |
| `initPublisher` | [PublisherTokBox](#publishertokbox) | `parentId:string`<br/>`cameraOptions:any`<br/>_`callback:function`_ | Starts local video stream, appending it to **parentId** HTML element, with the specific **cameraOptions** settings and executing **callback** function in the end |
| `initSession` | [Session](#session) | _`apikey:string`_<br/>`sessionId:string` | Returns a session with id **sessionId** |
| `initPublisher` | [Publisher](#publisher) | `parentId:string`<br/>`cameraOptions:any`<br/>_`callback:function`_ | Starts local video stream, appending it to **parentId** HTML element, with the specific **cameraOptions** settings and executing **callback** function in the end |
| `checkSystemRequirements` | Number | | Returns 1 if the browser supports WebRTC, 0 otherwise|
| `getDevices` | Promise | `callback(error, deviceInfo):function` | Collects information about the media input and output devices available on the system, returned in **deviceInfo** array |
#### **SessionTokBox**
#### **Session**
| Method | Returns | Parameters (show in order, optional italic) | Description |
| ---------------- | ------- | ------------------------------------------- | ----------- |
| `connect` | | `token:string`<br/>`callback(error):function` | Connects to the session using **token** and executes **callback** in the end (_error_ parameter null if success)|
| `disconnect` | | | Leaves the session, destroying all streams and deleting the user as a participant |
| `publish` | | `publisher:PublisherTokBox` | Publishes the specific user's local stream contained in PublisherTokBox object to the session |
| `unpublish` | | `publisher:PublisherTokBox` | Unpublishes the specific user's local stream contained in PublisherTokBox object |
| `publish` | | `publisher:Publisher` | Publishes the specific user's local stream contained in Publisher object to the session |
| `unpublish` | | `publisher:Publisher` | Unpublishes the specific user's local stream contained in Publisher object |
| `on` | | `eventName:string`<br/>`callback:function` | **callback** function will be triggered each time **eventName** event is recieved |
| `once` | | `eventName:string`<br/>`callback:function` | **callback** function will be triggered once when **eventName** event is recieved. The listener is removed immediately |
| `off` | | `eventName:string`<br/>`eventHandler:any` | Removes **eventHandler** handler for **eventName** event |
| `subscribe` | [SubscriberTokBox](#subscribertokbox) | `stream:Stream`<br/>`htmlId:string`<br/>_`videoOptions:any`_ | Subscribes to **stream**, appending a new HTML Video element to DOM element of **htmlId** id, with **videoOptions** settings. This method is usually called in the callback of _streamCreated_ event |
| `unsubscribe` | | `subscriber:SubscriberTokBox` | Unsubscribes from **subscriber**, automatically removing its HTML Video element |
| `subscribe` | [Subscriber](#subscriber) | `stream:Stream`<br/>`htmlId:string`<br/>_`videoOptions:any`_ | Subscribes to **stream**, appending a new HTML Video element to DOM element of **htmlId** id, with **videoOptions** settings. This method is usually called in the callback of _streamCreated_ event |
| `unsubscribe` | | `subscriber:Subscriber` | Unsubscribes from **subscriber**, automatically removing its HTML Video element |
| Property | Type | Description |
| ------------| ------ | ---------------------------- |
| `sessionId` | string | The unique id of the session |
#### **PublisherTokBox**
#### **Publisher**
| Method | Returns | Parameters (show in order, optional italic) | Description |
| -------------- | ------- | ------------------------------------------- | ----------- |
| `publishAudio` | | `value:boolean`| Enable or disable the audio track depending on whether value is _true_ or _false_ |
| `publishVideo` | | `value:boolean`| Enable or disable the video track depending on whether value is _true_ or _false_ |
| `destroy` | [PublisherTokBox](#publishertokbox) || Delets the publisher object and removes it from DOM. The rest of users will trigger a _streamDestroyed_ event |
| `destroy` | [Publisher](#publisher) || Delets the publisher object and removes it from DOM. The rest of users will trigger a _streamDestroyed_ event |
| Property | Type | Description |
| ------------| ------ | ---------------------------- |
@ -299,9 +293,9 @@ API reference
| `element` | Element | The parent HTML Element which contains the publisher |
| `id` | string | The id of the HTML Video element of the publisher |
| `stream` | Stream | The stream object of the publisher |
| `session` | [SessionTokBox](#sessiontokbox) | The session to which the publisher belongs |
| `session` | [Session](#session) | The session to which the publisher belongs |
#### **SubscriberTokBox**
#### **Subscriber**
| Method | Returns | Parameters (show in order, optional italic) | Description |
| -------------- | ------- | ------------------------------------------- | ----------- |
| | | | |

View File

@ -2,11 +2,10 @@
"name": "openvidu-browser",
"version": "0.2.0",
"description": "OpenVidu Browser",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"main": "lib/OpenVidu/index.js",
"types": "lib/OpenVidu/index.d.ts",
"scripts": {
"browserify1": "cd ts/OpenVidu && browserify Main.ts -p [ tsify ] --exclude kurento-browser-extensions --debug -o ../../static/js/OpenVidu.js -v",
"browserify2": "cd ts/OpenViduTokBox && browserify Main.ts -p [ tsify ] --exclude kurento-browser-extensions --debug -o ../../static/js/OpenViduTokBox.js -v",
"browserify": "cd ts/OpenVidu && browserify Main.ts -p [ tsify ] --exclude kurento-browser-extensions --debug -o ../../static/js/OpenVidu.js -v",
"test": "echo \"Error: no test specified\" && exit 1",
"prepublish": "cd ts && tsc",
"developing": "cd ts && tsc -w"

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -14,344 +14,84 @@
* limitations under the License.
*
*/
import { Session, SessionOptions } from './Session';
import { Stream } from './Stream';
import * as RpcBuilder from 'kurento-jsonrpc';
import { OpenViduInternal } from '../OpenViduInternal/OpenViduInternal';
export type Callback<T> = (error?: any, openVidu?: T) => void;
import { Session } from './Session';
import { Publisher } from './Publisher';
import * as adapter from 'webrtc-adapter';
if (window) {
window["adapter"] = adapter;
}
export class OpenVidu {
private session: Session;
private jsonRpcClient: any;
private rpcParams: any;
private callback: Callback<OpenVidu>;
private camera: Stream;
private remoteStreams: Stream[] = [];
openVidu: OpenViduInternal;
constructor(private wsUri: string) {
if (this.wsUri.charAt(wsUri.length - 1) != '/') {
this.wsUri += '/';
}
this.wsUri += 'room';
this.openVidu = new OpenViduInternal(wsUri);
}
initSession(apiKey: string, sessionId: string): Session;
initSession(sessionId: string): Session;
/* NEW METHODS */
initSession(sessionId) {
console.log("Session initialized!");
this.session = new Session(this, sessionId);
return this.session;
}
initPublisherTagged(parentId: string, cameraOptions: any, callback?) {
console.log("Publisher tagged initialized!");
this.getCamera(cameraOptions);
if (callback == null) {
this.camera.requestCameraAccess((error, camera) => {
if (error) {
console.log("Error accessing the camera");
}
else {
this.camera.setVideoElement(this.cameraReady(camera!, parentId));
}
});
return this.camera;
initSession(param1, param2?): any {
if (this.checkSystemRequirements()){
if (typeof param2 == "string") {
return new Session(this.openVidu.initSession(param2), this);
} else {
return new Session(this.openVidu.initSession(param1), this);
}
} else {
this.camera.requestCameraAccess((error, camera) => {
if (error) {
callback(error);
}
else {
this.camera.setVideoElement(this.cameraReady(camera!, parentId));
callback(undefined);
}
});
return this.camera;
alert("Browser not supported");
}
}
cameraReady(camera: Stream, parentId: string) {
this.camera = camera;
let videoElement = this.camera.playOnlyVideo(parentId, null);
this.camera.emitStreamReadyEvent();
return videoElement;
initPublisher(parentId: string, cameraOptions: any): Publisher;
initPublisher(parentId: string, cameraOptions: any, callback: any): Publisher;
initPublisher(parentId: string, cameraOptions: any, callback?): any {
if (this.checkSystemRequirements()){
if (!("audio" in cameraOptions && "data" in cameraOptions && "mediaConstraints" in cameraOptions &&
"video" in cameraOptions && (Object.keys(cameraOptions).length === 4))) {
cameraOptions = {
audio: cameraOptions.audio != null ? cameraOptions.audio : true,
video: cameraOptions.video != null ? cameraOptions.video : true,
data: true,
mediaConstraints: {
audio: true,
video: { width: { ideal: 1280 } }
}
}
}
return new Publisher(this.openVidu.initPublisherTagged(parentId, cameraOptions, callback), parentId);
} else {
alert("Browser not supported");
}
}
initPublisher(cameraOptions: any, callback) {
console.log("Publisher initialized!");
checkSystemRequirements(): number {
let browser = adapter.browserDetails.browser;
let version = adapter.browserDetails.version;
this.getCamera(cameraOptions);
this.camera.requestCameraAccess((error, camera) => {
if (error) callback(error);
else callback(undefined);
//Bug fix: 'navigator.userAgent' in Firefox for Ubuntu 14.04 does not return "Firefox/[version]" in the string, so version returned is null
if ((browser == 'firefox') && (version == null)) {
return 1;
}
if (((browser == 'chrome') && (version >= 28)) || ((browser == 'edge') && (version >= 12)) || ((browser == 'firefox') && (version >= 22))) {
return 1;
} else {
return 0;
}
}
getDevices(callback) {
navigator.mediaDevices.enumerateDevices().then((deviceInfos) => {
callback(null, deviceInfos);
}).catch((error) => {
console.log("Error getting devices: " + error);
callback(error, null);
});
}
getLocalStream() {
return this.camera;
}
getRemoteStreams() {
return this.remoteStreams;
}
/* NEW METHODS */
getRoom() {
return this.session;
}
connect(callback: Callback<OpenVidu>): void {
this.callback = callback;
this.initJsonRpcClient(this.wsUri);
}
private initJsonRpcClient(wsUri: string): void {
let config = {
heartbeat: 3000,
sendCloseMessage: false,
ws: {
uri: wsUri,
useSockJS: false,
onconnected: this.connectCallback.bind(this),
ondisconnect: this.disconnectCallback.bind(this),
onreconnecting: this.reconnectingCallback.bind(this),
onreconnected: this.reconnectedCallback.bind(this)
},
rpc: {
requestTimeout: 15000,
//notifications
participantJoined: this.onParticipantJoined.bind(this),
participantPublished: this.onParticipantPublished.bind(this),
participantUnpublished: this.onParticipantLeft.bind(this),
participantLeft: this.onParticipantLeft.bind(this),
participantEvicted: this.onParticipantEvicted.bind(this),
sendMessage: this.onNewMessage.bind(this),
iceCandidate: this.iceCandidateEvent.bind(this),
mediaError: this.onMediaError.bind(this),
custonNotification: this.customNotification.bind(this)
}
};
this.jsonRpcClient = new RpcBuilder.clients.JsonRpcClient(config);
}
private customNotification(params) {
if (this.isRoomAvailable()) {
this.session.emitEvent("custom-message-received", [{ params: params }]);
}
}
private connectCallback(error) {
if (error) {
this.callback(error);
} else {
this.callback(null);
}
}
private isRoomAvailable() {
if (this.session !== undefined && this.session instanceof Session) {
return true;
} else {
console.warn('Room instance not found');
return false;
}
}
private disconnectCallback() {
console.log('Websocket connection lost');
if (this.isRoomAvailable()) {
this.session.onLostConnection();
} else {
alert('Connection error. Please reload page.');
}
}
private reconnectingCallback() {
console.log('Websocket connection lost (reconnecting)');
if (this.isRoomAvailable()) {
this.session.onLostConnection();
} else {
alert('Connection error. Please reload page.');
}
}
private reconnectedCallback() {
console.log('Websocket reconnected');
}
private onParticipantJoined(params) {
if (this.isRoomAvailable()) {
this.session.onParticipantJoined(params);
}
}
private onParticipantPublished(params) {
if (this.isRoomAvailable()) {
this.session.onParticipantPublished(params);
}
}
private onParticipantLeft(params) {
if (this.isRoomAvailable()) {
this.session.onParticipantLeft(params);
}
}
private onParticipantEvicted(params) {
if (this.isRoomAvailable()) {
this.session.onParticipantEvicted(params);
}
}
private onNewMessage(params) {
if (this.isRoomAvailable()) {
this.session.onNewMessage(params);
}
}
private iceCandidateEvent(params) {
if (this.isRoomAvailable()) {
this.session.recvIceCandidate(params);
}
}
private onRoomClosed(params) {
if (this.isRoomAvailable()) {
this.session.onRoomClosed(params);
}
}
private onMediaError(params) {
if (this.isRoomAvailable()) {
this.session.onMediaError(params);
}
}
setRpcParams(params: any) {
this.rpcParams = params;
}
sendRequest(method, params, callback?) {
if (params && params instanceof Function) {
callback = params;
params = undefined;
}
params = params || {};
if (this.rpcParams && this.rpcParams !== null && this.rpcParams !== undefined) {
for (let index in this.rpcParams) {
if (this.rpcParams.hasOwnProperty(index)) {
params[index] = this.rpcParams[index];
console.log('RPC param added to request {' + index + ': ' + this.rpcParams[index] + '}');
}
}
}
console.log('Sending request: { method:"' + method + '", params: ' + JSON.stringify(params) + ' }');
this.jsonRpcClient.send(method, params, callback);
}
close(forced) {
if (this.isRoomAvailable()) {
this.session.leave(forced, this.jsonRpcClient);
}
};
disconnectParticipant(stream) {
if (this.isRoomAvailable()) {
this.session.disconnect(stream);
}
}
getCamera(options?) {
if (this.camera) {
return this.camera;
}
options = options || {
audio: true,
video: true,
data: true,
mediaConstraints: {
audio: true,
video: { width: { ideal: 1280 } }
}
}
options.participant = this.session.getLocalParticipant();
this.camera = new Stream(this, true, this.session, options);
return this.camera;
};
/*joinSession(options: SessionOptions, callback: Callback<Session>) {
this.session.configure(options);
this.session.connect2();
this.session.addEventListener('room-connected', roomEvent => callback(undefined,this.session));
this.session.addEventListener('error-room', error => callback(error));
return this.session;
};*/
//CHAT
sendMessage(room, user, message) {
this.sendRequest('sendMessage', {
message: message,
userMessage: user,
roomMessage: room
}, function (error, response) {
if (error) {
console.error(error);
}
});
};
sendCustomRequest(params, callback) {
this.sendRequest('customRequest', params, callback);
};
toggleLocalVideoTrack(activate: boolean) {
this.getCamera().getWebRtcPeer().videoEnabled = activate;
}
toggleLocalAudioTrack(activate: boolean) {
this.getCamera().getWebRtcPeer().audioEnabled = activate;
}
publishLocalVideoAudio() {
this.toggleLocalVideoTrack(true);
this.toggleLocalAudioTrack(true);
}
unpublishLocalVideoAudio() {
this.toggleLocalVideoTrack(false);
this.toggleLocalAudioTrack(false);
}
}

View File

@ -5,13 +5,12 @@
*
* stream.hasAudio(); stream.hasVideo(); stream.hasData();
*/
import { Stream, StreamOptions, VideoOptions } from '../OpenVidu/Stream';
import { OpenViduTokBox } from './OpenViduTokBox';
import { SessionTokBox } from './SessionTokBox';
import { Stream, StreamOptions, VideoOptions } from '../OpenViduInternal/Stream';
import { Session } from './Session';
import EventEmitter = require('wolfy87-eventemitter');
export class PublisherTokBox {
export class Publisher {
private ee = new EventEmitter();
@ -19,7 +18,7 @@ export class PublisherTokBox {
element: Element;
id: string;
stream: Stream;
session: SessionTokBox; //Initialized by SessionTokBox.publish(PublisherTokBox)
session: Session; //Initialized by Session.publish(Publisher)
constructor(stream: Stream, parentId: string) {
this.stream = stream;

View File

@ -1,524 +1,179 @@
import { Stream } from './Stream';
import { OpenVidu } from './OpenVidu';
import { Participant, ParticipantOptions } from './Participant';
import EventEmitter = require('wolfy87-eventemitter');
import { SessionInternal, SessionOptions } from '../OpenViduInternal/SessionInternal';
import { Stream } from '../OpenViduInternal/Stream';
export interface SessionOptions {
sessionId: string;
participantId: string;
subscribeToStreams?: boolean;
updateSpeakerInterval?: number;
thresholdSpeaker?: number;
}
import { OpenVidu } from './OpenVidu';
import { Publisher} from './Publisher';
import { Subscriber } from './Subscriber';
export class Session {
private id: string;
private ee = new EventEmitter();
private streams = {};
private participants = {};
private participantsSpeaking: Participant[] = [];
private connected = false;
private localParticipant: Participant;
private subscribeToStreams: boolean;
private updateSpeakerInterval: number;
public thresholdSpeaker: number;
private options: SessionOptions
//capabilities: Capabilities
//connection: Connection
sessionId: String;
constructor(private openVidu: OpenVidu, private sessionId: string) {
this.localParticipant = new Participant(this.openVidu, true, this);
constructor(private session: SessionInternal, private openVidu: OpenVidu) {
this.sessionId = session.getSessionId();
this.session.addEventListener('stream-removed-default', event => {
event.stream.removeVideo();
});
}
/* NEW METHODS */
connect(token, callback) {
this.openVidu.connect((error) => {
if (error) {
callback('ERROR CONNECTING TO OPENVIDU');
}
else {
this.configure({
sessionId: this.sessionId,
participantId: token,
subscribeToStreams: this.subscribeToStreams
});
let joinParams = {
user: token,
room: this.sessionId,
dataChannels: false
}
if (this.localParticipant) {
if (Object.keys(this.localParticipant.getStreams()).some(streamId =>
this.streams[streamId].isDataChannelEnabled())) {
joinParams.dataChannels = true;
}
}
this.openVidu.sendRequest('joinRoom', joinParams, (error, response) => {
if (error) {
callback('UNABLE TO JOIN ROOM');
} else {
this.connected = true;
let exParticipants = response.value;
let roomEvent = {
participants: new Array<Participant>(),
streams: new Array<Stream>()
}
let length = exParticipants.length;
for (let i = 0; i < length; i++) {
let participant = new Participant(this.openVidu, false, this,
exParticipants[i]);
this.participants[participant.getId()] = participant;
roomEvent.participants.push(participant);
let streams = participant.getStreams();
for (let key in streams) {
roomEvent.streams.push(streams[key]);
if (this.subscribeToStreams) {
streams[key].subscribe();
}
}
}
//if (this.subscribeToStreams) {
for (let stream of roomEvent.streams) {
this.ee.emitEvent('stream-added', [{ stream }]);
// Adding the remote stream to the OpenVidu object
this.openVidu.getRemoteStreams().push(stream);
}
//}
callback(undefined);
}
});
}
// Early configuration to deactivate automatic subscription to streams
this.session.configure({
sessionId: this.session.getSessionId(),
participantId: token,
subscribeToStreams: false
});
this.session.connect(token, callback);
}
publish() {
this.openVidu.getCamera().publish();
disconnect() {
this.openVidu.openVidu.close(false);
}
onStreamAddedOV(callback) {
this.addEventListener("stream-added", streamEvent => {
publish(publisher: Publisher) {
publisher.session = this;
publisher.stream.publish();
}
unpublish(publisher: Publisher) {
this.session.unpublish(publisher.stream);
}
on(eventName: string, callback) {
let realEventName = '';
switch (eventName) {
case 'streamCreated':
realEventName = 'stream-added';
break;
case 'streamDestroyed':
realEventName = 'stream-removed';
break;
}
if (realEventName != '') {
this.session.addEventListener(realEventName, event => {
callback(event);
});
} else {
this.session.addEventListener(eventName, event => {
callback(event);
});
}
}
once(eventName: string, callback) {
let realEventName = '';
switch (eventName) {
case 'streamCreated':
realEventName = 'stream-added';
break;
case 'streamDestroyed':
realEventName = 'stream-removed';
break;
}
if (realEventName != '') {
this.session.addOnceEventListener(realEventName, event => {
callback(event);
});
} else {
this.session.addOnceEventListener(eventName, event => {
callback(event);
});
}
}
off(eventName: string, eventHandler) {
let realEventName = '';
switch (eventName) {
case 'streamCreated':
realEventName = 'stream-added';
break;
case 'streamDestroyed':
realEventName = 'stream-removed';
break;
}
if (realEventName != '') {
this.session.removeListener(realEventName, eventHandler);
} else {
this.session.removeListener(eventName, eventHandler);
}
}
subscribe(stream: Stream, htmlId: string, videoOptions: any): Subscriber;
subscribe(stream: Stream, htmlId: string): Subscriber;
subscribe(param1, param2, param3?): Subscriber {
// Subscription
this.session.subscribe(param1);
let subscriber = new Subscriber(param1, param2);
param1.playOnlyVideo(param2, null);
return subscriber;
}
unsubscribe(subscriber: Subscriber) {
this.session.unsuscribe(subscriber.stream);
subscriber.stream.removeVideo();
}
/* Shortcut event API */
onStreamCreated(callback) {
this.session.addEventListener("stream-added", streamEvent => {
callback(streamEvent.stream);
});
}
onStreamRemovedOV(callback) {
this.addEventListener("stream-removed", streamEvent => {
onStreamDestroyed(callback) {
this.session.addEventListener("stream-removed", streamEvent => {
callback(streamEvent.stream);
});
}
onParticipantJoinedOV(callback) {
this.addEventListener("participant-joined", participantEvent => {
onParticipantJoined(callback) {
this.session.addEventListener("participant-joined", participantEvent => {
callback(participantEvent.participant);
});
}
onParticipantLeftOV(callback) {
this.addEventListener("participant-left", participantEvent => {
onParticipantLeft(callback) {
this.session.addEventListener("participant-left", participantEvent => {
callback(participantEvent.participant);
});
}
onParticipantPublishedOV(callback) {
this.addEventListener("participant-published", participantEvent => {
onParticipantPublished(callback) {
this.session.addEventListener("participant-published", participantEvent => {
callback(participantEvent.participant);
});
}
onParticipantEvictedOV(callback) {
this.addEventListener("participant-evicted", participantEvent => {
onParticipantEvicted(callback) {
this.session.addEventListener("participant-evicted", participantEvent => {
callback(participantEvent.participant);
});
}
onRoomClosedOV(callback) {
this.addEventListener("room-closed", roomEvent => {
onRoomClosed(callback) {
this.session.addEventListener("room-closed", roomEvent => {
callback(roomEvent.room);
});
}
onLostConnectionOV(callback) {
this.addEventListener("lost-connection", roomEvent => {
onLostConnection(callback) {
this.session.addEventListener("lost-connection", roomEvent => {
callback(roomEvent.room);
});
}
onMediaErrorOV(callback) {
this.addEventListener("error-media", errorEvent => {
onMediaError(callback) {
this.session.addEventListener("error-media", errorEvent => {
callback(errorEvent.error)
});
}
/* NEW METHODS */
configure(options: SessionOptions) {
this.options = options;
this.id = options.sessionId;
this.subscribeToStreams = options.subscribeToStreams == null ? true : options.subscribeToStreams;
this.updateSpeakerInterval = options.updateSpeakerInterval || 1500;
this.thresholdSpeaker = options.thresholdSpeaker || -50;
this.localParticipant.setId(options.participantId);
this.activateUpdateMainSpeaker();
this.participants[options.participantId] = this.localParticipant;
}
getId() {
return this.id;
}
getSessionId() {
return this.sessionId;
}
private activateUpdateMainSpeaker() {
setInterval(() => {
if (this.participantsSpeaking.length > 0) {
this.ee.emitEvent('update-main-speaker', [{
participantId: this.participantsSpeaking[this.participantsSpeaking.length - 1]
}]);
}
}, this.updateSpeakerInterval);
}
getLocalParticipant() {
return this.localParticipant;
}
addEventListener(eventName, listener) {
this.ee.on(eventName, listener);
}
addOnceEventListener(eventName, listener) {
this.ee.once(eventName, listener);
}
removeListener(eventName, listener) {
this.ee.off(eventName, listener);
}
emitEvent(eventName, eventsArray) {
this.ee.emitEvent(eventName, eventsArray);
}
subscribe(stream: Stream) {
stream.subscribe();
}
unsuscribe(stream) {
console.log("Unsubscribing from " + stream.getId());
this.openVidu.sendRequest('unsubscribeFromVideo', {
sender: stream.getId()
},
function (error, response) {
if (error) {
console.error(error);
} else {
console.info("Unsubscribed correctly from " + stream.getId());
}
});
}
onParticipantPublished(options) {
let participant = new Participant(this.openVidu, false, this, options);
let pid = participant.getId();
if (!(pid in this.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)
this.participants[pid] = participant;
this.ee.emitEvent('participant-published', [{ participant }]);
let streams = participant.getStreams();
for (let key in streams) {
let stream = streams[key];
if (this.subscribeToStreams) {
stream.subscribe();
}
this.ee.emitEvent('stream-added', [{ stream }]);
// Adding the remote stream to the OpenVidu object
this.openVidu.getRemoteStreams().push(stream);
}
}
onParticipantJoined(msg) {
let participant = new Participant(this.openVidu, false, this, msg);
let pid = participant.getId();
if (!(pid in this.participants)) {
console.log("New participant to participants list with id", pid);
this.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:", this.participants[pid], ", joined now:", participant);
participant = this.participants[pid];
}
this.ee.emitEvent('participant-joined', [{
participant: participant
}]);
}
onParticipantLeft(msg) {
let participant = this.participants[msg.name];
if (participant !== undefined) {
delete this.participants[msg.name];
this.ee.emitEvent('participant-left', [{
participant: participant
}]);
let streams = participant.getStreams();
for (let key in streams) {
this.ee.emitEvent('stream-removed', [{
stream: streams[key],
preventDefault: () => { this.ee.removeEvent('stream-removed-default'); }
}]);
this.ee.emitEvent('stream-removed-default', [{
stream: streams[key]
}]);
// Deleting the removed stream from the OpenVidu object
let index = this.openVidu.getRemoteStreams().indexOf(streams[key]);
this.openVidu.getRemoteStreams().splice(index, 1);
}
participant.dispose();
} else {
console.warn("Participant " + msg.name
+ " unknown. Participants: "
+ JSON.stringify(this.participants));
}
};
onParticipantEvicted(msg) {
this.ee.emitEvent('participant-evicted', [{
localParticipant: this.localParticipant
}]);
};
onNewMessage(msg) {
console.log("New message: " + JSON.stringify(msg));
let room = msg.room;
let user = msg.user;
let message = msg.message;
if (user !== undefined) {
this.ee.emitEvent('newMessage', [{
room: room,
user: user,
message: message
}]);
} else {
console.warn("User undefined in new message:", msg);
}
}
recvIceCandidate(msg) {
let candidate = {
candidate: msg.candidate,
sdpMid: msg.sdpMid,
sdpMLineIndex: msg.sdpMLineIndex
}
let participant = this.participants[msg.endpointName];
if (!participant) {
console.error("Participant not found for endpoint " +
msg.endpointName + ". Ice candidate will be ignored.",
candidate);
return;
}
let streams = participant.getStreams();
for (let key in streams) {
let stream = streams[key];
stream.getWebRtcPeer().addIceCandidate(candidate, function (error) {
if (error) {
console.error("Error adding candidate for " + key
+ " stream of endpoint " + msg.endpointName
+ ": " + error);
}
});
}
}
onRoomClosed(msg) {
console.log("Room closed: " + JSON.stringify(msg));
let room = msg.room;
if (room !== undefined) {
this.ee.emitEvent('room-closed', [{
room: room
}]);
} else {
console.warn("Room undefined in on room closed", msg);
}
}
onLostConnection() {
if (!this.connected) {
console.warn('Not connected to room, ignoring lost connection notification');
return;
}
console.log('Lost connection in room ' + this.id);
let room = this.id;
if (room !== undefined) {
this.ee.emitEvent('lost-connection', [{ room }]);
} else {
console.warn('Room undefined when lost connection');
}
}
onMediaError(params) {
console.error("Media error: " + JSON.stringify(params));
let error = params.error;
if (error) {
this.ee.emitEvent('error-media', [{
error: error
}]);
} else {
console.warn("Received undefined media error. Params:", params);
}
}
/*
* forced means the user was evicted, no need to send the 'leaveRoom' request
*/
leave(forced, jsonRpcClient) {
forced = !!forced;
console.log("Leaving room (forced=" + forced + ")");
if (this.connected && !forced) {
this.openVidu.sendRequest('leaveRoom', function (error, response) {
if (error) {
console.error(error);
}
jsonRpcClient.close();
});
} else {
jsonRpcClient.close();
}
this.connected = false;
if (this.participants) {
for (let pid in this.participants) {
this.participants[pid].dispose();
delete this.participants[pid];
}
}
}
disconnect(stream: Stream) {
let participant = stream.getParticipant();
if (!participant) {
console.error("Stream to disconnect has no participant", stream);
return;
}
delete this.participants[participant.getId()];
participant.dispose();
if (participant === this.localParticipant) {
console.log("Unpublishing my media (I'm " + participant.getId() + ")");
delete this.localParticipant;
this.openVidu.sendRequest('unpublishVideo', function (error, response) {
if (error) {
console.error(error);
} else {
console.info("Media unpublished correctly");
}
});
} else {
this.unsuscribe(stream);
}
}
unpublish(stream: Stream) {
let participant = stream.getParticipant();
if (!participant) {
console.error("Stream to disconnect has no participant", stream);
return;
}
if (participant === this.localParticipant) {
delete this.participants[participant.getId()];
participant.dispose();
console.log("Unpublishing my media (I'm " + participant.getId() + ")");
delete this.localParticipant;
this.openVidu.sendRequest('unpublishVideo', function (error, response) {
if (error) {
console.error(error);
} else {
console.info("Media unpublished correctly");
}
});
}
}
getStreams() {
return this.streams;
}
addParticipantSpeaking(participantId) {
this.participantsSpeaking.push(participantId);
}
removeParticipantSpeaking(participantId) {
let pos = -1;
for (let i = 0; i < this.participantsSpeaking.length; i++) {
if (this.participantsSpeaking[i] == participantId) {
pos = i;
break;
}
}
if (pos != -1) {
this.participantsSpeaking.splice(pos, 1);
}
}
/* Shortcut event API */
}

View File

@ -1,10 +1,8 @@
import { Stream, StreamOptions, VideoOptions } from '../OpenVidu/Stream';
import { OpenViduTokBox } from './OpenViduTokBox';
import { SessionTokBox } from './SessionTokBox';
import { Stream, StreamOptions, VideoOptions } from '../OpenViduInternal/Stream';
import EventEmitter = require('wolfy87-eventemitter');
export class SubscriberTokBox {
export class Subscriber {
private ee = new EventEmitter();

View File

@ -1,4 +1,5 @@
export * from './OpenVidu';
export * from './Participant';
export * from './Stream';
export * from './Session';
export * from './Publisher';
export * from './Subscriber';
export * from '../OpenViduInternal/Stream';

View File

@ -18,7 +18,7 @@
"forceConsistentCasingInFileNames": true,
"allowSyntheticDefaultImports": true,
"strictNullChecks": true,
"outDir": "../../lib/OpenVidu",
"outDir": "../../lib",
"emitBOM": false,
"preserveConstEnums": true,
"sourceMap": true

View File

@ -1,11 +1,11 @@
import { OpenViduTokBox } from './OpenViduTokBox';
import { OpenViduInternal } from './OpenViduInternal';
//This export with --standalone option allows using OpenVidu from bowser with namespace
//export { OpenVidu } from './OpenVidu';
//This "hack" allows to use OpenVidu from the global space window
if(window){
window["OpenViduTokBox"] = OpenViduTokBox;
window["OpenViduInternal"] = OpenViduInternal;
}
//Command to generate bundle.js without namespace

View File

@ -0,0 +1,357 @@
/*
* (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.
*
*/
import { SessionInternal, SessionOptions } from './SessionInternal';
import { Stream } from './Stream';
import * as RpcBuilder from 'kurento-jsonrpc';
export type Callback<T> = (error?: any, openVidu?: T) => void;
export class OpenViduInternal {
private session: SessionInternal;
private jsonRpcClient: any;
private rpcParams: any;
private callback: Callback<OpenViduInternal>;
private camera: Stream;
private remoteStreams: Stream[] = [];
constructor(private wsUri: string) {
if (this.wsUri.charAt(wsUri.length - 1) != '/') {
this.wsUri += '/';
}
this.wsUri += 'room';
}
/* NEW METHODS */
initSession(sessionId) {
console.log("Session initialized!");
this.session = new SessionInternal(this, sessionId);
return this.session;
}
initPublisherTagged(parentId: string, cameraOptions: any, callback?) {
console.log("Publisher tagged initialized!");
this.getCamera(cameraOptions);
if (callback == null) {
this.camera.requestCameraAccess((error, camera) => {
if (error) {
console.log("Error accessing the camera");
}
else {
this.camera.setVideoElement(this.cameraReady(camera!, parentId));
}
});
return this.camera;
} else {
this.camera.requestCameraAccess((error, camera) => {
if (error) {
callback(error);
}
else {
this.camera.setVideoElement(this.cameraReady(camera!, parentId));
callback(undefined);
}
});
return this.camera;
}
}
cameraReady(camera: Stream, parentId: string) {
this.camera = camera;
let videoElement = this.camera.playOnlyVideo(parentId, null);
this.camera.emitStreamReadyEvent();
return videoElement;
}
initPublisher(cameraOptions: any, callback) {
console.log("Publisher initialized!");
this.getCamera(cameraOptions);
this.camera.requestCameraAccess((error, camera) => {
if (error) callback(error);
else callback(undefined);
});
}
getLocalStream() {
return this.camera;
}
getRemoteStreams() {
return this.remoteStreams;
}
/* NEW METHODS */
getRoom() {
return this.session;
}
connect(callback: Callback<OpenViduInternal>): void {
this.callback = callback;
this.initJsonRpcClient(this.wsUri);
}
private initJsonRpcClient(wsUri: string): void {
let config = {
heartbeat: 3000,
sendCloseMessage: false,
ws: {
uri: wsUri,
useSockJS: false,
onconnected: this.connectCallback.bind(this),
ondisconnect: this.disconnectCallback.bind(this),
onreconnecting: this.reconnectingCallback.bind(this),
onreconnected: this.reconnectedCallback.bind(this)
},
rpc: {
requestTimeout: 15000,
//notifications
participantJoined: this.onParticipantJoined.bind(this),
participantPublished: this.onParticipantPublished.bind(this),
participantUnpublished: this.onParticipantLeft.bind(this),
participantLeft: this.onParticipantLeft.bind(this),
participantEvicted: this.onParticipantEvicted.bind(this),
sendMessage: this.onNewMessage.bind(this),
iceCandidate: this.iceCandidateEvent.bind(this),
mediaError: this.onMediaError.bind(this),
custonNotification: this.customNotification.bind(this)
}
};
this.jsonRpcClient = new RpcBuilder.clients.JsonRpcClient(config);
}
private customNotification(params) {
if (this.isRoomAvailable()) {
this.session.emitEvent("custom-message-received", [{ params: params }]);
}
}
private connectCallback(error) {
if (error) {
this.callback(error);
} else {
this.callback(null);
}
}
private isRoomAvailable() {
if (this.session !== undefined && this.session instanceof SessionInternal) {
return true;
} else {
console.warn('Room instance not found');
return false;
}
}
private disconnectCallback() {
console.log('Websocket connection lost');
if (this.isRoomAvailable()) {
this.session.onLostConnection();
} else {
alert('Connection error. Please reload page.');
}
}
private reconnectingCallback() {
console.log('Websocket connection lost (reconnecting)');
if (this.isRoomAvailable()) {
this.session.onLostConnection();
} else {
alert('Connection error. Please reload page.');
}
}
private reconnectedCallback() {
console.log('Websocket reconnected');
}
private onParticipantJoined(params) {
if (this.isRoomAvailable()) {
this.session.onParticipantJoined(params);
}
}
private onParticipantPublished(params) {
if (this.isRoomAvailable()) {
this.session.onParticipantPublished(params);
}
}
private onParticipantLeft(params) {
if (this.isRoomAvailable()) {
this.session.onParticipantLeft(params);
}
}
private onParticipantEvicted(params) {
if (this.isRoomAvailable()) {
this.session.onParticipantEvicted(params);
}
}
private onNewMessage(params) {
if (this.isRoomAvailable()) {
this.session.onNewMessage(params);
}
}
private iceCandidateEvent(params) {
if (this.isRoomAvailable()) {
this.session.recvIceCandidate(params);
}
}
private onRoomClosed(params) {
if (this.isRoomAvailable()) {
this.session.onRoomClosed(params);
}
}
private onMediaError(params) {
if (this.isRoomAvailable()) {
this.session.onMediaError(params);
}
}
setRpcParams(params: any) {
this.rpcParams = params;
}
sendRequest(method, params, callback?) {
if (params && params instanceof Function) {
callback = params;
params = undefined;
}
params = params || {};
if (this.rpcParams && this.rpcParams !== null && this.rpcParams !== undefined) {
for (let index in this.rpcParams) {
if (this.rpcParams.hasOwnProperty(index)) {
params[index] = this.rpcParams[index];
console.log('RPC param added to request {' + index + ': ' + this.rpcParams[index] + '}');
}
}
}
console.log('Sending request: { method:"' + method + '", params: ' + JSON.stringify(params) + ' }');
this.jsonRpcClient.send(method, params, callback);
}
close(forced) {
if (this.isRoomAvailable()) {
this.session.leave(forced, this.jsonRpcClient);
}
};
disconnectParticipant(stream) {
if (this.isRoomAvailable()) {
this.session.disconnect(stream);
}
}
getCamera(options?) {
if (this.camera) {
return this.camera;
}
options = options || {
audio: true,
video: true,
data: true,
mediaConstraints: {
audio: true,
video: { width: { ideal: 1280 } }
}
}
options.participant = this.session.getLocalParticipant();
this.camera = new Stream(this, true, this.session, options);
return this.camera;
};
/*joinSession(options: SessionOptions, callback: Callback<Session>) {
this.session.configure(options);
this.session.connect2();
this.session.addEventListener('room-connected', roomEvent => callback(undefined,this.session));
this.session.addEventListener('error-room', error => callback(error));
return this.session;
};*/
//CHAT
sendMessage(room, user, message) {
this.sendRequest('sendMessage', {
message: message,
userMessage: user,
roomMessage: room
}, function (error, response) {
if (error) {
console.error(error);
}
});
};
sendCustomRequest(params, callback) {
this.sendRequest('customRequest', params, callback);
};
toggleLocalVideoTrack(activate: boolean) {
this.getCamera().getWebRtcPeer().videoEnabled = activate;
}
toggleLocalAudioTrack(activate: boolean) {
this.getCamera().getWebRtcPeer().audioEnabled = activate;
}
publishLocalVideoAudio() {
this.toggleLocalVideoTrack(true);
this.toggleLocalAudioTrack(true);
}
unpublishLocalVideoAudio() {
this.toggleLocalVideoTrack(false);
this.toggleLocalAudioTrack(false);
}
}

View File

@ -1,7 +1,7 @@
// Participant --------------------------------
import { Stream, StreamOptions } from './Stream';
import { OpenVidu } from './OpenVidu';
import { Session } from './Session';
import { OpenViduInternal } from './OpenViduInternal';
import { SessionInternal } from './SessionInternal';
type ObjMap<T> = { [s: string]: T; }
@ -10,13 +10,13 @@ export interface ParticipantOptions {
streams?: StreamOptions[];
}
export class Participant {
export class ParticipantInternal {
private id: string;
private streams: ObjMap<Stream> = {};
private streamsOpts: StreamOptions[] = [];
constructor( private openVidu: OpenVidu, private local: boolean, private room: Session, private options?: ParticipantOptions ) {
constructor( private openVidu: OpenViduInternal, private local: boolean, private room: SessionInternal, private options?: ParticipantOptions ) {
if ( options ) {

View File

@ -0,0 +1,524 @@
import { Stream } from './Stream';
import { OpenViduInternal } from './OpenViduInternal';
import { ParticipantInternal, ParticipantOptions } from './ParticipantInternal';
import EventEmitter = require('wolfy87-eventemitter');
export interface SessionOptions {
sessionId: string;
participantId: string;
subscribeToStreams?: boolean;
updateSpeakerInterval?: number;
thresholdSpeaker?: number;
}
export class SessionInternal {
private id: string;
private ee = new EventEmitter();
private streams = {};
private participants = {};
private participantsSpeaking: ParticipantInternal[] = [];
private connected = false;
private localParticipant: ParticipantInternal;
private subscribeToStreams: boolean;
private updateSpeakerInterval: number;
public thresholdSpeaker: number;
private options: SessionOptions
constructor(private openVidu: OpenViduInternal, private sessionId: string) {
this.localParticipant = new ParticipantInternal(this.openVidu, true, this);
}
/* NEW METHODS */
connect(token, callback) {
this.openVidu.connect((error) => {
if (error) {
callback('ERROR CONNECTING TO OPENVIDU');
}
else {
this.configure({
sessionId: this.sessionId,
participantId: token,
subscribeToStreams: this.subscribeToStreams
});
let joinParams = {
user: token,
room: this.sessionId,
dataChannels: false
}
if (this.localParticipant) {
if (Object.keys(this.localParticipant.getStreams()).some(streamId =>
this.streams[streamId].isDataChannelEnabled())) {
joinParams.dataChannels = true;
}
}
this.openVidu.sendRequest('joinRoom', joinParams, (error, response) => {
if (error) {
callback('UNABLE TO JOIN ROOM');
} else {
this.connected = true;
let exParticipants = response.value;
let roomEvent = {
participants: new Array<ParticipantInternal>(),
streams: new Array<Stream>()
}
let length = exParticipants.length;
for (let i = 0; i < length; i++) {
let participant = new ParticipantInternal(this.openVidu, false, this,
exParticipants[i]);
this.participants[participant.getId()] = participant;
roomEvent.participants.push(participant);
let streams = participant.getStreams();
for (let key in streams) {
roomEvent.streams.push(streams[key]);
if (this.subscribeToStreams) {
streams[key].subscribe();
}
}
}
//if (this.subscribeToStreams) {
for (let stream of roomEvent.streams) {
this.ee.emitEvent('stream-added', [{ stream }]);
// Adding the remote stream to the OpenVidu object
this.openVidu.getRemoteStreams().push(stream);
}
//}
callback(undefined);
}
});
}
});
}
publish() {
this.openVidu.getCamera().publish();
}
onStreamAddedOV(callback) {
this.addEventListener("stream-added", streamEvent => {
callback(streamEvent.stream);
});
}
onStreamRemovedOV(callback) {
this.addEventListener("stream-removed", streamEvent => {
callback(streamEvent.stream);
});
}
onParticipantJoinedOV(callback) {
this.addEventListener("participant-joined", participantEvent => {
callback(participantEvent.participant);
});
}
onParticipantLeftOV(callback) {
this.addEventListener("participant-left", participantEvent => {
callback(participantEvent.participant);
});
}
onParticipantPublishedOV(callback) {
this.addEventListener("participant-published", participantEvent => {
callback(participantEvent.participant);
});
}
onParticipantEvictedOV(callback) {
this.addEventListener("participant-evicted", participantEvent => {
callback(participantEvent.participant);
});
}
onRoomClosedOV(callback) {
this.addEventListener("room-closed", roomEvent => {
callback(roomEvent.room);
});
}
onLostConnectionOV(callback) {
this.addEventListener("lost-connection", roomEvent => {
callback(roomEvent.room);
});
}
onMediaErrorOV(callback) {
this.addEventListener("error-media", errorEvent => {
callback(errorEvent.error)
});
}
/* NEW METHODS */
configure(options: SessionOptions) {
this.options = options;
this.id = options.sessionId;
this.subscribeToStreams = options.subscribeToStreams == null ? true : options.subscribeToStreams;
this.updateSpeakerInterval = options.updateSpeakerInterval || 1500;
this.thresholdSpeaker = options.thresholdSpeaker || -50;
this.localParticipant.setId(options.participantId);
this.activateUpdateMainSpeaker();
this.participants[options.participantId] = this.localParticipant;
}
getId() {
return this.id;
}
getSessionId() {
return this.sessionId;
}
private activateUpdateMainSpeaker() {
setInterval(() => {
if (this.participantsSpeaking.length > 0) {
this.ee.emitEvent('update-main-speaker', [{
participantId: this.participantsSpeaking[this.participantsSpeaking.length - 1]
}]);
}
}, this.updateSpeakerInterval);
}
getLocalParticipant() {
return this.localParticipant;
}
addEventListener(eventName, listener) {
this.ee.on(eventName, listener);
}
addOnceEventListener(eventName, listener) {
this.ee.once(eventName, listener);
}
removeListener(eventName, listener) {
this.ee.off(eventName, listener);
}
emitEvent(eventName, eventsArray) {
this.ee.emitEvent(eventName, eventsArray);
}
subscribe(stream: Stream) {
stream.subscribe();
}
unsuscribe(stream) {
console.log("Unsubscribing from " + stream.getId());
this.openVidu.sendRequest('unsubscribeFromVideo', {
sender: stream.getId()
},
function (error, response) {
if (error) {
console.error(error);
} else {
console.info("Unsubscribed correctly from " + stream.getId());
}
});
}
onParticipantPublished(options) {
let participant = new ParticipantInternal(this.openVidu, false, this, options);
let pid = participant.getId();
if (!(pid in this.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)
this.participants[pid] = participant;
this.ee.emitEvent('participant-published', [{ participant }]);
let streams = participant.getStreams();
for (let key in streams) {
let stream = streams[key];
if (this.subscribeToStreams) {
stream.subscribe();
}
this.ee.emitEvent('stream-added', [{ stream }]);
// Adding the remote stream to the OpenVidu object
this.openVidu.getRemoteStreams().push(stream);
}
}
onParticipantJoined(msg) {
let participant = new ParticipantInternal(this.openVidu, false, this, msg);
let pid = participant.getId();
if (!(pid in this.participants)) {
console.log("New participant to participants list with id", pid);
this.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:", this.participants[pid], ", joined now:", participant);
participant = this.participants[pid];
}
this.ee.emitEvent('participant-joined', [{
participant: participant
}]);
}
onParticipantLeft(msg) {
let participant = this.participants[msg.name];
if (participant !== undefined) {
delete this.participants[msg.name];
this.ee.emitEvent('participant-left', [{
participant: participant
}]);
let streams = participant.getStreams();
for (let key in streams) {
this.ee.emitEvent('stream-removed', [{
stream: streams[key],
preventDefault: () => { this.ee.removeEvent('stream-removed-default'); }
}]);
this.ee.emitEvent('stream-removed-default', [{
stream: streams[key]
}]);
// Deleting the removed stream from the OpenVidu object
let index = this.openVidu.getRemoteStreams().indexOf(streams[key]);
this.openVidu.getRemoteStreams().splice(index, 1);
}
participant.dispose();
} else {
console.warn("Participant " + msg.name
+ " unknown. Participants: "
+ JSON.stringify(this.participants));
}
};
onParticipantEvicted(msg) {
this.ee.emitEvent('participant-evicted', [{
localParticipant: this.localParticipant
}]);
};
onNewMessage(msg) {
console.log("New message: " + JSON.stringify(msg));
let room = msg.room;
let user = msg.user;
let message = msg.message;
if (user !== undefined) {
this.ee.emitEvent('newMessage', [{
room: room,
user: user,
message: message
}]);
} else {
console.warn("User undefined in new message:", msg);
}
}
recvIceCandidate(msg) {
let candidate = {
candidate: msg.candidate,
sdpMid: msg.sdpMid,
sdpMLineIndex: msg.sdpMLineIndex
}
let participant = this.participants[msg.endpointName];
if (!participant) {
console.error("Participant not found for endpoint " +
msg.endpointName + ". Ice candidate will be ignored.",
candidate);
return;
}
let streams = participant.getStreams();
for (let key in streams) {
let stream = streams[key];
stream.getWebRtcPeer().addIceCandidate(candidate, function (error) {
if (error) {
console.error("Error adding candidate for " + key
+ " stream of endpoint " + msg.endpointName
+ ": " + error);
}
});
}
}
onRoomClosed(msg) {
console.log("Room closed: " + JSON.stringify(msg));
let room = msg.room;
if (room !== undefined) {
this.ee.emitEvent('room-closed', [{
room: room
}]);
} else {
console.warn("Room undefined in on room closed", msg);
}
}
onLostConnection() {
if (!this.connected) {
console.warn('Not connected to room, ignoring lost connection notification');
return;
}
console.log('Lost connection in room ' + this.id);
let room = this.id;
if (room !== undefined) {
this.ee.emitEvent('lost-connection', [{ room }]);
} else {
console.warn('Room undefined when lost connection');
}
}
onMediaError(params) {
console.error("Media error: " + JSON.stringify(params));
let error = params.error;
if (error) {
this.ee.emitEvent('error-media', [{
error: error
}]);
} else {
console.warn("Received undefined media error. Params:", params);
}
}
/*
* forced means the user was evicted, no need to send the 'leaveRoom' request
*/
leave(forced, jsonRpcClient) {
forced = !!forced;
console.log("Leaving room (forced=" + forced + ")");
if (this.connected && !forced) {
this.openVidu.sendRequest('leaveRoom', function (error, response) {
if (error) {
console.error(error);
}
jsonRpcClient.close();
});
} else {
jsonRpcClient.close();
}
this.connected = false;
if (this.participants) {
for (let pid in this.participants) {
this.participants[pid].dispose();
delete this.participants[pid];
}
}
}
disconnect(stream: Stream) {
let participant = stream.getParticipant();
if (!participant) {
console.error("Stream to disconnect has no participant", stream);
return;
}
delete this.participants[participant.getId()];
participant.dispose();
if (participant === this.localParticipant) {
console.log("Unpublishing my media (I'm " + participant.getId() + ")");
delete this.localParticipant;
this.openVidu.sendRequest('unpublishVideo', function (error, response) {
if (error) {
console.error(error);
} else {
console.info("Media unpublished correctly");
}
});
} else {
this.unsuscribe(stream);
}
}
unpublish(stream: Stream) {
let participant = stream.getParticipant();
if (!participant) {
console.error("Stream to disconnect has no participant", stream);
return;
}
if (participant === this.localParticipant) {
delete this.participants[participant.getId()];
participant.dispose();
console.log("Unpublishing my media (I'm " + participant.getId() + ")");
delete this.localParticipant;
this.openVidu.sendRequest('unpublishVideo', function (error, response) {
if (error) {
console.error(error);
} else {
console.info("Media unpublished correctly");
}
});
}
}
getStreams() {
return this.streams;
}
addParticipantSpeaking(participantId) {
this.participantsSpeaking.push(participantId);
}
removeParticipantSpeaking(participantId) {
let pos = -1;
for (let i = 0; i < this.participantsSpeaking.length; i++) {
if (this.participantsSpeaking[i] == participantId) {
pos = i;
break;
}
}
if (pos != -1) {
this.participantsSpeaking.splice(pos, 1);
}
}
}

View File

@ -5,9 +5,9 @@
*
* stream.hasAudio(); stream.hasVideo(); stream.hasData();
*/
import { Participant } from './Participant';
import { Session } from './Session';
import { OpenVidu, Callback } from './OpenVidu';
import { ParticipantInternal } from './ParticipantInternal';
import { SessionInternal } from './SessionInternal';
import { OpenViduInternal, Callback } from './OpenViduInternal';
import EventEmitter = require('wolfy87-eventemitter');
import * as kurentoUtils from 'kurento-utils';
@ -33,7 +33,7 @@ function hide(id: string) {
export interface StreamOptions {
id: string;
participant: Participant;
participant: ParticipantInternal;
recvVideo: any;
recvAudio: any;
video: boolean;
@ -56,7 +56,7 @@ export class Stream {
private video: HTMLVideoElement;
private videoElements: VideoOptions[] = [];
private elements: HTMLDivElement[] = [];
private participant: Participant;
private participant: ParticipantInternal;
private speechEvent: any;
private recvVideo: any;
private recvAudio: any;
@ -73,7 +73,7 @@ export class Stream {
private parentId: string;
public isReady: boolean = false;
constructor(private openVidu: OpenVidu, private local: boolean, private room: Session, options: StreamOptions) {
constructor(private openVidu: OpenViduInternal, private local: boolean, private room: SessionInternal, options: StreamOptions) {
if (options.id) {
this.id = options.id;

View File

@ -0,0 +1,4 @@
export * from './OpenViduInternal';
export * from './ParticipantInternal';
export * from './SessionInternal';
export * from './Stream';

View File

@ -18,7 +18,7 @@
"forceConsistentCasingInFileNames": true,
"allowSyntheticDefaultImports": true,
"strictNullChecks": true,
"outDir": "../../lib/OpenViduTokBox",
"outDir": "../../lib/OpenViduInternal",
"emitBOM": false,
"preserveConstEnums": true,
"sourceMap": true

View File

@ -1,97 +0,0 @@
/*
* (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.
*
*/
import { OpenVidu } from '../OpenVidu/OpenVidu';
import { SessionTokBox } from './SessionTokBox';
import { PublisherTokBox } from './PublisherTokBox';
import * as adapter from 'webrtc-adapter';
if (window) {
window["adapter"] = adapter;
}
export class OpenViduTokBox {
openVidu: OpenVidu;
constructor(private wsUri: string) {
this.openVidu = new OpenVidu(wsUri);
}
initSession(apiKey: string, sessionId: string): SessionTokBox;
initSession(sessionId: string): SessionTokBox;
initSession(param1, param2?): any {
if (this.checkSystemRequirements()){
if (typeof param2 == "string") {
return new SessionTokBox(this.openVidu.initSession(param2), this);
} else {
return new SessionTokBox(this.openVidu.initSession(param1), this);
}
} else {
alert("Browser not supported");
}
}
initPublisher(parentId: string, cameraOptions: any): PublisherTokBox;
initPublisher(parentId: string, cameraOptions: any, callback: any): PublisherTokBox;
initPublisher(parentId: string, cameraOptions: any, callback?): any {
if (this.checkSystemRequirements()){
if (!("audio" in cameraOptions && "data" in cameraOptions && "mediaConstraints" in cameraOptions &&
"video" in cameraOptions && (Object.keys(cameraOptions).length === 4))) {
cameraOptions = {
audio: true,
video: true,
data: true,
mediaConstraints: {
audio: true,
video: { width: { ideal: 1280 } }
}
}
}
return new PublisherTokBox(this.openVidu.initPublisherTagged(parentId, cameraOptions, callback), parentId);
} else {
alert("Browser not supported");
}
}
checkSystemRequirements(): number {
let browser = adapter.browserDetails.browser;
let version = adapter.browserDetails.version;
//Bug fix: 'navigator.userAgent' in Firefox for Ubuntu 14.04 does not return "Firefox/[version]" in the string, so version returned is null
if ((browser == 'firefox') && (version == null)) {
return 1;
}
if (((browser == 'chrome') && (version >= 28)) || ((browser == 'edge') && (version >= 12)) || ((browser == 'firefox') && (version >= 22))) {
return 1;
} else {
return 0;
}
}
getDevices(callback) {
navigator.mediaDevices.enumerateDevices().then((deviceInfos) => {
callback(null, deviceInfos);
}).catch((error) => {
console.log("Error getting devices: " + error);
callback(error, null);
});
}
}

View File

@ -1,179 +0,0 @@
import { Session, SessionOptions } from '../OpenVidu/Session';
import { Stream } from '../OpenVidu/Stream';
import { OpenViduTokBox } from './OpenViduTokBox';
import { PublisherTokBox } from './PublisherTokBox';
import { SubscriberTokBox } from './SubscriberTokBox';
export class SessionTokBox {
//capabilities: Capabilities
//connection: Connection
sessionId: String;
constructor(private session: Session, private openVidu: OpenViduTokBox) {
this.sessionId = session.getSessionId();
this.session.addEventListener('stream-removed-default', event => {
event.stream.removeVideo();
});
}
connect(token, callback) {
// Early configuration to deactivate automatic subscription to streams
this.session.configure({
sessionId: this.session.getSessionId(),
participantId: token,
subscribeToStreams: false
});
this.session.connect(token, callback);
}
disconnect() {
this.openVidu.openVidu.close(false);
}
publish(publisher: PublisherTokBox) {
publisher.session = this;
publisher.stream.publish();
}
unpublish(publisher: PublisherTokBox) {
this.session.unpublish(publisher.stream);
}
on(eventName: string, callback) {
let realEventName = '';
switch (eventName) {
case 'streamCreated':
realEventName = 'stream-added';
break;
case 'streamDestroyed':
realEventName = 'stream-removed';
break;
}
if (realEventName != '') {
this.session.addEventListener(realEventName, event => {
callback(event);
});
} else {
this.session.addEventListener(eventName, event => {
callback(event);
});
}
}
once(eventName: string, callback) {
let realEventName = '';
switch (eventName) {
case 'streamCreated':
realEventName = 'stream-added';
break;
case 'streamDestroyed':
realEventName = 'stream-removed';
break;
}
if (realEventName != '') {
this.session.addOnceEventListener(realEventName, event => {
callback(event);
});
} else {
this.session.addOnceEventListener(eventName, event => {
callback(event);
});
}
}
off(eventName: string, eventHandler) {
let realEventName = '';
switch (eventName) {
case 'streamCreated':
realEventName = 'stream-added';
break;
case 'streamDestroyed':
realEventName = 'stream-removed';
break;
}
if (realEventName != '') {
this.session.removeListener(realEventName, eventHandler);
} else {
this.session.removeListener(eventName, eventHandler);
}
}
subscribe(stream: Stream, htmlId: string, videoOptions: any): SubscriberTokBox;
subscribe(stream: Stream, htmlId: string): SubscriberTokBox;
subscribe(param1, param2, param3?): SubscriberTokBox {
// Subscription
this.session.subscribe(param1);
let subscriber = new SubscriberTokBox(param1, param2);
param1.playOnlyVideo(param2, null);
return subscriber;
}
unsubscribe(subscriber: SubscriberTokBox) {
this.session.unsuscribe(subscriber.stream);
subscriber.stream.removeVideo();
}
/* Shortcut event API */
onStreamCreated(callback) {
this.session.addEventListener("stream-added", streamEvent => {
callback(streamEvent.stream);
});
}
onStreamDestroyed(callback) {
this.session.addEventListener("stream-removed", streamEvent => {
callback(streamEvent.stream);
});
}
onParticipantJoined(callback) {
this.session.addEventListener("participant-joined", participantEvent => {
callback(participantEvent.participant);
});
}
onParticipantLeft(callback) {
this.session.addEventListener("participant-left", participantEvent => {
callback(participantEvent.participant);
});
}
onParticipantPublished(callback) {
this.session.addEventListener("participant-published", participantEvent => {
callback(participantEvent.participant);
});
}
onParticipantEvicted(callback) {
this.session.addEventListener("participant-evicted", participantEvent => {
callback(participantEvent.participant);
});
}
onRoomClosed(callback) {
this.session.addEventListener("room-closed", roomEvent => {
callback(roomEvent.room);
});
}
onLostConnection(callback) {
this.session.addEventListener("lost-connection", roomEvent => {
callback(roomEvent.room);
});
}
onMediaError(callback) {
this.session.addEventListener("error-media", errorEvent => {
callback(errorEvent.error)
});
}
/* Shortcut event API */
}

View File

@ -1,4 +0,0 @@
export * from './OpenViduTokBox';
export * from './PublisherTokBox';
export * from './SessionTokBox';
export * from './SubscriberTokBox';

View File

@ -24,7 +24,7 @@
"rxjs": "^5.1.0",
"ts-helpers": "^1.1.1",
"zone.js": "^0.7.6",
"openvidu-browser": "0.1.1"
"openvidu-browser": "0.2.0"
},
"devDependencies": {
"@angular/cli": "1.0.0-rc.1",

View File

@ -47,18 +47,18 @@
<input type="checkbox" id="toggle-video" name="toggle-video" [checked]="toggleVideo" (change)="updateToggleVideo($event)"> Toggle your video
<input type="checkbox" id="toggle-audio" name="toggle-audio" [checked]="toggleAudio" (change)="updateToggleAudio($event)"> Toggle your audio
<div>
<div id="publisher"></div>
<div id="subscriber"></div>
<div *ngFor="let s of streams; let i = index">
<div style="top: 0;">
<stream [stream]="s"></stream>
</div>
<div *ngIf="stats[i]">
<div style="margin-left: 50px;">
<input type="checkbox" id="toggle-state" name="toggle-state" [checked]="true" (change)="updateToggleState(i)"> Show conexion state
<input type="checkbox" id="toggle-state" name="toggle-state" [checked]="true" (change)="updateToggleState(i)"> Show conexion state
<div [attr.id]="'state-' + i" style="display: block;">
<p><b>Signaling state:</b> {{signalingState[i]}}</p>
<p><b>ICE connection state:</b> {{iceConnectionState[i]}}</p>
</div>
<input type="checkbox" id="toggle-statistics" name="toggle-statistics" (change)="updateToggleStatistics(i)"> Show statistics
<input type="checkbox" id="toggle-statistics" name="toggle-statistics" (change)="updateToggleStatistics(i)"> Show
statistics
<div>
<h2 *ngIf="stats[i].bitrate">Bitrate: {{stats[i].bitrate}}</h2>
<h2 *ngIf="stats[i].framerate">Framerate: {{stats[i].framerate}}</h2>

View File

@ -1,7 +1,7 @@
import { Observable } from 'rxjs/Rx';
import { enableDebugTools } from '@angular/platform-browser';
import { Component } from '@angular/core';
import { OpenVidu, Session, Stream } from 'openvidu-browser';
import { OpenVidu, Session, Publisher, Stream } from 'openvidu-browser';
@Component({
selector: 'app-root',
@ -10,13 +10,16 @@ import { OpenVidu, Session, Stream } from 'openvidu-browser';
export class AppComponent {
private openVidu: OpenVidu;
private session: Session;
private publisher: Publisher;
cameraOptions: any;
// Join form
sessionId: string;
participantId: string;
// Session
session: Session;
streams: Stream[] = [];
// Publish options
@ -36,10 +39,6 @@ export class AppComponent {
constructor() {
this.generateParticipantInfo();
window.onbeforeunload = () => {
this.openVidu.close(true);
}
//this.obtainSupportedConstraints();
}
@ -96,82 +95,70 @@ export class AppComponent {
console.log(mediaConstraints);
var cameraOptions = {
this.cameraOptions = {
audio: this.joinWithAudio,
video: this.joinWithVideo,
data: true,
mediaConstraints: mediaConstraints
}
this.joinSessionShared(cameraOptions);
this.joinSessionShared();
}
joinSessionShared(cameraOptions) {
joinSessionShared() {
this.toggleVideo = this.joinWithVideo;
this.toggleAudio = this.joinWithAudio;
this.openVidu = new OpenVidu("wss://" + location.hostname + ":8443/");
this.session = this.openVidu.initSession(this.sessionId);
this.openVidu.connect((error, openVidu) => {
if (error)
return console.log(error);
let camera = openVidu.getCamera(cameraOptions);
camera.requestCameraAccess((error, camera) => {
if (error)
return console.log(error);
var sessionOptions = {
sessionId: this.sessionId,
participantId: this.participantId
}
openVidu.joinSession(sessionOptions, (error, session) => {
if (error)
return console.log(error);
this.session = session;
camera.publish();
this.addVideoTag(camera);
this.intervalStats().subscribe();
session.addEventListener("stream-added", streamEvent => {
this.addVideoTag(streamEvent.stream);
console.log("Stream " + streamEvent.stream + " added");
});
session.addEventListener("stream-removed", streamEvent => {
this.removeVideoTag(streamEvent.stream);
console.log("Stream " + streamEvent.stream + " removed");
});
});
this.session.on('streamCreated', (event) => {
this.session.subscribe(event.stream, 'subscriber', {
insertMode: 'append',
width: '100%',
height: '100%'
});
this.addVideoTag(event.stream);
});
this.session.on('streamDestroyed', (event) => {
this.removeVideoTag(event.stream);
});
this.session.connect(this.participantId, (error) => {
// If the connection is successful, initialize a publisher and publish to the session
if (!error) {
// 4) Get your own camera stream with the desired resolution and publish it, only if the user is supposed to do so
this.publisher = this.openVidu.initPublisher('publisher', this.cameraOptions);
// 5) Publish your stream
this.session.publish(this.publisher);
this.intervalStats().subscribe();
} else {
console.log('There was an error connecting to the session:', error.code, error.message);
}
});
}
leaveSession() {
this.session.disconnect();
this.session = null;
this.streams = [];
this.openVidu.close(true);
this.generateParticipantInfo();
}
updateToggleVideo(event) {
this.openVidu.toggleLocalVideoTrack(event.target.checked);
this.publisher.publishVideo(event.target.checked);
let msg = (event.target.checked) ? 'Publishing video...' : 'Unpublishing video...'
console.log(msg);
}
updateToggleAudio(event) {
this.openVidu.toggleLocalAudioTrack(event.target.checked);
this.publisher.publishAudio(event.target.checked);
let msg = (event.target.checked) ? 'Publishing audio...' : 'Unpublishing audio...'
console.log(msg);
}

View File

@ -1,7 +1,7 @@
server.port: 5000
spring.datasource.url: jdbc:mysql://mysql:3307/demo
spring.datasource.username: demo_user
spring.datasource.url: jdbc:mysql://localhost/openvidu_sample_app
spring.datasource.username: root
spring.datasource.password: pass
spring.datasource.driverClassName: com.mysql.jdbc.Driver
spring.jpa.database: MYSQL

View File

@ -1,6 +1,6 @@
import { Component, OnInit } from '@angular/core';
import { Location } from '@angular/common';
import { OpenViduTokBox, SessionTokBox, PublisherTokBox } from 'openvidu-browser';
import { OpenVidu, Session, Publisher } from 'openvidu-browser';
import { VideoSessionService } from '../../services/video-session.service';
import { AuthenticationService } from '../../services/authentication.service';
@ -16,9 +16,9 @@ export class VideoSessionComponent implements OnInit {
lesson: Lesson;
OV: OpenViduTokBox;
session: SessionTokBox;
publisher: PublisherTokBox;
OV: OpenVidu;
session: Session;
publisher: Publisher;
sessionId: string;
token: string;
@ -43,7 +43,7 @@ export class VideoSessionComponent implements OnInit {
// In this case, the method ngOnInit takes care of it
// 1) Initialize OpenVidu and your Session
this.OV = new OpenViduTokBox("wss://" + location.hostname + ":8443/");
this.OV = new OpenVidu("wss://" + location.hostname + ":8443/");
this.session = this.OV.initSession("apikey", this.sessionId);
// 2) Specify the actions when events take place