openvidu-browser LocalRecording API

pull/30/head
pabloFuente 2018-03-01 11:13:45 +01:00
parent 7d64b242f4
commit 36d5018507
9 changed files with 309 additions and 41 deletions

View File

@ -1,37 +1,37 @@
{ {
"author": "OpenVidu", "author": "OpenVidu",
"dependencies": { "dependencies": {
"freeice": "2.2.0", "freeice": "2.2.0",
"hark": "1.1.6", "hark": "1.1.6",
"inherits": "2.0.3", "inherits": "2.0.3",
"merge": "1.2.0", "merge": "1.2.0",
"sdp-translator": "0.1.24", "sdp-translator": "0.1.24",
"ua-parser-js": "0.7.17", "ua-parser-js": "0.7.17",
"uuid": "3.1.0", "uuid": "3.1.0",
"webrtc-adapter": "6.1.1", "webrtc-adapter": "6.1.1",
"wolfy87-eventemitter": "5.2.4" "wolfy87-eventemitter": "5.2.4"
}, },
"description": "OpenVidu Browser", "description": "OpenVidu Browser",
"devDependencies": { "devDependencies": {
"browserify": "15.1.0", "browserify": "15.1.0",
"tsify": "3.0.4", "tsify": "3.0.4",
"typescript": "2.6.2", "typescript": "2.6.2",
"uglify-js": "3.3.5" "uglify-js": "3.3.5"
}, },
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "lib/OpenVidu/index.js", "main": "lib/OpenVidu/index.js",
"name": "openvidu-browser", "name": "openvidu-browser",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git://github.com/OpenVidu/openvidu" "url": "git://github.com/OpenVidu/openvidu"
}, },
"scripts": { "scripts": {
"browserify": "VERSION=${VERSION:-}; cd ts/OpenVidu && browserify Main.ts -p [ tsify ] --exclude kurento-browser-extensions --debug -o ../../static/js/openvidu-browser-$VERSION.js -v", "browserify": "VERSION=${VERSION:-}; cd ts/OpenVidu && browserify Main.ts -p [ tsify ] --exclude kurento-browser-extensions --debug -o ../../static/js/openvidu-browser-$VERSION.js -v",
"browserify-prod": "VERSION=${VERSION:-}; cd ts/OpenVidu && browserify --debug Main.ts -p [ tsify ] --exclude kurento-browser-extensions | uglifyjs --source-map content=inline --output ../../static/js/openvidu-browser-$VERSION.min.js", "browserify-prod": "VERSION=${VERSION:-}; cd ts/OpenVidu && browserify --debug Main.ts -p [ tsify ] --exclude kurento-browser-extensions | uglifyjs --source-map content=inline --output ../../static/js/openvidu-browser-$VERSION.min.js",
"prepublish": "cd ts/OpenViduInternal && tsc && cd ../OpenVidu && tsc && cd ../.. && tsc --declaration ts/OpenVidu/index.ts --outDir lib --sourceMap && tsc --declaration ts/OpenVidu/Main.ts --outDir lib --sourceMap", "prepublish": "cd ts/OpenViduInternal && tsc && cd ../OpenVidu && tsc && cd ../.. && tsc --declaration ts/OpenVidu/index.ts --outDir lib --sourceMap && tsc --declaration ts/OpenVidu/Main.ts --outDir lib --sourceMap",
"test": "echo \"Error: no test specified\" && exit 1", "test": "echo \"Error: no test specified\" && exit 1",
"updatetsc": "cd ts/OpenViduInternal && tsc && cd ../OpenVidu && tsc && cd ../.. && tsc --declaration ts/OpenVidu/index.ts --outDir lib --sourceMap && tsc --declaration ts/OpenVidu/Main.ts --outDir lib --sourceMap" "updatetsc": "cd ts/OpenViduInternal && tsc && cd ../OpenVidu && tsc && cd ../.. && tsc --declaration ts/OpenVidu/index.ts --outDir lib --sourceMap --lib dom,es5,es2015.promise,scripthost && tsc --declaration ts/OpenVidu/Main.ts --outDir lib --sourceMap --lib dom,es5,es2015.promise,scripthost"
}, },
"types": "lib/OpenVidu/index.d.ts", "types": "lib/OpenVidu/index.d.ts",
"version": "1.7.0" "version": "1.7.0"
} }

View File

@ -20,6 +20,8 @@ import { Session } from './Session';
import { Publisher } from './Publisher'; import { Publisher } from './Publisher';
import { OpenViduError, OpenViduErrorName } from '../OpenViduInternal/OpenViduError'; import { OpenViduError, OpenViduErrorName } from '../OpenViduInternal/OpenViduError';
import { OutboundStreamOptions } from '../OpenViduInternal/index'; import { OutboundStreamOptions } from '../OpenViduInternal/index';
import { Stream } from '../OpenViduInternal/Stream';
import { LocalRecorder } from '../OpenViduInternal/LocalRecorder';
import * as adapter from 'webrtc-adapter'; import * as adapter from 'webrtc-adapter';
import * as screenSharing from '../ScreenSharing/Screen-Capturing.js'; import * as screenSharing from '../ScreenSharing/Screen-Capturing.js';
@ -269,4 +271,8 @@ export class OpenVidu {
console.warn = function () { }; console.warn = function () { };
} }
initLocalRecorder(stream: Stream): LocalRecorder {
return new LocalRecorder(stream);
}
} }

View File

@ -4,3 +4,4 @@ export * from './Publisher';
export * from './Subscriber'; export * from './Subscriber';
export * from '../OpenViduInternal/Stream'; export * from '../OpenViduInternal/Stream';
export * from '../OpenViduInternal/Connection'; export * from '../OpenViduInternal/Connection';
export * from '../OpenViduInternal/LocalRecorder';

View File

@ -21,7 +21,8 @@
"outDir": "../../lib", "outDir": "../../lib",
"emitBOM": false, "emitBOM": false,
"preserveConstEnums": true, "preserveConstEnums": true,
"sourceMap": true "sourceMap": true,
"lib": ["dom","es5","es2015.promise","scripthost"]
}, },
//"buildOnSave": true, //"buildOnSave": true,
"compileOnSave": true "compileOnSave": true

View File

@ -0,0 +1,258 @@
import { Stream } from "./Stream";
declare var MediaRecorder: any;
export const enum LocalRecoderState {
READY = 'READY',
RECORDING = 'RECORDING',
PAUSED = 'PAUSED',
FINISHED = 'FINISHED'
}
export class LocalRecorder {
state: LocalRecoderState;
private stream: Stream;
private mediaRecorder: any;
private chunks: any[] = [];
private blob: Blob;
private count: number = 0;
private id: string;
private videoPreviewSrc: string;
private htmlParentElementId: string;
private videoPreview: HTMLVideoElement;
constructor(stream: Stream) {
this.stream = stream;
this.id = this.stream.streamId + '_' + this.stream.connection.connectionId + '_localrecord';
this.state = LocalRecoderState.READY;
}
record() {
if (typeof MediaRecorder === 'undefined') {
console.error('MediaRecorder not supported on your browser. See compatibility in https://caniuse.com/#search=MediaRecorder');
throw (Error('MediaRecorder not supported on your browser. See compatibility in https://caniuse.com/#search=MediaRecorder'));
}
if (this.state !== LocalRecoderState.READY) {
throw (Error('\'LocalRecord.record()\' needs \'LocalRecord.state\' to be \'READY\' (current value: \'' + this.state + '\'). Call \'LocalRecorder.clean()\' or init a new LocalRecorder before'));
}
console.log("Starting local recording of stream '" + this.stream.streamId + "' of connection '" + this.stream.connection.connectionId + "'");
if (typeof MediaRecorder.isTypeSupported == 'function') {
let options;
if (MediaRecorder.isTypeSupported('video/webm;codecs=vp9')) {
options = { mimeType: 'video/webm;codecs=vp9' };
} else if (MediaRecorder.isTypeSupported('video/webm;codecs=h264')) {
options = { mimeType: 'video/webm;codecs=h264' };
} else if (MediaRecorder.isTypeSupported('video/webm;codecs=vp8')) {
options = { mimeType: 'video/webm;codecs=vp8' };
}
console.log('Using mimeType ' + options.mimeType);
this.mediaRecorder = new MediaRecorder(this.stream.getMediaStream(), options);
} else {
console.warn('isTypeSupported is not supported, using default codecs for browser');
this.mediaRecorder = new MediaRecorder(this.stream.getMediaStream());
}
this.mediaRecorder.start(10);
this.mediaRecorder.ondataavailable = (e) => {
this.chunks.push(e.data);
};
this.mediaRecorder.onerror = (e) => {
console.error('MediaRecorder error: ', e);
};
this.mediaRecorder.onstart = () => {
console.log('MediaRecorder started (state=' + this.mediaRecorder.state + ")");
};
this.mediaRecorder.onstop = () => {
this.onStopDefault();
};
this.mediaRecorder.onpause = () => {
console.log('MediaRecorder paused (state=' + this.mediaRecorder.state + ")");
}
this.mediaRecorder.onresume = () => {
console.log('MediaRecorder resumed (state=' + this.mediaRecorder.state + ")");
}
this.mediaRecorder.onwarning = (e) => {
console.log('MediaRecorder warning: ' + e);
};
this.state = LocalRecoderState.RECORDING;
}
stop(): Promise<any> {
return new Promise((resolve, reject) => {
try {
if (this.state === LocalRecoderState.READY || this.state === LocalRecoderState.FINISHED) {
throw (Error('\'LocalRecord.stop()\' needs \'LocalRecord.state\' to be \'RECORDING\' or \'PAUSED\' (current value: \'' + this.state + '\'). Call \'LocalRecorder.start()\' before'));
}
this.mediaRecorder.onstop = () => {
this.onStopDefault();
resolve();
}
} catch (e) {
reject(e);
}
try {
this.mediaRecorder.stop();
} catch (e) {
reject(e);
}
});
}
pause() {
if (this.state !== LocalRecoderState.RECORDING) {
throw (Error('\'LocalRecord.pause()\' needs \'LocalRecord.state\' to be \'RECORDING\' (current value: \'' + this.state + '\'). Call \'LocalRecorder.start()\' or \'LocalRecorder.resume()\' before'));
}
this.mediaRecorder.pause();
this.state = LocalRecoderState.PAUSED;
}
resume() {
if (this.state !== LocalRecoderState.PAUSED) {
throw (Error('\'LocalRecord.resume()\' needs \'LocalRecord.state\' to be \'PAUSED\' (current value: \'' + this.state + '\'). Call \'LocalRecorder.pause()\' before'));
}
this.mediaRecorder.resume();
this.state = LocalRecoderState.RECORDING;
}
preview(parentElement): HTMLVideoElement {
if (this.state !== LocalRecoderState.FINISHED) {
throw (Error('\'LocalRecord.preview()\' needs \'LocalRecord.state\' to be \'FINISHED\' (current value: \'' + this.state + '\'). Call \'LocalRecorder.stop()\' before'));
}
this.videoPreview = document.createElement('video');
this.videoPreview.id = this.id;
this.videoPreview.autoplay = true;
if (typeof parentElement === "string") {
this.htmlParentElementId = parentElement;
let parentElementDom = document.getElementById(parentElement);
if (parentElementDom) {
this.videoPreview = parentElementDom.appendChild(this.videoPreview);
}
} else {
this.htmlParentElementId = parentElement.id;
this.videoPreview = parentElement.appendChild(this.videoPreview);
}
this.videoPreview.src = this.videoPreviewSrc;
return this.videoPreview;
}
clean() {
let f = () => {
delete this.blob;
this.chunks = [];
this.count = 0;
delete this.mediaRecorder;
this.state = LocalRecoderState.READY;
}
if (this.state === LocalRecoderState.RECORDING || this.state === LocalRecoderState.PAUSED) {
this.stop().then(() => f()).catch(() => f());
} else {
f();
}
}
download() {
if (this.state !== LocalRecoderState.FINISHED) {
throw (Error('\'LocalRecord.download()\' needs \'LocalRecord.state\' to be \'FINISHED\' (current value: \'' + this.state + '\'). Call \'LocalRecorder.stop()\' before'));
} else {
let a: HTMLAnchorElement = document.createElement("a");
a.style.display = 'none';
document.body.appendChild(a);
let url = window.URL.createObjectURL(this.blob);
a.href = url;
a.download = this.id + '.webm';
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
}
}
getBlob(): Blob {
if (this.state !== LocalRecoderState.FINISHED) {
throw (Error('Call \'LocalRecord.stop()\' before getting Blob file'));
} else {
return this.blob;
}
}
uploadAsBinary(endpoint: string): Promise<any> {
return new Promise((resolve, reject) => {
if (this.state !== LocalRecoderState.FINISHED) {
reject(Error('\'LocalRecord.uploadAsBinary()\' needs \'LocalRecord.state\' to be \'FINISHED\' (current value: \'' + this.state + '\'). Call \'LocalRecorder.stop()\' before'));
} else {
let http = new XMLHttpRequest();
http.open("POST", endpoint, true);
http.onreadystatechange = () => {
if (http.readyState === 4) {
if (http.status === 200) {
resolve("File uploaded");
} else {
reject(Error("Upload error: " + http.status));
}
}
}
http.send(this.blob);
}
});
}
uploadAsMultipartfile(endpoint: string): Promise<any> {
return new Promise((resolve, reject) => {
if (this.state !== LocalRecoderState.FINISHED) {
reject(Error('\'LocalRecord.uploadAsMultipartfile()\' needs \'LocalRecord.state\' to be \'FINISHED\' (current value: \'' + this.state + '\'). Call \'LocalRecorder.stop()\' before'));
} else {
let http = new XMLHttpRequest();
http.open("POST", endpoint, true);
let sendable = new FormData();
sendable.append("name", this.id);
sendable.append("file", this.blob);
http.onreadystatechange = () => {
if (http.readyState === 4) {
if (http.status === 200) {
resolve("File uploaded");
} else {
reject(Error("Upload error: " + http.status));
}
}
}
http.send(sendable);
}
});
}
private onStopDefault() {
console.log('MediaRecorder stopped (state=' + this.mediaRecorder.state + ")");
this.blob = new Blob(this.chunks, { type: "video/webm" });
this.chunks = [];
this.videoPreviewSrc = window.URL.createObjectURL(this.blob);
this.state = LocalRecoderState.FINISHED;
}
}

View File

@ -693,7 +693,7 @@ export class Stream {
} }
private stopWebRtcStats() { private stopWebRtcStats() {
if (this.webRtcStats.isEnabled()) { if (this.webRtcStats != null && this.webRtcStats.isEnabled()) {
this.webRtcStats.stopWebRtcStats(); this.webRtcStats.stopWebRtcStats();
} }
} }

View File

@ -82,7 +82,7 @@ export class WebRtcStats {
http.open("POST", url, true); http.open("POST", url, true);
http.setRequestHeader("Content-type", "application/json"); http.setRequestHeader("Content-type", "application/json");
http.onreadystatechange = () => { // Call a function when the state changes. http.onreadystatechange = () => { // Call a function when the state changes.
if (http.readyState == 4 && http.status == 200) { if (http.readyState == 4 && http.status == 200) {
console.log("WebRtc stats succesfully sent to " + url + " for stream " + this.stream.streamId + " of connection " + this.stream.connection.connectionId); console.log("WebRtc stats succesfully sent to " + url + " for stream " + this.stream.streamId + " of connection " + this.stream.connection.connectionId);

View File

@ -2,4 +2,5 @@ export * from './OpenViduInternal';
export * from './Connection'; export * from './Connection';
export * from './SessionInternal'; export * from './SessionInternal';
export * from './Stream'; export * from './Stream';
export * from './LocalRecorder';
export * from './OpenViduError'; export * from './OpenViduError';

View File

@ -1,7 +1,7 @@
{ {
"compilerOptions": { "compilerOptions": {
"allowJs": true, "allowJs": true,
"target": "es6", "target": "es5",
"module": "commonjs", "module": "commonjs",
//"noImplicitAny": true, //"noImplicitAny": true,
"noImplicitThis": true, "noImplicitThis": true,
@ -21,8 +21,9 @@
"outDir": "../../lib/OpenViduInternal", "outDir": "../../lib/OpenViduInternal",
"emitBOM": false, "emitBOM": false,
"preserveConstEnums": true, "preserveConstEnums": true,
"sourceMap": true "sourceMap": true,
"lib": ["dom","es5","es2015.promise","scripthost"]
}, },
//"buildOnSave": true, //"buildOnSave": true,
"compileOnSave":true "compileOnSave": true
} }