chapin666 / kurento-group-call-node Goto Github PK
View Code? Open in Web Editor NEWkurento group call server
License: MIT License
kurento group call server
License: MIT License
When i am running node index.js, then this error came.
/kurento-group-call-node-master/node_modules/core-js/modules/_typed-buffer.js:157
if(numberLength != byteLength)throw RangeError(WRONG_LENGTH);
RangeError: Wrong length!
at validateArrayBufferArguments (/home/shahnawaz/nodejs/example/kurento-group-call-node-master/node_modules/core-js/modules/_typed-buffer.js:157:39)
at new ArrayBuffer (/home/shahnawaz/nodejs/example/kurento-group-call-node-master/node_modules/core-js/modules/_typed-buffer.js:247:29)
at zlib.js:344:28
at NativeModule.compileForInternalLoader (internal/bootstrap/loaders.js:276:7)
at NativeModule.compileForPublicLoader (internal/bootstrap/loaders.js:218:10)
at loadNativeModule (internal/modules/cjs/helpers.js:24:9)
at Function.Module._load (internal/modules/cjs/loader.js:870:15)
at Module.require (internal/modules/cjs/loader.js:1040:19)
at require (internal/modules/cjs/helpers.js:72:18)
at Object. (/home/shahnawaz/nodejs/example/kurento-group-call-node-master/node_modules/engine.io/lib/transports/polling.js:8:12)
Why have you hosted the node app on 'https'?
How can I add a filter? for example the faceOverlayFilter (or others)?
I want to send modified sdp to chrome. However it is not receiving that modified sdp which is changed in sdp_pattern.txt
v=0
o=mozilla...THIS_IS_SDPARTA-65.0 2266787997050267451 0 IN IP4 0.0.0.0
s=-
t=0 0
a=sendrecv
a=fingerprint:sha-256 79:3F:28:AD:AE:2A:B7:05:60:8D:53:CF:88:3B:88:32:9A:04:71:04:A3:B9:F3:08:D2:41:0C:38:95:41:21:F5
a=group:BUNDLE 0 1 2 // Specify the mids to bundle
a=ice-options:trickle
a=msid-semantic:WMS *
m=audio 63259 UDP/TLS/RTP/SAVPF 109 9 0 8 101 // For 1st audio track
c=IN IP4 192.168.0.12
a=candidate:0 1 UDP 2122252543 192.168.0.12 63259 typ host
... some candidates
a=candidate:1 2 TCP 2105524478 192.168.0.12 9 typ host tcptype active
a=sendrecv
a=end-of-candidates
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
... some extmap
a=fmtp:109 maxplaybackrate=48000;stereo=1;useinbandfec=1
a=fmtp:101 0-15
a=ice-pwd:foo
a=ice-ufrag:bar
a=mid:0 // Identifier of m-line. Thus each m-line has one mid.
a=msid:{aaaaa} {bbbbb} // Media Stream ID. There is msid in each m-line.
a=rtcp-mux
a=rtpmap:109 opus/48000/2
... some rtpmap
a=setup:actpass
a=ssrc:45563795 cname:{ccccc} // Except simulcast case, there is one ssrc in each m-line
m=audio 63259 UDP/TLS/RTP/SAVPF 109 9 0 8 101 // For 2nd audio track
c=IN IP4 192.168.0.12
a=sendrecv
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
... some extmap
a=fmtp:109 maxplaybackrate=48000;stereo=1;useinbandfec=1
a=fmtp:101 0-15
a=ice-pwd:foo
a=ice-ufrag:bar
a=mid:1 // Identifier of m-line. Thus each m-line has one mid.
a=msid:{ddddd} {eeeee} // Media Stream ID. There is msid in each m-line.
a=rtcp-mux
a=rtpmap:109 opus/48000/2
... some rtpmap
a=setup:actpass
a=ssrc:12345678 cname:{fffff} // Except simulcast case, there is one ssrc in each m-line
m=video 63259 UDP/TLS/RTP/SAVPF 120 121 126 97 // For video track
c=IN IP4 192.168.0.12
a=sendrecv
a=extmap:3 urn:ietf:params:rtp-hdrext:sdes:mid
... some extmap
a=fmtp:126 profile-level-id=42e01f;level-asymmetry-allowed=1;packetization-mode=1
... some fmtp
a=ice-pwd:foo
a=ice-ufrag:bar
a=mid:2
a=msid:{ggggg} {hhhhh}
a=rtcp-fb:120 nack
... some rtcp-fb
a=rtcp-mux
a=rtpmap:120 VP8/90000
... some rtpmap
a=setup:actpass
a=ssrc:1242852923 cname:{iiiii}
webrtcpeer.js
var freeice = require('freeice');
var inherits = require('inherits');
var UAParser = require('ua-parser-js');
var uuid = require('uuid');
var hark = require('hark');
var EventEmitter = require('events').EventEmitter;
var recursive = require('merge').recursive.bind(undefined, true);
var sdpTranslator = require('sdp-translator');
try {
require('kurento-browser-extensions');
} catch (error) {
if (typeof getScreenConstraints === 'undefined') {
console.warn('screen sharing is not available');
getScreenConstraints = function getScreenConstraints(sendSource, callback) {
callback(new Error('This library is not enabled for screen sharing'));
};
}
}
var MEDIA_CONSTRAINTS = {
audio: true,
video: {
width: 640,
framerate: 15
}
};
var ua = window && window.navigator ? window.navigator.userAgent : '';
var parser = new UAParser(ua);
var browser = parser.getBrowser();
var usePlanB = false;
if (browser.name === 'Chrome' || browser.name === 'Chromium') {
console.log(browser.name + ': using SDP PlanB');
usePlanB = true;
}
function noop(error) {
if (error)
console.error(error);
}
function trackStop(track) {
track.stop && track.stop();
}
function streamStop(stream) {
stream.getTracks().forEach(trackStop);
}
var dumpSDP = function (description) {
if (typeof description === 'undefined' || description === null) {
return '';
}
return 'type: ' + description.type + '\r\n' + description.sdp;
};
function bufferizeCandidates(pc, onerror) {
var candidatesQueue = [];
pc.addEventListener('signalingstatechange', function () {
if (this.signalingState === 'stable') {
while (candidatesQueue.length) {
var entry = candidatesQueue.shift();
this.addIceCandidate(entry.candidate, entry.callback, entry.callback);
}
}
});
return function (candidate, callback) {
callback = callback || onerror;
switch (pc.signalingState) {
case 'closed':
callback(new Error('PeerConnection object is closed'));
break;
case 'stable':
if (pc.remoteDescription) {
pc.addIceCandidate(candidate, callback, callback);
break;
}
default:
candidatesQueue.push({
candidate: candidate,
callback: callback
});
}
};
}
function removeFIDFromOffer(sdp) {
var n = sdp.indexOf('a=ssrc-group:FID');
if (n > 0) {
return sdp.slice(0, n);
} else {
return sdp;
}
}
function getSimulcastInfo(videoStream) {
var videoTracks = videoStream.getVideoTracks();
if (!videoTracks.length) {
console.warn('No video tracks available in the video stream');
return '';
}
var lines = [
'a=x-google-flag:conference',
'a=ssrc-group:SIM 1 2 3',
'a=ssrc:1 cname:localVideo',
'a=ssrc:1 msid:' + videoStream.id + ' ' + videoTracks[0].id,
'a=ssrc:1 mslabel:' + videoStream.id,
'a=ssrc:1 label:' + videoTracks[0].id,
'a=ssrc:2 cname:localVideo',
'a=ssrc:2 msid:' + videoStream.id + ' ' + videoTracks[0].id,
'a=ssrc:2 mslabel:' + videoStream.id,
'a=ssrc:2 label:' + videoTracks[0].id,
'a=ssrc:3 cname:localVideo',
'a=ssrc:3 msid:' + videoStream.id + ' ' + videoTracks[0].id,
'a=ssrc:3 mslabel:' + videoStream.id,
'a=ssrc:3 label:' + videoTracks[0].id
];
lines.push('');
return lines.join('\n');
}
function WebRtcPeer(mode, options, callback) {
if (!(this instanceof WebRtcPeer)) {
return new WebRtcPeer(mode, options, callback);
}
WebRtcPeer.super_.call(this);
if (options instanceof Function) {
callback = options;
options = undefined;
}
options = options || {};
callback = (callback || noop).bind(this);
var self = this;
var localVideo = options.localVideo;
var remoteVideo = options.remoteVideo;
var videoStream = options.videoStream;
var audioStream = options.audioStream;
var mediaConstraints = options.mediaConstraints;
var connectionConstraints = options.connectionConstraints;
var pc = options.peerConnection;
var sendSource = options.sendSource || 'webcam';
var dataChannelConfig = options.dataChannelConfig;
var useDataChannels = options.dataChannels || false;
var dataChannel;
var guid = uuid.v4();
var configuration = recursive({ iceServers: freeice() }, options.configuration);
var onicecandidate = options.onicecandidate;
if (onicecandidate)
this.on('icecandidate', onicecandidate);
var oncandidategatheringdone = options.oncandidategatheringdone;
if (oncandidategatheringdone) {
this.on('candidategatheringdone', oncandidategatheringdone);
}
var simulcast = options.simulcast;
var multistream = options.multistream;
var interop = new sdpTranslator.Interop();
var candidatesQueueOut = [];
var candidategatheringdone = false;
Object.defineProperties(this, {
'peerConnection': {
get: function () {
return pc;
}
},
'id': {
value: options.id || guid,
writable: false
},
'remoteVideo': {
get: function () {
return remoteVideo;
}
},
'localVideo': {
get: function () {
return localVideo;
}
},
'dataChannel': {
get: function () {
return dataChannel;
}
},
'currentFrame': {
get: function () {
if (!remoteVideo)
return;
if (remoteVideo.readyState < remoteVideo.HAVE_CURRENT_DATA)
throw new Error('No video stream data available');
var canvas = document.createElement('canvas');
canvas.width = remoteVideo.videoWidth;
canvas.height = remoteVideo.videoHeight;
canvas.getContext('2d').drawImage(remoteVideo, 0, 0);
return canvas;
}
}
});
if (!pc) {
pc = new RTCPeerConnection ({ sdpSemantics : "unified-plan" });
if (useDataChannels && !dataChannel) {
var dcId = 'WebRtcPeer-' + self.id;
var dcOptions = undefined;
if (dataChannelConfig) {
dcId = dataChannelConfig.id || dcId;
dcOptions = dataChannelConfig.options;
}
dataChannel = pc.createDataChannel(dcId, dcOptions);
if (dataChannelConfig) {
dataChannel.onopen = dataChannelConfig.onopen;
dataChannel.onclose = dataChannelConfig.onclose;
dataChannel.onmessage = dataChannelConfig.onmessage;
dataChannel.onbufferedamountlow = dataChannelConfig.onbufferedamountlow;
dataChannel.onerror = dataChannelConfig.onerror || noop;
}
}
}
pc.addEventListener('icecandidate', function (event) {
var candidate = event.candidate;
if (EventEmitter.listenerCount(self, 'icecandidate') || EventEmitter.listenerCount(self, 'candidategatheringdone')) {
if (candidate) {
var cand;
if (multistream && usePlanB) {
cand = interop.candidateToUnifiedPlan(candidate);
} else {
cand = candidate;
}
self.emit('icecandidate', cand);
candidategatheringdone = false;
} else if (!candidategatheringdone) {
self.emit('candidategatheringdone');
candidategatheringdone = true;
}
} else if (!candidategatheringdone) {
candidatesQueueOut.push(candidate);
if (!candidate)
candidategatheringdone = true;
}
});
pc.onaddstream = options.onaddstream;
pc.onnegotiationneeded = options.onnegotiationneeded;
this.on('newListener', function (event, listener) {
if (event === 'icecandidate' || event === 'candidategatheringdone') {
while (candidatesQueueOut.length) {
var candidate = candidatesQueueOut.shift();
if (!candidate === (event === 'candidategatheringdone')) {
listener(candidate);
}
}
}
});
var addIceCandidate = bufferizeCandidates(pc);
this.addIceCandidate = function (iceCandidate, callback) {
var candidate;
if (multistream && usePlanB) {
candidate = interop.candidateToPlanB(iceCandidate);
} else {
candidate = new RTCIceCandidate(iceCandidate);
}
console.log('ICE candidate received');
callback = (callback || noop).bind(this);
addIceCandidate(candidate, callback);
};
this.generateOffer = function (callback) {
callback = callback.bind(this);
var offerAudio = true;
var offerVideo = true;
if (mediaConstraints) {
offerAudio = typeof mediaConstraints.audio === 'boolean' ? mediaConstraints.audio : true;
offerVideo = typeof mediaConstraints.video === 'boolean' ? mediaConstraints.video : true;
}
var browserDependantConstraints = browser.name === 'Firefox' && browser.version > 34 ? {
offerToReceiveAudio: mode !== 'sendonly' && offerAudio,
offerToReceiveVideo: mode !== 'sendonly' && offerVideo
} : {
mandatory: {
OfferToReceiveAudio: mode !== 'sendonly' && offerAudio,
OfferToReceiveVideo: mode !== 'sendonly' && offerVideo
},
optional: [{ DtlsSrtpKeyAgreement: true }]
};
var constraints = recursive(browserDependantConstraints, connectionConstraints);
console.log('constraints: ' + JSON.stringify(constraints));
pc.createOffer(constraints).then(function (offer) {
console.log('Created SDP offer');
offer = mangleSdpToAddSimulcast(offer);
return pc.setLocalDescription(offer);
}).then(function () {
var localDescription = pc.localDescription;
console.log('Local description set', localDescription.sdp);
if (multistream && usePlanB) {
localDescription = interop.toUnifiedPlan(localDescription);
console.log('offer::origPlanB->UnifiedPlan', dumpSDP(localDescription));
}
callback(null, localDescription.sdp, self.processAnswer.bind(self));
}).catch(callback);
};
this.getLocalSessionDescriptor = function () {
return pc.localDescription;
};
this.getRemoteSessionDescriptor = function () {
return pc.remoteDescription;
};
function setRemoteVideo() {
if (remoteVideo) {
var stream = pc.getRemoteStreams()[0];
var url = stream;
remoteVideo.pause();
remoteVideo.srcObject = url;
remoteVideo.load();
console.log('Remote URL:', url);
}
}
this.showLocalVideo = function () {
localVideo.srcObject = videoStream;
localVideo.muted = true;
};
this.send = function (data) {
if (dataChannel && dataChannel.readyState === 'open') {
dataChannel.send(data);
} else {
console.warn('Trying to send data over a non-existing or closed data channel');
}
};
this.processAnswer = function (sdpAnswer, callback) {
callback = (callback || noop).bind(this);
var answer = new RTCSessionDescription({
type: 'answer',
sdp: sdpAnswer
});
if (multistream && usePlanB) {
var planBAnswer = interop.toPlanB(answer);
console.log('asnwer::planB', dumpSDP(planBAnswer));
answer = planBAnswer;
}
console.log('SDP answer received, setting remote description');
if (pc.signalingState === 'closed') {
return callback('PeerConnection is closed');
}
pc.setRemoteDescription(answer, function () {
setRemoteVideo();
callback();
}, callback);
};
this.processOffer = function (sdpOffer, callback) {
callback = callback.bind(this);
var offer = new RTCSessionDescription({
type: 'offer',
sdp: sdpOffer
});
if (multistream && usePlanB) {
var planBOffer = interop.toPlanB(offer);
console.log('offer::planB', dumpSDP(planBOffer));
offer = planBOffer;
}
console.log('SDP offer received, setting remote description');
if (pc.signalingState === 'closed') {
return callback('PeerConnection is closed');
}
pc.setRemoteDescription(offer).then(function () {
return setRemoteVideo();
}).then(function () {
return pc.createAnswer();
}).then(function (answer) {
answer = mangleSdpToAddSimulcast(answer);
console.log('Created SDP answer');
return pc.setLocalDescription(answer);
}).then(function () {
var localDescription = pc.localDescription;
if (multistream && usePlanB) {
localDescription = interop.toUnifiedPlan(localDescription);
console.log('answer::origPlanB->UnifiedPlan', dumpSDP(localDescription));
}
console.log('Local description set', localDescription.sdp);
callback(null, localDescription.sdp);
}).catch(callback);
};
function mangleSdpToAddSimulcast(answer) {
if (simulcast) {
if (browser.name === 'Chrome' || browser.name === 'Chromium') {
console.log('Adding multicast info');
answer = new RTCSessionDescription({
'type': answer.type,
'sdp': removeFIDFromOffer(answer.sdp) + getSimulcastInfo(videoStream)
});
} else {
console.warn('Simulcast is only available in Chrome browser.');
}
}
return answer;
}
function start() {
if (pc.signalingState === 'closed') {
callback('The peer connection object is in "closed" state. This is most likely due to an invocation of the dispose method before accepting in the dialogue');
}
if (videoStream && localVideo) {
self.showLocalVideo();
}
if (videoStream) {
pc.addStream(videoStream);
}
if (audioStream) {
pc.addStream(audioStream);
}
var browser = parser.getBrowser();
if (mode === 'sendonly' && (browser.name === 'Chrome' || browser.name === 'Chromium') && browser.major === 39) {
mode = 'sendrecv';
}
callback();
}
if (mode !== 'recvonly' && !videoStream && !audioStream) {
function getMedia(constraints) {
if (constraints === undefined) {
constraints = MEDIA_CONSTRAINTS;
}
getUserMedia(constraints, function (stream) {
videoStream = stream;
start();
}, callback);
}
if (sendSource === 'webcam') {
getMedia(mediaConstraints);
} else {
getScreenConstraints(sendSource, function (error, constraints_) {
if (error)
return callback(error);
constraints = [mediaConstraints];
constraints.unshift(constraints_);
getMedia(recursive.apply(undefined, constraints));
}, guid);
}
} else {
setTimeout(start, 0);
}
this.on('_dispose', function () {
if (localVideo) {
localVideo.pause();
localVideo.srcObject = '';
localVideo.load();
localVideo.muted = false;
}
if (remoteVideo) {
remoteVideo.pause();
remoteVideo.srcObject = '';
remoteVideo.load();
}
self.removeAllListeners();
if (window.cancelChooseDesktopMedia !== undefined) {
window.cancelChooseDesktopMedia(guid);
}
});
}
inherits(WebRtcPeer, EventEmitter);
function createEnableDescriptor(type) {
var method = 'get' + type + 'Tracks';
return {
enumerable: true,
get: function () {
if (!this.peerConnection)
return;
var streams = this.peerConnection.getLocalStreams();
if (!streams.length)
return;
for (var i = 0, stream; stream = streams[i]; i++) {
var tracks = stream[method]();
for (var j = 0, track; track = tracks[j]; j++)
if (!track.enabled)
return false;
}
return true;
},
set: function (value) {
function trackSetEnable(track) {
track.enabled = value;
}
this.peerConnection.getLocalStreams().forEach(function (stream) {
stream[method]().forEach(trackSetEnable);
});
}
};
}
Object.defineProperties(WebRtcPeer.prototype, {
'enabled': {
enumerable: true,
get: function () {
return this.audioEnabled && this.videoEnabled;
},
set: function (value) {
this.audioEnabled = this.videoEnabled = value;
}
},
'audioEnabled': createEnableDescriptor('Audio'),
'videoEnabled': createEnableDescriptor('Video')
});
WebRtcPeer.prototype.getLocalStream = function (index) {
if (this.peerConnection) {
return this.peerConnection.getLocalStreams()[index || 0];
}
};
WebRtcPeer.prototype.getRemoteStream = function (index) {
if (this.peerConnection) {
return this.peerConnection.getRemoteStreams()[index || 0];
}
};
WebRtcPeer.prototype.dispose = function () {
console.log('Disposing WebRtcPeer');
var pc = this.peerConnection;
var dc = this.dataChannel;
try {
if (dc) {
if (dc.signalingState === 'closed')
return;
dc.close();
}
if (pc) {
if (pc.signalingState === 'closed')
return;
pc.getLocalStreams().forEach(streamStop);
pc.close();
}
} catch (err) {
console.warn('Exception disposing webrtc peer ' + err);
}
this.emit('_dispose');
};
function WebRtcPeerRecvonly(options, callback) {
if (!(this instanceof WebRtcPeerRecvonly)) {
return new WebRtcPeerRecvonly(options, callback);
}
WebRtcPeerRecvonly.super_.call(this, 'recvonly', options, callback);
}
inherits(WebRtcPeerRecvonly, WebRtcPeer);
function WebRtcPeerSendonly(options, callback) {
if (!(this instanceof WebRtcPeerSendonly)) {
return new WebRtcPeerSendonly(options, callback);
}
WebRtcPeerSendonly.super_.call(this, 'sendonly', options, callback);
}
inherits(WebRtcPeerSendonly, WebRtcPeer);
function WebRtcPeerSendrecv(options, callback) {
if (!(this instanceof WebRtcPeerSendrecv)) {
return new WebRtcPeerSendrecv(options, callback);
}
WebRtcPeerSendrecv.super_.call(this, 'sendrecv', options, callback);
}
inherits(WebRtcPeerSendrecv, WebRtcPeer);
function harkUtils(stream, options) {
return hark(stream, options);
}
exports.bufferizeCandidates = bufferizeCandidates;
exports.WebRtcPeerRecvonly = WebRtcPeerRecvonly;
exports.WebRtcPeerSendonly = WebRtcPeerSendonly;
exports.WebRtcPeerSendrecv = WebRtcPeerSendrecv;
exports.hark = harkUtils;
I want to offer above mentioned sdp , however chrome is offering its defualt sdp. Also I have updated to unified plan in webrtcpeer.js , however chrome is running on planb.
in console I get:
This appears to be Chrome
WebRtcPeer.js:30 Chrome: using SDP PlanB
I focus on the mobile app, please code for record room.
How many users can the kurento handle when using SFU of kurento-group-call node?
I tried to implement the group call node SFU on a local server. I'm running the KMS server on a virtual machine with 4GB dedicated memory. I was able to successfully enter up to 15 users without errors. But when the 16th user tries to enter, I get the timeout error and the remote videos from other users did not appear.
{ [Error: Request has timed out]
request: '{"jsonrpc":"2.0","method":"invoke","params":{"object":"c2ba9879-d04e-446b-abb7-66629eb41119_kurento.MediaPipeline/413426fd-927c-41de-acc7-47d52654719a_kurento.WebRtcEndpoint","operation":"connect","operationParams":{"sink":"c2ba9879-d04e-446b-abb7-66629eb41119_kurento.MediaPipeline/d233ed2d-ca10-4783-9dc3-bd5de656e07c_kurento.WebRtcEndpoint"},"sessionId":"26449ab3-433e-4335-88b9-86e435578e04"},"id":792}',
retry: [Function: retry] }
Hello,
How can I add many viewers in this project ? I'm new on kurento. I want to start one presenter first and then it will be more presenter with many viewers. I want to understand logic.
Thank you :)
Can you help me figure out how to record all the participants of the room?
Thanks
Hi , First of all thanks for providing an example for making a group call using node. my problem is whenever a new user enters the room node gives me this error " Error gathering candidates". The problem is coming from this line endpoint.gatherCandidates(error => {
if (error) {
return callback(error);
}
});
and There are no videos working on the page except for the local one.
Thanks in advance
I followed your instructions, demo starts correctly but if i open 2 different windows and inserting a name and "room1" in the first one and a different name and "room1" in the second one, i only get a blue window with the room name and the button "leave room", and nothing else.
I followed your instructions but it does not work, what im doing wrong?
Hi Chapin666,
Please do let me know if you have any suggestions.
Thanks & Regards,
Sudhish
[email protected]
Hi
Can is three any sample where we can use the same web socket method for IOS native app
Hey,
I've reviewed your code and it doesn't look like sfu to me. It is Mesh topology again as shown in kurento examples. Please correct me if I am wrong.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.