Screen share first version: auto Chrome extension

pull/20/head
pabloFuente 2017-10-03 15:02:36 +02:00
parent 7fd3911e3f
commit 9497ba7ea2
11 changed files with 890 additions and 227 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -20,6 +20,8 @@ import { Session } from './Session';
import { Publisher } from './Publisher';
import * as adapter from 'webrtc-adapter';
import * as screenSharing from '../ScreenSharing/Screen-Capturing.js';
import * as screenSharingAuto from '../ScreenSharing/Screen-Capturing-Auto.js';
if (window) {
window["adapter"] = adapter;
@ -55,16 +57,112 @@ export class OpenVidu {
initPublisher(parentId: string, cameraOptions?: any, callback?: Function): any {
if (this.checkSystemRequirements()) {
let publisher: Publisher;
if (cameraOptions != null) {
let cameraOptionsAux = {
sendAudio: cameraOptions.audio != null ? cameraOptions.audio : true,
sendVideo: cameraOptions.video != null ? cameraOptions.video : true,
activeAudio: cameraOptions.activeAudio != null ? cameraOptions.activeAudio : true,
activeVideo: cameraOptions.activeVideo != null ? cameraOptions.activeVideo : true,
data: true,
mediaConstraints: this.openVidu.generateMediaConstraints(cameraOptions)
};
cameraOptions = cameraOptionsAux;
if (!cameraOptions.screen) {
// Webcam and/or microphone is being requested
let cameraOptionsAux = {
sendAudio: cameraOptions.audio != null ? cameraOptions.audio : true,
sendVideo: cameraOptions.video != null ? cameraOptions.video : true,
activeAudio: cameraOptions.activeAudio != null ? cameraOptions.activeAudio : true,
activeVideo: cameraOptions.activeVideo != null ? cameraOptions.activeVideo : true,
data: true,
mediaConstraints: this.openVidu.generateMediaConstraints(cameraOptions)
};
cameraOptions = cameraOptionsAux;
publisher = new Publisher(this.openVidu.initPublisherTagged(parentId, cameraOptions, callback), parentId, false);
console.info("'Publisher' initialized");
return publisher;
} else {
if (adapter.browserDetails.browser === 'firefox' && adapter.browserDetails.version >= 52) {
publisher = new Publisher(this.openVidu.initPublisherScreen(parentId), parentId, true);
screenSharingAuto.getScreenId((error, sourceId, screenConstraints) => {
cameraOptions = {
sendAudio: cameraOptions.audio != null ? cameraOptions.audio : true,
sendVideo: cameraOptions.video != null ? cameraOptions.video : true,
activeAudio: cameraOptions.activeAudio != null ? cameraOptions.activeAudio : true,
activeVideo: cameraOptions.activeVideo != null ? cameraOptions.activeVideo : true,
data: true,
mediaConstraints: {
video: screenConstraints.video,
audio: false
}
}
publisher.stream.configureScreenOptions(cameraOptions);
console.info("'Publisher' initialized");
});
return publisher;
} else if (adapter.browserDetails.browser === 'chrome') {
// Screen is being requested
/*screenSharing.isChromeExtensionAvailable((availability) => {
switch (availability) {
case 'available':
console.warn('EXTENSION AVAILABLE!!!');
screenSharing.getScreenConstraints((error, screenConstraints) => {
if (!error) {
console.warn(screenConstraints);
}
});
break;
case 'unavailable':
console.warn('EXTENSION NOT AVAILABLE!!!');
break;
case 'isFirefox':
console.warn('IT IS FIREFOX!!!');
screenSharing.getScreenConstraints((error, screenConstraints) => {
if (!error) {
console.warn(screenConstraints);
}
});
break;
}
});*/
screenSharingAuto.getScreenId((error, sourceId, screenConstraints) => {
if (error === 'not-installed') {
console.error('Error capturing the screen: an extension is needed');
if (confirm('You need an extension to share your screen!\n\n' +
'This is the URL where you can install it:\n' +
'https://chrome.google.com/webstore/detail/screen-capturing/ajhifddimkapgcifgcodmmfdlknahffk\n\n' +
'Click OK to install it (Pop-Up can be blocked) and then reload this page')) {
window.open(
'https://chrome.google.com/webstore/detail/screen-capturing/ajhifddimkapgcifgcodmmfdlknahffk',
'_blank'
);
}
return;
} else if (error === 'permission-denied') {
console.error('Error capturing the screen: Permission Denied');
return;
}
cameraOptions = {
sendAudio: cameraOptions.audio != null ? cameraOptions.audio : true,
sendVideo: cameraOptions.video != null ? cameraOptions.video : true,
activeAudio: cameraOptions.activeAudio != null ? cameraOptions.activeAudio : true,
activeVideo: cameraOptions.activeVideo != null ? cameraOptions.activeVideo : true,
data: true,
mediaConstraints: {
video: screenConstraints.video,
audio: false
}
}
publisher.stream.configureScreenOptions(cameraOptions);
}, (error) => {
console.error('getScreenId error', error);
return;
});
publisher = new Publisher(this.openVidu.initPublisherScreen(parentId), parentId, true);
console.info("'Publisher' initialized");
return publisher;
} else {
console.error('Screen sharing not supported on ' + adapter.browserDetails.browser);
}
}
} else {
cameraOptions = {
sendAudio: true,
@ -77,12 +175,10 @@ export class OpenVidu {
video: { width: { ideal: 1280 } }
}
}
publisher = new Publisher(this.openVidu.initPublisherTagged(parentId, cameraOptions, callback), parentId, false);
console.info("'Publisher' initialized");
return publisher;
}
var publisher = new Publisher(this.openVidu.initPublisherTagged(parentId, cameraOptions, callback), parentId);
console.info("'Publisher' initialized");
return publisher;
} else {
alert("Browser not supported");
}
@ -113,10 +209,10 @@ export class OpenVidu {
}
enableProdMode() {
console.log = function() {};
console.debug = function() {};
console.info = function() {};
console.warn = function() {};
console.log = function () { };
console.debug = function () { };
console.info = function () { };
console.warn = function () { };
}
}

View File

@ -19,9 +19,11 @@ export class Publisher {
id: string;
stream: Stream;
session: Session; //Initialized by Session.publish(Publisher)
isScreenRequested: boolean = false;
constructor(stream: Stream, parentId: string) {
constructor(stream: Stream, parentId: string, isScreenRequested: boolean) {
this.stream = stream;
this.isScreenRequested = isScreenRequested;
this.stream.addEventListener('camera-access-changed', (event) => {
this.accessAllowed = event.accessAllowed;
@ -82,13 +84,13 @@ export class Publisher {
if (eventName == 'videoPlaying') {
var video = this.stream.getVideoElement();
if (!this.stream.displayMyRemote() && video &&
video.currentTime > 0 &&
video.paused == false &&
video.currentTime > 0 &&
video.paused == false &&
video.ended == false &&
video.readyState == 4) {
this.ee.emitEvent('videoPlaying', [{
element: this.stream.getVideoElement()
}]);
this.ee.emitEvent('videoPlaying', [{
element: this.stream.getVideoElement()
}]);
} else {
this.stream.addOnceEventListener('video-is-playing', (element) => {
this.ee.emitEvent('videoPlaying', [{
@ -100,13 +102,13 @@ export class Publisher {
if (eventName == 'remoteVideoPlaying') {
var video = this.stream.getVideoElement();
if (this.stream.displayMyRemote() && video &&
video.currentTime > 0 &&
video.paused == false &&
video.currentTime > 0 &&
video.paused == false &&
video.ended == false &&
video.readyState == 4) {
this.ee.emitEvent('remoteVideoPlaying', [{
element: this.stream.getVideoElement()
}]);
this.ee.emitEvent('remoteVideoPlaying', [{
element: this.stream.getVideoElement()
}]);
} else {
this.stream.addOnceEventListener('remote-video-is-playing', (element) => {
this.ee.emitEvent('remoteVideoPlaying', [{

View File

@ -76,8 +76,15 @@ export class Session {
}
publish(publisher: Publisher) {
publisher.session = this;
publisher.stream.publish();
if (publisher.isScreenRequested && !publisher.stream.isScreenRequestedReady) {
publisher.stream.addOnceEventListener('screen-ready', () => {
publisher.session = this;
publisher.stream.publish();
});
} else {
publisher.session = this;
publisher.stream.publish();
}
}
unpublish(publisher: Publisher) {
@ -121,64 +128,4 @@ export class Session {
subscriber.stream.removeVideo();
}
/* Shortcut event API */
onStreamCreated(callback) {
this.session.addEventListener("streamCreated", streamEvent => {
callback(streamEvent.stream);
});
}
onStreamDestroyed(callback) {
this.session.addEventListener("streamDestroyed", streamEvent => {
callback(streamEvent.stream);
});
}
onParticipantJoined(callback) {
this.session.addEventListener("participant-joined", participantEvent => {
callback(participantEvent.connection);
});
}
onParticipantLeft(callback) {
this.session.addEventListener("participant-left", participantEvent => {
callback(participantEvent.connection);
});
}
onParticipantPublished(callback) {
this.session.addEventListener("participant-published", participantEvent => {
callback(participantEvent.connection);
});
}
onParticipantEvicted(callback) {
this.session.addEventListener("participant-evicted", participantEvent => {
callback(participantEvent.connection);
});
}
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

@ -42,32 +42,65 @@ export class OpenViduInternal {
return this.session;
}
initPublisherTagged(parentId: string, cameraOptions: any, callback?) {
initPublisherTagged(parentId: string, cameraOptions: any, callback?: Function): Stream {
this.getCamera(cameraOptions);
if (callback == null) {
this.camera.requestCameraAccess((error, camera) => {
if (error) {
console.error("Error accessing the camera", error);
}
else {
this.camera.setVideoElement(this.cameraReady(camera!, parentId));
}
});
return this.camera;
} else {
this.camera.requestCameraAccess((error, camera) => {
if (error) {
this.camera.requestCameraAccess((error, camera) => {
if (error) {
console.error("Error accessing the camera", error);
if (callback) {
callback(error);
}
else {
this.camera.setVideoElement(this.cameraReady(camera!, parentId));
} else {
this.camera.setVideoElement(this.cameraReady(camera!, parentId));
if (callback) {
callback(undefined);
}
}
});
return this.camera;
}
initPublisherScreen(parentId: string, callback?): Stream {
this.camera = new Stream(this, true, this.session, 'screen-options');
this.camera.addOnceEventListener('can-request-screen', () => {
this.camera.requestCameraAccess((error, camera) => {
if (error) {
console.error("Error capturing the screen", error);
if (callback) {
callback(error);
}
}
else {
this.camera.setVideoElement(this.cameraReady(camera!, parentId));
if (this.camera.getSendAudio()) {
// If the user wants to send audio with the screen capturing
navigator.mediaDevices.getUserMedia({ audio: true, video: false })
.then(userStream => {
this.camera.getWrStream().addTrack(userStream.getAudioTracks()[0]);
this.camera.isScreenRequestedReady = true;
this.camera.ee.emitEvent('screen-ready');
if (callback) {
callback(undefined);
}
})
.catch(error => {
this.camera.ee.emitEvent('access-denied-by-publisher');
console.error("Access denied", error);
if (callback) callback(error, this);
});
} else {
this.camera.isScreenRequestedReady = true;
this.camera.ee.emitEvent('screen-ready');
if (callback) {
callback(undefined);
}
}
}
});
return this.camera;
}
});
return this.camera;
}
cameraReady(camera: Stream, parentId: string) {

View File

@ -53,7 +53,7 @@ export class Stream {
public connection: Connection;
private ee = new EventEmitter();
ee = new EventEmitter();
private wrStream: MediaStream;
private wp: any;
private id: string;
@ -81,25 +81,14 @@ export class Stream {
public isVideoELementCreated: boolean = false;
public accessIsAllowed: boolean = false;
public accessIsDenied: boolean = false;
public isScreenRequestedReady: boolean = false;
constructor(private openVidu: OpenViduInternal, private local: boolean, private room: SessionInternal, options: StreamOptions) {
if (options.id) {
this.id = options.id;
constructor(private openVidu: OpenViduInternal, private local: boolean, private room: SessionInternal, options: any) {
if (options !== 'screen-options') {
this.configureOptions(options);
} else {
this.id = "webcam";
this.connection = this.room.getLocalParticipant();
}
this.connection = options.connection;
this.recvVideo = options.recvVideo || false;
this.recvAudio = options.recvAudio || false;
this.sendVideo = options.sendVideo;
this.sendAudio = options.sendAudio;
this.activeAudio = options.activeAudio;
this.activeVideo = options.activeVideo;
this.dataChannel = options.data || false;
this.mediaConstraints = options.mediaConstraints;
this.addEventListener('src-added', (srcEvent) => {
this.videoSrcObject = srcEvent.srcObject;
if (this.video) this.video.srcObject = srcEvent.srcObject;
@ -159,6 +148,14 @@ export class Stream {
return this.recvAudio;
}
getSendVideo() {
return this.sendVideo;
}
getSendAudio() {
return this.sendAudio;
}
subscribeToMyRemote() {
this.showMyRemote = true;
@ -397,7 +394,7 @@ export class Stream {
});
}
private cameraAccessSuccess(userStream, callback) {
private cameraAccessSuccess(userStream: MediaStream, callback: Function) {
this.accessIsAllowed = true;
this.accessIsDenied = false;
this.ee.emitEvent('access-allowed-by-publisher');
@ -592,7 +589,7 @@ export class Stream {
participantId: participantId
}]);
});
}
}
for (let videoElement of this.videoElements) {
@ -679,4 +676,39 @@ export class Stream {
console.info((this.local ? "Local " : "Remote ") + "'Stream' with id [" + this.getId() + "]' has been succesfully disposed");
}
private configureOptions(options) {
if (options.id) {
this.id = options.id;
} else {
this.id = "webcam";
}
this.connection = options.connection;
this.recvVideo = options.recvVideo || false;
this.recvAudio = options.recvAudio || false;
this.sendVideo = options.sendVideo;
this.sendAudio = options.sendAudio;
this.activeAudio = options.activeAudio;
this.activeVideo = options.activeVideo;
this.dataChannel = options.data || false;
this.mediaConstraints = options.mediaConstraints;
}
configureScreenOptions(options) {
if (options.id) {
this.id = options.id;
} else {
this.id = "screen";
}
this.recvVideo = options.recvVideo || false;
this.recvAudio = options.recvAudio || false;
this.sendVideo = options.sendVideo;
this.sendAudio = options.sendAudio;
this.activeAudio = options.activeAudio;
this.activeVideo = options.activeVideo;
this.dataChannel = options.data || false;
this.mediaConstraints = options.mediaConstraints;
this.ee.emitEvent('can-request-screen');
}
}

View File

@ -0,0 +1,164 @@
// Last time updated at Feb 16, 2017, 08:32:23
// Latest file can be found here: https://cdn.webrtc-experiment.com/getScreenId.js
// Muaz Khan - www.MuazKhan.com
// MIT License - www.WebRTC-Experiment.com/licence
// Documentation - https://github.com/muaz-khan/getScreenId.
// ______________
// getScreenId.js
/*
getScreenId(function (error, sourceId, screen_constraints) {
// error == null || 'permission-denied' || 'not-installed' || 'installed-disabled' || 'not-chrome'
// sourceId == null || 'string' || 'firefox'
if(sourceId == 'firefox') {
navigator.mozGetUserMedia(screen_constraints, onSuccess, onFailure);
}
else navigator.webkitGetUserMedia(screen_constraints, onSuccess, onFailure);
});
*/
window.getScreenId = function (callback) {
// for Firefox:
// sourceId == 'firefox'
// screen_constraints = {...}
if (!!navigator.mozGetUserMedia) {
callback(null, 'firefox', {
video: {
mozMediaSource: 'window',
mediaSource: 'window'
}
});
return;
}
window.addEventListener('message', onIFrameCallback);
function onIFrameCallback(event) {
if (!event.data) return;
if (event.data.chromeMediaSourceId) {
if (event.data.chromeMediaSourceId === 'PermissionDeniedError') {
callback('permission-denied');
} else callback(null, event.data.chromeMediaSourceId, getScreenConstraints(null, event.data.chromeMediaSourceId));
}
if (event.data.chromeExtensionStatus) {
callback(event.data.chromeExtensionStatus, null, getScreenConstraints(event.data.chromeExtensionStatus));
}
// this event listener is no more needed
window.removeEventListener('message', onIFrameCallback);
}
setTimeout(postGetSourceIdMessage, 100);
};
function getScreenConstraints(error, sourceId) {
var screen_constraints = {
audio: false,
video: {
mandatory: {
chromeMediaSource: error ? 'screen' : 'desktop',
maxWidth: window.screen.width > 1920 ? window.screen.width : 1920,
maxHeight: window.screen.height > 1080 ? window.screen.height : 1080
},
optional: []
}
};
if (sourceId) {
screen_constraints.video.mandatory.chromeMediaSourceId = sourceId;
}
return screen_constraints;
}
function postGetSourceIdMessage() {
if (!iframe) {
loadIFrame(postGetSourceIdMessage);
return;
}
if (!iframe.isLoaded) {
setTimeout(postGetSourceIdMessage, 100);
return;
}
iframe.contentWindow.postMessage({
captureSourceId: true
}, '*');
}
var iframe;
// this function is used in RTCMultiConnection v3
window.getScreenConstraints = function (callback) {
loadIFrame(function () {
getScreenId(function (error, sourceId, screen_constraints) {
callback(error, screen_constraints.video);
});
});
};
function loadIFrame(loadCallback) {
if (iframe) {
loadCallback();
return;
}
iframe = document.createElement('iframe');
iframe.onload = function () {
iframe.isLoaded = true;
loadCallback();
};
iframe.src = 'https://www.webrtc-experiment.com/getSourceId/'; // https://wwww.yourdomain.com/getScreenId.html
iframe.style.display = 'none';
(document.body || document.documentElement).appendChild(iframe);
}
window.getChromeExtensionStatus = function (callback) {
// for Firefox:
if (!!navigator.mozGetUserMedia) {
callback('installed-enabled');
return;
}
window.addEventListener('message', onIFrameCallback);
function onIFrameCallback(event) {
if (!event.data) return;
if (event.data.chromeExtensionStatus) {
callback(event.data.chromeExtensionStatus);
}
// this event listener is no more needed
window.removeEventListener('message', onIFrameCallback);
}
setTimeout(postGetChromeExtensionStatusMessage, 100);
};
function postGetChromeExtensionStatusMessage() {
if (!iframe) {
loadIFrame(postGetChromeExtensionStatusMessage);
return;
}
if (!iframe.isLoaded) {
setTimeout(postGetChromeExtensionStatusMessage, 100);
return;
}
iframe.contentWindow.postMessage({
getChromeExtensionStatus: true
}, '*');
}
exports.getScreenId = getScreenId;
exports.getChromeExtensionStatus = getChromeExtensionStatus;

View File

@ -0,0 +1,127 @@
// global variables
var chromeMediaSource = 'screen';
var sourceId;
var screenCallback;
var isFirefox = typeof window.InstallTrigger !== 'undefined';
var isOpera = !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0;
var isChrome = !!window.chrome && !isOpera;
window.addEventListener('message', function (event) {
if (event.origin != window.location.origin) {
return;
}
onMessageCallback(event.data);
});
// and the function that handles received messages
function onMessageCallback(data) {
// "cancel" button is clicked
if (data == 'PermissionDeniedError') {
chromeMediaSource = 'PermissionDeniedError';
if (screenCallback)
return screenCallback('PermissionDeniedError');
else
throw new Error('PermissionDeniedError');
}
// extension notified his presence
if (data == 'rtcmulticonnection-extension-loaded') {
chromeMediaSource = 'desktop';
}
// extension shared temp sourceId
if (data.sourceId && screenCallback) {
screenCallback(sourceId = data.sourceId);
}
}
// this method can be used to check if chrome extension is installed & enabled.
function isChromeExtensionAvailable(callback) {
if (isFirefox)
return callback(false);
if (chromeMediaSource == 'desktop')
return callback('isFirefox');
// ask extension if it is available
window.postMessage('are-you-there', '*');
setTimeout(function () {
if (chromeMediaSource == 'screen') {
callback('unavailable');
}
else
callback('available');
}, 2000);
}
// this function can be used to get "source-id" from the extension
function getSourceId(callback) {
if (!callback)
throw '"callback" parameter is mandatory.';
if (sourceId)
return callback(sourceId);
screenCallback = callback;
window.postMessage('get-sourceId', '*');
}
function getChromeExtensionStatus(extensionid, callback) {
if (isFirefox)
return callback('not-chrome');
if (arguments.length != 2) {
callback = extensionid;
extensionid = 'ajhifddimkapgcifgcodmmfdlknahffk'; // default extension-id
}
var image = document.createElement('img');
image.src = 'chrome-extension://' + extensionid + '/icon.png';
image.onload = function () {
chromeMediaSource = 'screen';
window.postMessage('are-you-there', '*');
setTimeout(function () {
if (chromeMediaSource == 'screen') {
callback(extensionid == extensionid ? 'installed-enabled' : 'installed-disabled');
}
else
callback('installed-enabled');
}, 2000);
};
image.onerror = function () {
callback('not-installed');
};
}
// this function explains how to use above methods/objects
function getScreenConstraints(callback) {
var firefoxScreenConstraints = {
mozMediaSource: 'window',
mediaSource: 'window'
};
if (isFirefox)
return callback(null, firefoxScreenConstraints);
// this statement defines getUserMedia constraints
// that will be used to capture content of screen
var screen_constraints = {
mandatory: {
chromeMediaSource: chromeMediaSource,
maxWidth: screen.width > 1920 ? screen.width : 1920,
maxHeight: screen.height > 1080 ? screen.height : 1080
},
optional: []
};
// this statement verifies chrome extension availability
// if installed and available then it will invoke extension API
// otherwise it will fallback to command-line based screen capturing API
if (chromeMediaSource == 'desktop' && !sourceId) {
getSourceId(function () {
screen_constraints.mandatory.chromeMediaSourceId = sourceId;
callback(sourceId == 'PermissionDeniedError' ? sourceId : null, screen_constraints);
});
return;
}
// this statement sets gets 'sourceId" and sets "chromeMediaSourceId"
if (chromeMediaSource == 'desktop') {
screen_constraints.mandatory.chromeMediaSourceId = sourceId;
}
// now invoking native getUserMedia API
callback(null, screen_constraints);
}
exports.getScreenConstraints = getScreenConstraints;
exports.isChromeExtensionAvailable = isChromeExtensionAvailable;
exports.getChromeExtensionStatus = getChromeExtensionStatus;
exports.getSourceId = getSourceId;

View File

@ -187,7 +187,8 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
video: this.sendVideo,
activeAudio: this.activeAudio,
activeVideo: this.activeVideo,
quality: 'MEDIUM'
quality: 'MEDIUM',
screen: this.optionsVideo === 'screen' ? true : false
});
this.publisher.on('videoElementCreated', (event) => {