diff --git a/openvidu-browser/src/main/resources/ts/Main.ts b/openvidu-browser/src/main/resources/ts/OpenVidu/Main.ts similarity index 100% rename from openvidu-browser/src/main/resources/ts/Main.ts rename to openvidu-browser/src/main/resources/ts/OpenVidu/Main.ts diff --git a/openvidu-browser/src/main/resources/ts/OpenVidu.ts b/openvidu-browser/src/main/resources/ts/OpenVidu/OpenVidu.ts similarity index 91% rename from openvidu-browser/src/main/resources/ts/OpenVidu.ts rename to openvidu-browser/src/main/resources/ts/OpenVidu/OpenVidu.ts index 13d9de4b..41080b55 100644 --- a/openvidu-browser/src/main/resources/ts/OpenVidu.ts +++ b/openvidu-browser/src/main/resources/ts/OpenVidu/OpenVidu.ts @@ -47,19 +47,35 @@ export class OpenVidu { return this.session; } - initPublisherTagged(parentId: string, cameraOptions: any, callback) { + initPublisherTagged(parentId: string, cameraOptions: any, callback?) { console.log("Publisher tagged initialized!"); this.getCamera(cameraOptions); - this.camera.requestCameraAccess((error, camera) => { - if (error){ - callback(error); - } - else { - this.camera.playOnlyVideo(parentId, null); - callback(undefined); - } - }); + + if (callback == null) { + this.camera.requestCameraAccess((error, camera) => { + if (error) { + console.log("Error accessing the camera"); + } + else { + this.camera.playOnlyVideo(parentId, null); + this.camera.isReady = true; + this.camera.emitStreamReadyEvent(); + } + }); + return this.camera; + } else { + this.camera.requestCameraAccess((error, camera) => { + if (error){ + callback(error); + } + else { + this.camera.playOnlyVideo(parentId, null); + callback(undefined); + } + }); + return this.camera; + } } initPublisher(cameraOptions: any, callback) { diff --git a/openvidu-browser/src/main/resources/ts/Participant.ts b/openvidu-browser/src/main/resources/ts/OpenVidu/Participant.ts similarity index 100% rename from openvidu-browser/src/main/resources/ts/Participant.ts rename to openvidu-browser/src/main/resources/ts/OpenVidu/Participant.ts diff --git a/openvidu-browser/src/main/resources/ts/Session.ts b/openvidu-browser/src/main/resources/ts/OpenVidu/Session.ts similarity index 91% rename from openvidu-browser/src/main/resources/ts/Session.ts rename to openvidu-browser/src/main/resources/ts/OpenVidu/Session.ts index dabd131a..c98202e6 100644 --- a/openvidu-browser/src/main/resources/ts/Session.ts +++ b/openvidu-browser/src/main/resources/ts/OpenVidu/Session.ts @@ -41,7 +41,8 @@ export class Session { else { this.configure({ sessionId: this.sessionId, - participantId: token + participantId: token, + subscribeToStreams: this.subscribeToStreams }); let joinParams = { @@ -91,14 +92,14 @@ export class Session { } } - if (this.subscribeToStreams) { + //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); } @@ -174,7 +175,7 @@ export class Session { this.options = options; this.id = options.sessionId; - this.subscribeToStreams = options.subscribeToStreams || true; + this.subscribeToStreams = options.subscribeToStreams == null ? true : options.subscribeToStreams; this.updateSpeakerInterval = options.updateSpeakerInterval || 1500; this.thresholdSpeaker = options.thresholdSpeaker || -50; this.localParticipant.setId(options.participantId); @@ -187,6 +188,10 @@ export class Session { return this.id; } + getSessionId(){ + return this.sessionId; + } + private activateUpdateMainSpeaker() { setInterval(() => { @@ -236,11 +241,10 @@ export class Session { if ( this.subscribeToStreams ) { stream.subscribe(); - this.ee.emitEvent( 'stream-added', [{ stream }] ); - - // Adding the remote stream to the OpenVidu object - this.openVidu.getRemoteStreams().push(stream); } + this.ee.emitEvent( 'stream-added', [{ stream }] ); + // Adding the remote stream to the OpenVidu object + this.openVidu.getRemoteStreams().push(stream); } } @@ -457,6 +461,31 @@ export class Session { } } + 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; } diff --git a/openvidu-browser/src/main/resources/ts/Stream.ts b/openvidu-browser/src/main/resources/ts/OpenVidu/Stream.ts similarity index 97% rename from openvidu-browser/src/main/resources/ts/Stream.ts rename to openvidu-browser/src/main/resources/ts/OpenVidu/Stream.ts index b8ef7e5f..34d52bd5 100644 --- a/openvidu-browser/src/main/resources/ts/Stream.ts +++ b/openvidu-browser/src/main/resources/ts/OpenVidu/Stream.ts @@ -70,6 +70,7 @@ export class Stream { private dataChannelOpened = false; private videoSrc: string; + public isReady: boolean = false; constructor(private openVidu: OpenVidu, private local: boolean, private room: Session, options: StreamOptions) { @@ -100,6 +101,10 @@ export class Stream { }]); } + emitStreamReadyEvent(){ + this.ee.emitEvent('stream-ready'), [{}]; + } + getVideoSrc() { return this.videoSrc; } @@ -426,8 +431,13 @@ export class Stream { publish() { // FIXME: Throw error when stream is not local - - this.initWebRtcPeer(this.publishVideoCallback); + if (this.isReady) { + this.initWebRtcPeer(this.publishVideoCallback); + } else { + this.addEventListener('stream-ready', streamEvent => { + this.publish(); + }); + } // 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. diff --git a/openvidu-browser/src/main/resources/ts/definitions.d.ts b/openvidu-browser/src/main/resources/ts/OpenVidu/definitions.d.ts similarity index 100% rename from openvidu-browser/src/main/resources/ts/definitions.d.ts rename to openvidu-browser/src/main/resources/ts/OpenVidu/definitions.d.ts diff --git a/openvidu-browser/src/main/resources/ts/index.ts b/openvidu-browser/src/main/resources/ts/OpenVidu/index.ts similarity index 100% rename from openvidu-browser/src/main/resources/ts/index.ts rename to openvidu-browser/src/main/resources/ts/OpenVidu/index.ts diff --git a/openvidu-browser/src/main/resources/ts/tsconfig.json b/openvidu-browser/src/main/resources/ts/OpenVidu/tsconfig.json similarity index 95% rename from openvidu-browser/src/main/resources/ts/tsconfig.json rename to openvidu-browser/src/main/resources/ts/OpenVidu/tsconfig.json index 898c6068..45c04d87 100644 --- a/openvidu-browser/src/main/resources/ts/tsconfig.json +++ b/openvidu-browser/src/main/resources/ts/OpenVidu/tsconfig.json @@ -18,7 +18,7 @@ "forceConsistentCasingInFileNames": true, "allowSyntheticDefaultImports": true, "strictNullChecks": true, - "outDir": "../lib", + "outDir": "../../lib/OpenVidu", "emitBOM": false, "preserveConstEnums": true, "sourceMap": true diff --git a/openvidu-browser/src/main/resources/ts/OpenViduTokBox/Main.ts b/openvidu-browser/src/main/resources/ts/OpenViduTokBox/Main.ts new file mode 100644 index 00000000..b34e27d1 --- /dev/null +++ b/openvidu-browser/src/main/resources/ts/OpenViduTokBox/Main.ts @@ -0,0 +1,12 @@ +import { OpenViduTokBox } from './OpenViduTokBox'; + +//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; +} + +//Command to generate bundle.js without namespace +//watchify Main.ts -p [ tsify ] --exclude kurento-browser-extensions --debug -o ../static/js/OpenVidu.js -v \ No newline at end of file diff --git a/openvidu-browser/src/main/resources/ts/OpenViduTokBox/OpenViduTokBox.ts b/openvidu-browser/src/main/resources/ts/OpenViduTokBox/OpenViduTokBox.ts new file mode 100644 index 00000000..defbc3bc --- /dev/null +++ b/openvidu-browser/src/main/resources/ts/OpenViduTokBox/OpenViduTokBox.ts @@ -0,0 +1,45 @@ +/* + * (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'; + +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 (typeof param2 == "string") { + return new SessionTokBox(this.openVidu.initSession(param2), this); + } else { + return new SessionTokBox(this.openVidu.initSession(param1), this); + } + } + + initPublisher(parentId: string, cameraOptions: any): PublisherTokBox { + return new PublisherTokBox(this.openVidu.initPublisherTagged(parentId, cameraOptions)); + } + +} diff --git a/openvidu-browser/src/main/resources/ts/OpenViduTokBox/PublisherTokBox.ts b/openvidu-browser/src/main/resources/ts/OpenViduTokBox/PublisherTokBox.ts new file mode 100644 index 00000000..e3e5661e --- /dev/null +++ b/openvidu-browser/src/main/resources/ts/OpenViduTokBox/PublisherTokBox.ts @@ -0,0 +1,20 @@ +/* + * 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(); + */ +import { Stream, StreamOptions, VideoOptions } from '../OpenVidu/Stream'; +import { OpenViduTokBox } from './OpenViduTokBox'; +import { SessionTokBox } from './SessionTokBox'; + +export class PublisherTokBox { + + stream: Stream; + + constructor(stream: Stream) { + this.stream = stream; + } + +} diff --git a/openvidu-browser/src/main/resources/ts/OpenViduTokBox/SessionTokBox.ts b/openvidu-browser/src/main/resources/ts/OpenViduTokBox/SessionTokBox.ts new file mode 100644 index 00000000..4cc55bc1 --- /dev/null +++ b/openvidu-browser/src/main/resources/ts/OpenViduTokBox/SessionTokBox.ts @@ -0,0 +1,121 @@ +import { Session, SessionOptions } from '../OpenVidu/Session'; +import { Stream } from '../OpenVidu/Stream'; + +import { OpenViduTokBox } from './OpenViduTokBox'; +import { PublisherTokBox } from './PublisherTokBox'; + +export class SessionTokBox { + + constructor(private session: Session, private openVidu: OpenViduTokBox) { } + + 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.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 { + console.warn("That is not a supported event!"); + } + } + + subscribe(stream: Stream, htmlId: string, videoOptions: any); + subscribe(stream: Stream, htmlId: string); + + subscribe(param1, param2, param3?) { + // Subscription + this.session.subscribe(param1); + param1.playOnlyVideo(param2, null); + } + + + + + /* 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 */ +} diff --git a/openvidu-browser/src/main/resources/ts/OpenViduTokBox/definitions.d.ts b/openvidu-browser/src/main/resources/ts/OpenViduTokBox/definitions.d.ts new file mode 100644 index 00000000..1db280a1 --- /dev/null +++ b/openvidu-browser/src/main/resources/ts/OpenViduTokBox/definitions.d.ts @@ -0,0 +1,3 @@ +declare module "kurento-jsonrpc"; +declare module "webrtc-adapter"; +declare module "kurento-utils"; \ No newline at end of file diff --git a/openvidu-browser/src/main/resources/ts/OpenViduTokBox/index.ts b/openvidu-browser/src/main/resources/ts/OpenViduTokBox/index.ts new file mode 100644 index 00000000..f0364657 --- /dev/null +++ b/openvidu-browser/src/main/resources/ts/OpenViduTokBox/index.ts @@ -0,0 +1,3 @@ +export * from './OpenViduTokBox'; +export * from './PublisherTokBox'; +export * from './SessionTokBox'; diff --git a/openvidu-browser/src/main/resources/ts/OpenViduTokBox/tsconfig.json b/openvidu-browser/src/main/resources/ts/OpenViduTokBox/tsconfig.json new file mode 100644 index 00000000..ee0d88a4 --- /dev/null +++ b/openvidu-browser/src/main/resources/ts/OpenViduTokBox/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "declaration": true, + "target": "es5", + "module": "commonjs", + //"noImplicitAny": true, + "noImplicitThis": true, + //"noUnusedLocals": true, + //"noUnusedParameters": true, + "skipDefaultLibCheck": true, + "skipLibCheck": true, + "suppressExcessPropertyErrors": true, + "suppressImplicitAnyIndexErrors": true, + //"allowUnusedLabels": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + //"allowUnreachableCode": true, + "forceConsistentCasingInFileNames": true, + "allowSyntheticDefaultImports": true, + "strictNullChecks": true, + "outDir": "../../lib/OpenViduTokBox", + "emitBOM": false, + "preserveConstEnums": true, + "sourceMap": true + }, + //"buildOnSave": true, + "compileOnSave":true +} \ No newline at end of file diff --git a/openvidu-sample-app/src/main/resources/frontend/src/app/components/video-session/video-session.component.ts b/openvidu-sample-app/src/main/resources/frontend/src/app/components/video-session/video-session.component.ts index 079b6b73..c07cbe5d 100644 --- a/openvidu-sample-app/src/main/resources/frontend/src/app/components/video-session/video-session.component.ts +++ b/openvidu-sample-app/src/main/resources/frontend/src/app/components/video-session/video-session.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit } from '@angular/core'; import { Location } from '@angular/common'; -import { OpenVidu, Session, Stream } from 'openvidu-browser'; +import { OpenViduTokBox, SessionTokBox, PublisherTokBox } from 'openvidu-browser'; import { VideoSessionService } from '../../services/video-session.service'; import { AuthenticationService } from '../../services/authentication.service'; @@ -16,8 +16,8 @@ export class VideoSessionComponent implements OnInit { lesson: Lesson; - OV: OpenVidu; - session: Session; + OV: OpenViduTokBox; + session: SessionTokBox; sessionId: string; token: string; @@ -44,69 +44,27 @@ export class VideoSessionComponent implements OnInit { // In this case, the method ngOnInit takes care of it // 1) Initialize OpenVidu and your Session - this.OV = new OpenVidu("wss://" + location.hostname + ":8443/"); + this.OV = new OpenViduTokBox("wss://" + location.hostname + ":8443/"); this.session = this.OV.initSession(this.sessionId); - - // 2) Specify the actions when participants enter and leave the session - this.session.onStreamAddedOV((stream) => { - console.warn("Stream added:"); - console.warn(stream); - stream.playOnlyVideo(this.remoteParentId, null); + // 2) Specify the actions when events take place + this.session.on('streamCreated', (event) => { + console.warn("Stream created:"); + console.warn(event.stream); + this.session.subscribe(event.stream, this.remoteParentId); }); - this.session.onStreamRemovedOV((stream) => { - console.warn("Stream removed:"); - console.warn(stream); - stream.removeVideo(this.remoteParentId); - }); - this.session.onParticipantJoinedOV((participant) => { - console.warn("Participant joined:"); - console.warn(participant); - }); - this.session.onParticipantLeftOV((participant) => { - console.warn("Participant left:"); - console.warn(participant); - }); - this.session.onParticipantPublishedOV((participant) => { - console.warn("Participant published:"); - console.warn(participant); - }); - this.session.onParticipantEvictedOV((participant) => { - console.warn("Participant evicted:"); - console.warn(participant); - }); - this.session.onRoomClosedOV((room) => { - console.warn("Room closed:"); - console.warn(room); - }); - this.session.onLostConnectionOV((room) => { - console.warn("Connection lost:"); - console.warn(room); - }); - this.session.onMediaErrorOV((error) => { - console.warn("Media error:"); - console.warn(error); - }); - // 3) Connect to the session this.session.connect(this.token, (error) => { - if (error) return console.log("There was an error: " + error); - - - // 4) Get your own camera stream with the desired resolution and publish it, only if the user is supposed to do so - - // Local publish generating an HTML video element as a child of parentId HTML element - this.OV.initPublisherTagged(this.localParentId, this.cameraOptions, (error) => { - if (error) return console.log("There was an error with your camera: " + error); - this.session.publish(); - }); - - // Local publish without generating an HTML video element - /*this.OV.initPublisher(this.cameraOptions, (error) => { - if (error) return console.log("There was an error: " + error); - this.session.publish(); - });*/ + if (!error) { + // 4) Get your own camera stream with the desired resolution and publish it, only if the user is supposed to do so + let publisher = this.OV.initPublisher(this.localParentId, this.cameraOptions); + // 5) Publish your stream + this.session.publish(publisher); + } + else { + return console.log("There was an error: " + error); + } }); @@ -169,26 +127,14 @@ export class VideoSessionComponent implements OnInit { ngOnDestroy() { this.videoSessionService.removeUser(this.lesson.id).subscribe( response => { - console.warn("You have succesfully left the lesson"); - }, - error => { - console.log(error); - }); + console.warn("You have succesfully left the lesson"); + }, + error => { + console.log(error); + }); this.toggleScrollPage("auto"); this.exitFullScreen(); - if (this.OV) this.OV.close(false); - } - - toggleLocalVideo() { - this.localVideoActivated = !this.localVideoActivated; - this.OV.toggleLocalVideoTrack(this.localVideoActivated); - this.videoIcon = this.localVideoActivated ? 'videocam' : 'videocam_off'; - } - - toggleLocalAudio() { - this.localAudioActivated = !this.localAudioActivated; - this.OV.toggleLocalAudioTrack(this.localAudioActivated); - this.audioIcon = this.localAudioActivated ? 'mic' : 'mic_off'; + if (this.OV) this.session.disconnect(); } toggleScrollPage(scroll: string) { @@ -196,39 +142,6 @@ export class VideoSessionComponent implements OnInit { content.style.overflow = scroll; } - toggleFullScreen() { - let document: any = window.document; - let fs = document.getElementsByTagName('html')[0]; - if (!document.fullscreenElement && - !document.mozFullScreenElement && - !document.webkitFullscreenElement && - !document.msFullscreenElement) { - console.log("enter FULLSCREEN!"); - this.fullscreenIcon = 'fullscreen_exit'; - if (fs.requestFullscreen) { - fs.requestFullscreen(); - } else if (fs.msRequestFullscreen) { - fs.msRequestFullscreen(); - } else if (fs.mozRequestFullScreen) { - fs.mozRequestFullScreen(); - } else if (fs.webkitRequestFullscreen) { - fs.webkitRequestFullscreen(); - } - } else { - console.log("exit FULLSCREEN!"); - this.fullscreenIcon = 'fullscreen'; - if (document.exitFullscreen) { - document.exitFullscreen(); - } else if (document.msExitFullscreen) { - document.msExitFullscreen(); - } else if (document.mozCancelFullScreen) { - document.mozCancelFullScreen(); - } else if (document.webkitExitFullscreen) { - document.webkitExitFullscreen(); - } - } - } - exitFullScreen() { let document: any = window.document; let fs = document.getElementsByTagName('html')[0];