var http = require('http');
var MultiStream = require('multistream');
var util = require('util');
var readableStream = require('readable-stream');
var Videostream = require('videostream');
var ut_pex = require('ut_pex');
var WebTorrent = require('webtorrent');
var SimplePeer = require('simple-peer');
/**
* @module OakStreaming
*/
module.exports = OakStreaming;
/**
* This constructor creates a OakStreaming instance. OakStreaming instances can seed and/or receive and/or relay video streams.
* In order to stream a video from one OakStreaming instance to another, a peer-to-peer connection between both OakStreaming instances has to be established.
* To build up a peer-to-peer connection between two OakStreaming instances, signaling data has to be exchanged between both instances.
* This exchange of signaling data can happen automatically via a signaling server or manually by using the createSignalingData, createSignalingDataResponse
* and procressSignalingData methods of the OakStreaming instances. OakStreaming instances can seed a video stream by using the streamVideo method.
* OakStreaming instances can receive, play back and relay a video stream by using the loadVideo method.
* OakStreaming instances can also (partly) receive a video stream from a Web server via XML HTTP Requests (XHRs).
* @constructor
*/
function OakStreaming(OakName){
var self = this;
(function(){
var OakName = OakName || Math.floor(Math.random() * Math.pow(10,300) + 1);
//console.log("Version: Archer In OakStreaming constructor. this.name: " + OakName);
// Only methods should be part of the API, i.e. only methods should be publically accessible.
// Every method should have access to these variables. Therefore they are defined at this high scope.
var simplePeerCreationCounter = 0;
var connectionsWaitingForSignalingData = [];
var theTorrent = null;
var peersToAdd = [];
var bytesReceivedFromServer = 0;
var notificationsBecauseNewWires = 0;
var SIZE_OF_VIDEO_FILE = 0;
self.streamVideo = streamVideo;
self.loadVideo = loadVideo;
self.forTesting_connectedToNewWebTorrentPeer = null;
/** This method returns the number of bytes downloaded from the Web server.
* @pulic
* @method get_number_of_bytes_downloaded_from_server
* @memberof OakStreaming
* @returns {Number}
*/
function get_number_of_bytes_downloaded_from_server (){
return bytesReceivedFromServer;
};
self.get_number_of_bytes_downloaded_from_server = get_number_of_bytes_downloaded_from_server;
/** This method returns the number of bytes downloaded from the peer-to-peer network. The return value includes bytes that were sent by the seeder.
* @pulic
* @method get_number_of_bystes_downloaded_P2P
* @memberof OakStreaming
* @returns {Number}
*/
self.get_number_of_bystes_downloaded_P2P = function(){
if(theTorrent){
return theTorrent.downloaded;
} else {
return 0;
}
};
/** This method returns the number of bytes uploaded to the peer-to-peer network.
* @pulic
* @method get_number_of_bytes_uploaded_P2P
* @memberof OakStreaming
* @returns {Number}
*/
self.get_number_of_bytes_uploaded_P2P = function(){
if(theTorrent){
return theTorrent.uploaded;
} else {
return 0;
}
};
/** This method returns the number of bytes downloaded from the peer-to-peer network.
* @pulic
* @method get_percentage_downloaded_of_torrent
* @memberof OakStreaming
* @returns {Number}
*/
self.get_percentage_downloaded_of_torrent = function(){
if(theTorrent){
return theTorrent.progress;
} else {
return 0;
}
};
/** This method returns the size in bytes of the video file that is or has been streamed/received.
* @pulic
* @method get_file_size
* @memberof OakStreaming
* @returns {Number}
*/
self.get_file_size = function(){
return SIZE_OF_VIDEO_FILE;
};
/**
* @callback OakStreaming~createSignalingData1_callback
* @param {SignalingData} signalingDataObject - Other OakStreaming instances can pass this object as an argument to their createSignalingDataResponse method in order to create another SignalingData object which is necessary for successfully finishing building up the peer-to-peer connection.
*/
/** This method creates signaling data that can be put into the createSignalingDataResponse method of another OakStreaming instance in order to manually building up a peer-to-peer connection between both OakStreaming instances.
* @pulic
* @method
* @param {OakStreaming~createSignalingData_callback} callback - This callback function gets called as soon as the signaling data has been created.
*/
self.createSignalingData = function (callback){
var alreadyCalledCallback = false;
var oakNumber = simplePeerCreationCounter;
////console.log("In createSignalingData for oakNumber: " + oakNumber);
connectionsWaitingForSignalingData[oakNumber] = new SimplePeer({initiator: true, tickle: false});
simplePeerCreationCounter++;
connectionsWaitingForSignalingData[oakNumber].on('signal', function (signalingData){
if(!alreadyCalledCallback){
alreadyCalledCallback = true;
signalingData.oakNumber = oakNumber;
callback(signalingData);
}
});
};
// This method creates (WebRTC-)signaling data as a response to singaling data of a createSignalingData method of another OakStreaming instance.
// This mehtod returns new (WebRTC-)signaling data which has to be put into processSignalingResponse method of the OakStreaming instance which created the original singaling data.
/**
* @callback OakStreaming~createSignalingData2_callback
* @param {SignalingData} signalingData - An object that the OakStreaming instance that created the initial signalingData object can pass as an argument to its processSignalingResponse method in order to build up the peer-to-peer connection.
*/
/** This method expects a signaling data object that was created by the createSignalingData method of another OakStreaming instance and generates the respective response signaling data object. In order to complete the signaling data exchange, this response signaling data object then has to be put into the processSignalingResponse method of the OakStreaming instance which has initialized the signaling.
* @pulic
* @method
* @param {SignalingData} signalingData - A signaling data object that was created by the createSignalingData method of another OakStreaming instance.
* @param {OakStreaming~createSignalingData2_callback} callback - This callback function gets called as soon as the response signaling data object has been created.
*/
self.createSignalingDataResponse = function (signalingData, callback){
var oakNumber = signalingData.oakNumber;
////console.log("In createSignalingDataResponse. In the beginning oakNumber: " + oakNumber);
signalingData.oakNumber = undefined;
var myPeer = new SimplePeer({initiator: false, tickle: false});
var index = simplePeerCreationCounter;
connectionsWaitingForSignalingData[index] = myPeer;
simplePeerCreationCounter++;
myPeer.on('signal', function (answerSignalingData){
////console.log("In createSignalingDataResponse, after onSignal oakNumber: " + oakNumber);
answerSignalingData.oakNumber = oakNumber;
////console.log("In createSignalingDataResponse, object that is returned with callback: " + JSON.stringify(answerSignalingData));
callback(answerSignalingData);
});
myPeer.signal(signalingData);
var self = this;
myPeer.on('connect', function(){
self.addSimplePeerInstance(connectionsWaitingForSignalingData[index], {}, function(){/*console.log("addSimplePeerInstance ended");*/});
});
};
// This method finally establishes a Web-RTC connection between the two OakStreaming instances. From now on both OakStreaming instances exchange video fragments.
/**
* @callback OakStreaming~createSignalingData3_callback
*/
/** This method expects a signaling data object that a createSignalingResponse method of another OakStreaming has generated based on a signaling data object that the createSignalingData method of this OakStreaming method created. After the signaling data object has been processed, this OakStreaming instance automatically builds up a peer-to-peer connection to the other OakStreaming instance. After the peer-to-peer connection has been established, both OakStreaming instances automatically exchange video fragments.
* @pulic
* @method
* @param {SignalingData} signalingData - A signaling data object that was created by the createSignalingResponse method of another OakStreaming instance. A necessary requirement is that the createSigngalingResponse method created the signaling data object based on a singaling data object that the createSignalingData method of this OakStreaming instance generated.
* @param {OakStreaming~createSignalingData3_callback} callback - This callback function gets called as soon as the peer-to-peer connection between the two OakStreaming instances has been established.
*/
self.processSignalingResponse = function (signalingData, callback){
////console.log("In processSignalingResponse, signalingData paramter: " + JSON.stringify(signalingData));
var oakNumber = signalingData.oakNumber;
signalingData.oakNumber = undefined;
////console.log("In processSignalingResponse, oakNumber: " + oakNumber);
////console.log("connectionsWaitingForSignalingData: " + connectionsWaitingForSignalingData);
var self = this;
(connectionsWaitingForSignalingData[oakNumber]).on('connect', function (){
////console.log('Established a simple-peer connection');
self.addSimplePeerInstance(connectionsWaitingForSignalingData[oakNumber]);
connectionsWaitingForSignalingData[oakNumber] = undefined;
callback();
});
////console.log("In processSignalingResponse, object that is passed to .signal(): " + JSON.stringify(signalingData));
connectionsWaitingForSignalingData[oakNumber].signal(signalingData);
};
/**
* @typedef Stream_Information
* @type {object}
* @property {number} video_file_size - The size in byte of the video file that was passed as the first argument to this method.
*/
/**
* @callback OakStreaming~streamVideoFinished
* @param {Stream_Information} stream_information_object - An object that other OakStreaming instances can pass as an argument to their loadVideo method to download the video from this and other OakStreaming instances and/or a Web server.
*/
/**
* This method creates a Stream_Information object that other OakStreaming instances can pass as an argument to their loadVideo method to download the video from this and other OakStreaming instances and/or a Web server.
* @pulic
* @method
* @param {object} video_file - The video file that should be streamed peer-to-peer to other OakStreaming instances. This paramter can either be a {@link https://developer.mozilla.org/en-US/docs/Web/API/File |W3C File object}, a {@link https://developer.mozilla.org/en-US/docs/Web/API/FileList |W3C FileList}, a {@link https://nodejs.org/api/buffer.html |Node Buffer object} or a {@link https://nodejs.org/api/stream.html#stream_class_stream_readable |Readable stream object}.
* @param {object} options - Options for the creation of the Stream_Information object. After its creation, the Stream_Information object gets passed by the streamVideo method as an argument to the callback function.
* @param {string} [options.web_server_URL] - URL of a Web server that can serve the video file. If this property is not set, XML HTTP Requests (XHRs) will be send to the Web server that served the Web page. If this property is set to false (not only falsy), no Web server will be requested.
* @param {number} [options.web_server_port = 80] - Port that will be used when communicating with the Web server that was specified in the web_server_URL property. This property should only be set when the web_server_URL property is set too.
* @param {string} [options.path_to_file_on_Web_server] - This path will be used for the XML HTTP Requests (XHRs). For example, a valid path could be "/videos/aVideoFile.mp4". The default value of this property is "/" concatenated with the name of the file that is seeded.
* @param {string} [options.hash_value] - The SHA-256 hash value of the video file that should by (partially) requested from the Web server. If this hash_value property is defined, it instead of the path_to_file_on_Web_server property will be used for XHRs.
* @param {number} [options.download_from_server_time_range = 5] - How many seconds of video playback must be buffered in advance such that no data is requested from the Web server.
* @param {string[][]} options.webTorrent_trackers - Array of arrays of WebTorrent tracking server URLs (strings). These WebTorrent trackers will be used to connect to other OakStreaming instances. In which order these tracking server a contacted is described in {@link http://www.bittorrent.org/beps/bep_0012.html}.
* @param {number} [options.Sequential_Requests_time_range = 20] - How many seconds of video playback must be buffered in advance such that no sequential data streams are requested from the WebTorrent network and instead video fragments are requested according to the rarest-peace-first strategy.
* @param {number} [options.create_readStream_request_size = 5000000] - The size of the sequential byte range requests to the WebTorrent network. Keeping the default value is sufficient for most use cases.
* @param {number} [options.peer_upload_limit_multiplier = 2] - The OakStreaming client will severly throttle the video data upload to other peers when (bytes_uploaded_to_other_peers * peer_upload_limit_multiplier + peer_upload_limit_addend > bytes_downloaded_from_other_peers). The OakStreaming client will stop the throtting as soon as the before mentioned inequality is no longer true.
* @param {number} [options.peer_upload_limit_addend = 3000000] - The OakStreaming client will severly throttle the video data upload to other peers when (bytes_uploaded_to_other_peers * peer_upload_limit_multiplier + peer_upload_limit_addend > bytes_downloaded_from_other_peers). The OakStreaming client will stop the throtting as soon as the before mentioned inequality is no longer true.
* @param {OakStreaming~streamVideoFinished} callback - This callback function gets called with the generated Stream_Information object at the end of the execution of streamVideo.
*/
function streamVideo(video_file, options, callback, returnTorrent, destroyTorrent){
var webTorrentClient = new WebTorrent();
var theTorrent;
var webTorrentFile;
//////console.log("streamVideo is executed");
//////console.log("videoFile: " + videoFile);
//////console.log("options: " + options);
//////console.log("callback: " + callback);
// options.web_server_URL &&
if(options && !options.path_to_file_on_XHR_server){
options.path_to_file_on_XHR_server = "/" + video_file.name;
}
if(options){
var stream_information_object = options;
} else {
stream_information_object = {};
}
if(video_file){
var seedingOptions = {
name: video_file.name + " - (Created by an OakStreaming client)"
};
if(options.webTorrent_trackers){
seedingOptions.announceList = options.webTorrent_trackers;
} else {
seedingOptions.announceList = [[]];
}
var self = this;
webTorrentClient.seed(video_file, seedingOptions, function(torrent){
theTorrent = torrent;
webTorrentFile = torrent.files[0];
////console.log("torrent file is seeded");
/* K42 Maybe I will need this later
var torrentFileAsBlobURL = torrent.torrentFileBlobURL;
var xhr = new XMLHttpRequest();
var XHROrMethodEndHappend = false;
xhr.open('GET', torrentFileAsBlobURL, true);
xhr.responseType = 'blob';
xhr.onload = function(e) {
if (this.status == 200) {
stream_information_object.torrentAsBlob = this.response;
if(XHROrMethodEndHappend){
callback(stream_information_object);
} else {
XHROrMethodEndHappend = true;
}
}
};
xhr.send();
*/
SIZE_OF_VIDEO_FILE = 0;
stream_information_object.size_of_video_file = 0;
for(var i=0, length=torrent.files.length; i<length; i++){
SIZE_OF_VIDEO_FILE += torrent.files[i].length;
stream_information_object.size_of_video_file += torrent.files[i].length;
}
stream_information_object.magnetURI = torrent.magnetURI;
////console.log("torrent.magnetURI: " + torrent.magnetURI);
stream_information_object.infoHash = torrent.infoHash;
stream_information_object.torrentFile = torrent.torrentFile.toString('base64');
////console.log("Creaded stream_information_object:\n" + JSON.stringify(stream_information_object));
// var bufferTorrent = parseTorrent(stream_information_object.parsedTorrent); K42
////console.log("In streamVideo " + self.OakName + ".forTesting_connectedToNewWebTorrentPeer gets created");
// This function calls the callback function when this OakStreaming instance already connected to another peer
// or as soon as it connects to another peer.
self.forTesting_connectedToNewWebTorrentPeer = function(callback){
////console.log("In streamVideo " + self.OakName + ".forTesting_connectedToNewWebTorrentPeer gets executed");
if(notificationsBecauseNewWires <= 0){
notificationsBecauseNewWires--;
var callbackCalled = false;
torrent.on('wire', function(wire){
if(!callbackCalled){
callback();
callbackCalled = true;
}
});
} else {
notificationsBecauseNewWires--;
callback();
}
};
// This is necessary such that the forTesting_connectedToNewWebTorrentPeer function knows how many peers already connected to this OakStreaming instance.
torrent.on('wire', function (wire){
notificationsBecauseNewWires++;
});
// For some Jasmine tests it is appropriate that the torrent gets destroyed immediately after the stream_information_object has been created. The destruction of the torrent stops the seeding.
if(returnTorrent === "It's a test"){
if(destroyTorrent){
notificationsBecauseNewWires = 0;
torrent.destroy();
webTorrentClient = undefined;
}
callback(stream_information_object, torrent);
} else {
callback(stream_information_object);
return stream_information_object;
}
});
} else {
callback(stream_information_object);
/* K42
if(XHROrMethodEndHappend){
callback(stream_information_object);
} else {
XHROrMethodEndHappend = true;
}
*/
}
/* Nicht löschen!!!
function updateChart(){
if(theTorrent && webTorrentFile){
document.getElementById("WebTorrent-received").innerHTML = "webTorrentFile.length: " + webTorrentFile.length + "\n torrent.uploaded: " + theTorrent.uploaded;
}
setTimeout(updateChart, 1000);
}
updateChart();
*/
}
function waitStartPlayingOffset(stream_information_object, callback, end_streaming_when_video_loaded){
if(Date.now() - timeReceiptStreamInformationObject >= startPlayingOffset){
//console.log("Video gets loaded");
timeLoadVideoMethodWasCalled = Date.now();
self.loadVideo(stream_information_object, callback, end_streaming_when_video_loaded);
} else {
setTimeout(function(){waitStartPlayingOffset(stream_information_object, callback, end_streaming_when_video_loaded);},10);
}
}
//A Wrapper for the Technical Evaluation
function loadVideo_technical_evaluation(stream_information_object, callback, end_streaming_when_video_loaded){
timeReceiptStreamInformationObject = Date.now();
waitStartPlayingOffset(stream_information_object, callback, end_streaming_when_video_loaded);
}
/**
* @callback OakStreaming~downloadingVideoFinished
*/
/**
* This method tries to receive the video stream described in the stream_information object.
* After this method has been called, the OakStreaming instance offers received video data to all OakStreaming instances with whom it is connected.
* The received video data will be streamed into the first HTML video element of the DOM.
* @pulic
* @method
* @param {Stream_Information} stream_information_object - This object contains all data that is needed to initiate downloading the video from other OakStreaming instances and/or a Web server. Stream_Information objects can be created by the {@link streamVideo|streamVideo} method.
* @param {OakStreaming~downloadingVideoFinished} callback - This callback gets called when the video has been buffered entirely.
* @param {boolean} end_streaming_when_video_downloaded - If this argument is true, all uploading to other OakStreaming instances is permanently cancelled and all processing of the loadVideo method permanently stopped as soon as the video has been downloaded completely.
*/
function loadVideo(stream_information_object, callback, end_streaming_when_video_loaded){
////console.log("loadVideo is called");
////console.log("option paramter:\n" + JSON.stringify(stream_information_object));
// This block is solely for the Technical Evaluation
/*
var timeLoadVideoMethodWasCalled = -42;
var timePlaybackWasStalled = 0;
var startUpTime = 0;
var timeTillTorrentOnDone = -42;
var startPlayingOffset = Math.floor(Math.random() * 10) + 1;
*/
var myVideo = document.getElementsByTagName('video')[0];
myVideo.addEventListener('error', function (err){
console.error(myVideo.error);
});
/*
myVideo.onplay = function(){
onsole.log("event onplay is thrown");
play = true;
if(canplay){
startUpTime = Date.now() - timeLoadVideoMethodWasCalled;
timePlaybackWasStalled += startUpTime;
videoStartUpOver = true;
}
};
*/
//var userPausedVideo = false;
/*
myVideo.pause = function(){
userPausedVideo = true;
};
*/
/*
myVideo.onwaiting = function() {
//console.log("Video is holded at " + (Date.now() - timeLoadVideoMethodWasCalled) + " miliseconds after loadVideo has been called.");
lastTimeWhenVideoHolded = Date.now();
};
*/
/*
myVideo.onstalled = function() {
//console.log("Video is stalled at " + (Date.now() - timeLoadVideoMethodWasCalled) + " miliseconds after loadVideo has been called.");
lastTimeWhenVideoHolded = Date.now();
};
*/
/*
myVideo.onplaying = function(){
if(playbackStopped){// && !userPausedVideo){
//console.log("Video is playing again after " + (Date.now() - lastTimeWhenVideoHolded) + " miliseconds.");
timePlaybackWasStalled += Date.now() - lastTimeWhenVideoHolded;
playbackStopped = false;
}
//userPausedVideo = false;
};
*/
// All these declared varibales until 'var self = this' are intended to be constants
var deliveryByServer = (stream_information_object.web_server_URL !== false && (stream_information_object.path_to_file_on_XHR_server || stream_information_object.hash_value)) ? true : false;
var deliveryByWebtorrent = stream_information_object.torrentFile ? true : false;
var XHRServerURL = stream_information_object.web_server_URL || false;
var XHR_PORT = stream_information_object.web_server_port || 80;
var pathToFileOnXHRServer = stream_information_object.path_to_file_on_XHR_server;
var hashValue = stream_information_object.hash_value;
//var webTorrentTrackers = stream_information_object.webTorrent_trackers;
var MAGNET_URI = stream_information_object.magnetURI;
////console.log("MAGNET_URI: " + MAGNET_URI);
var THE_RECEIVED_TORRENT_FILE = Buffer.from(stream_information_object.torrentFile, 'base64');
SIZE_OF_VIDEO_FILE = stream_information_object.size_of_video_file;
////console.log("stream_information_object.size_of_video_file: " + stream_information_object.size_of_video_file);
var DOWNLOAD_FROM_P2P_TIME_RANGE = stream_information_object.Sequential_Requests_time_range || 20; // eigentlich 20
var CREATE_READSTREAM_REQUEST_SIZE = stream_information_object.create_readStream_request_size || 6000000; // 12000000
var MINIMAL_TIMESPAN_BEFORE_NEW_WEBTORRENT_REQUEST = stream_information_object.minimal_timespan_before_new_webtorrent_request || 3; // in seconds
var DOWNLOAD_FROM_SERVER_TIME_RANGE = stream_information_object.download_from_server_time_range || 3; // vorher 3 (Das mit den 6MB beim start-up) eigentlich 5
var UPLOAD_LIMIT = stream_information_object.peer_upload_limit_multiplier || 2;
var ADDITION_TO_UPLOAD_LIMIT = stream_information_object.peer_upload_limit_addend || 3000000; // war vorer 500000
var XHR_REQUEST_SIZE = stream_information_object.xhrRequestSize || 2000000; // in byte 2000000
var THRESHOLD_FOR_RETURNING_OF_ANSWER_STREAM = stream_information_object.thresholdForReturningAnswerStream || 1000000; // in byte 1000000
var WATERMARK_HEIGHT_OF_ANSWERSTREAM = stream_information_object.watermarkHeightOfAnswerStream || 1999999; // in byte 1999999
var CHECK_IF_BUFFER_FULL_ENOUGH_INTERVAL = stream_information_object.checkIfBufferFullEnoughInterval || 500; // in miliseconds
var CHECK_IF_ANSWERSTREAM_READY_INTERVAL = stream_information_object.checkIfAnswerstreamReadyInterval || 200; // in miliseconds
var UPDATE_CHART_INTERVAL = stream_information_object.updateChartInterval || 1000; // in miliseconds
var CHOKE_IF_NECESSARY_INTERVAL = stream_information_object.chokeIfNecessaryInterval || 300; // in miliseconds Eigentlich erst 500 dann 200 dann 100
var CHECK_IF_NEW_CREATE_READSTREAM_NECESSARY_INTERVAL = stream_information_object.checkIfNewCreateReadstreamInterval || 2000 ; // 2000
// From here on most newly declared variables are not indeted to function as constants
// These variables are declared in this high scope in order to allow every function that is declared in loadVideo to access and manipulate these variables
var self = this;
var endStreaming = false;
var webTorrentClient = null;
var wires = []; // a wire is a connection to another peer out of the WebTorrent network
var globalvideostreamRequestNumber = 0;
bytesReceivedFromServer = 0; // This variable gets only initialized not declared
var webTorrentFile;
var videostreamRequestHandlers = [];
var inCritical = true; // incritical means that XHR requests will be conducted because there is less than DOWNLOAD_FROM_SERVER_TIME_RANGE seconds of video playback buffered.
var videoCompletelyLoadedByVideoPlayer = false;
var bytesTakenFromWebTorrent = 0;
var bytesTakenFromServer = 0;
var consoleCounter = 0; // This variable is only for debugging purposes
// var first2000BytesOfVideo = null; war zu verwirrend
// var numberBytesInFirst2000BytesOfVideo = 0; war zu verwirrend
var VideoCompletelyLoadedByWebtorrent = false;
var timeOfLastWebTorrentRequest = 0;
// Node.js readable streams are used to buffer video data before it gets put into the source buffer
function MyReadableStream(options){
readableStream.Readable.call(this, options);
}
util.inherits(MyReadableStream, readableStream.Readable);
MyReadableStream.prototype._read = function(size){};
if(deliveryByWebtorrent){
////console.log("entered if(deliveryByWebtorrent)");
webTorrentClient = new WebTorrent();
var webTorrentOptions = {announce: []};
/* Weiß nicht mehr warum das hier steht
if(stream_information_object.pathToFileToSeed){
webTorrentOptions.path = stream_information_object.pathToFileToSeed;
}
*/
// A magnetURI contains URLs to tracking servers and the info hash of the torrent.
//The client receives the complete torrent file from a tracking server.
webTorrentClient.add(THE_RECEIVED_TORRENT_FILE, webTorrentOptions, function (torrent){
// From this point on the WebTorrent instance will download video data from the WebTorrent network in the background in a rarest-peace-first manner as fast as possible.
// Sequential stream request like createreadstrime are prioritized over this rarest-peace-first background downloading.
////console.log("webTorrentClient.add torrent meta data ready");
theTorrent = torrent;
webTorrentFile = torrent.files[0];
torrent.on('done', function () {
VideoCompletelyLoadedByWebtorrent = true;
});
// Peers which used the offered methods to manually connect to this OakStreaming instance
// before a torrent file was loaded are added now to the set of peers that are used for video data exchange.
for(var j=0; j< peersToAdd.length; j++){
theTorrent.addPeer(peersToAdd[j][0]);
if(peersToAdd[j][1]){
(peersToAdd[j][1])();
}
}
// This function has the same purpose
////console.log("In loadVideo " + self.OakName + ".forTesting_connectedToNewWebTorrentPeer gets created");
// This function calls the callback function when this OakStreaming instance already connected to another peer
// or as soon as it connects to another peer.
self.forTesting_connectedToNewWebTorrentPeer = function(callback){
////console.log("In loadVideo " + self.OakName + ".forTesting_connectedToNewWebTorrentPeer gets called");
if(notificationsBecauseNewWires <= 0){
notificationsBecauseNewWires--;
var callbackCalled = false;
torrent.on('wire', function(wire){
if(!callbackCalled){
callback();
callbackCalled = true;
}
});
} else {
notificationsBecauseNewWires--;
callback();
}
};
torrent.on('wire', function (wire){
////console.log("torrent.on('wire', ..) is fired");
wires.push(wire);
if(!window.firstWire){
window.firstWire = wire;
}
notificationsBecauseNewWires++;
// This activates the ut_pex extension for this peer
// which is necessary to exchange peers between WebTorrent instances
wire.use(ut_pex());
//wire.ut_pex.start();
/*
wire.ut_pex.on('peer', function (peer){
theTorrent.addPeer(peer);
// got a peer
// probably add it to peer connections queue
});
*/
});
// The Videostream object conducts, depending on the current playback position, creaReadstream requests for video data that the loadVideo method has to answer in order to play back the video successfully
// The Videostream object probably has been created during the time webTorrentClient.add was called until its called the callback function.
// In this time the VideoStream object created by the videostream library probably has conducted some createReadstream requests whose handlers are saved in the videostreamRequestHandlers array.
// For these requests we now intialize WebTorrent streams.
for(var i=0, length=videostreamRequestHandlers.length; i<length; i++){
var thisRequest = videostreamRequestHandlers[i];
// To answer a createReadStream request a Multistream (https://www.npmjs.com/package/multistream) is returned which requests a Node readableStream as soon as its buffer has went dry.
// The current callback which should be called with the created readableStream is saved in currentlyExpectedCallback
if(thisRequest.currentlyExpectedCallback !== null){
////console.log("In onTorrent nachträglich webtorrent stream erzeugen thisRequest.start: " + thisRequest.start);
//////console.log("In onTorrent webTorrentFile.length: " + webTorrentFile.length);
if(myVideo.duration){
timeOfLastWebTorrentRequest = myVideo.currentTime;
} else {
timeOfLastWebTorrentRequest = 0;
}
var endCreateReadStream;
if(thisRequest.start + CREATE_READSTREAM_REQUEST_SIZE > webTorrentFile.length-1){
endCreateReadStream = webTorrentFile.length-1;
} else {
endCreateReadStream = thisRequest.start + CREATE_READSTREAM_REQUEST_SIZE;
}
thisRequest.webTorrentStream = webTorrentFile.createReadStream({"start" : thisRequest.start, "end" : endCreateReadStream});
/*
thisRequest.on('end', function(){
if(thisRequest.currentlyExpectedCallback !== null && thisRequest.start > thisRequest.lastEndCreateReadStream && thisRequest.start < thisRequest.videoFileSize){
var endCreateReadStream;
if(thisRequest.start + CREATE_READSTREAM_REQUEST_SIZE >= webTorrentFile.length-1){
endCreateReadStream = webTorrentFile.length-1;
} else {
endCreateReadStream = thisRequest.start + CREATE_READSTREAM_REQUEST_SIZE;
}
thisRequest.webTorrentStream = webTorrentFile.createReadStream({"start" : thisRequest.start, "end" : endCreateReadStream});
thisRequest.oldStartWebTorrent = thisRequest.start;
thisRequest.webTorrentStream.unpipe();
thisRequest.webTorrentStream.pipe(thisRequest.collectorStreamForWebtorrent);
}
});
*/
// Every videostreamRequestHandler has to save the byte index that it expects next
thisRequest.oldStartWebTorrent = thisRequest.start;
thisRequest.lastEndCreateReadStream = endCreateReadStream;
// Data that is received from the WebTorrent readable gets immmediately pumped into a writeable stream called collectorStreamForWebtorrent which processes the new data.
thisRequest.webTorrentStream.pipe(thisRequest.collectorStreamForWebtorrent);
}
}
});
}
// first2000BytesOfVideo = new MyReadableStream({highWaterMark: 2000}); hatte ich ja rausgenommen weil verfangen
var fileLikeObject = function (pathToFileOnXHRServer){
this.pathToFileOnXHRServer = pathToFileOnXHRServer;
};
// The VideoStream object will call createReadStream several times with different values for the start property of ops.
fileLikeObject.prototype.createReadStream = function (opts){
if(opts.start >= SIZE_OF_VIDEO_FILE){
////console.log("opts.start > SIZE_OF_VIDEO_FILE therefore cb(null,null) every time");
return (new MultiStream(function (cb){cb(null, null);}));
}
inCritical = true;
var thisRequest = new VideostreamRequestHandler(++globalvideostreamRequestNumber, opts, this);
//console.log(" called createreadStream: createRequestNumber: " + thisRequest.createReadStreamNumber);
//console.log("opts.start: " + opts.start);
//console.log(" opts.end: " + opts.end);
// Everytime I printed out the value of opts.end is was NaN.
// I suppose that should be interpreted as "till the end of the file"
// Of course, our returned stream should, nevertheless, not buffer a giant amount of video data in advance but instead retrieve and put out chunks of video data on-demand
if(opts.end && !isNaN(opts.end)){
thisRequest.end = opts.end + 1;
} else {
if(SIZE_OF_VIDEO_FILE !== 0){
thisRequest.end = SIZE_OF_VIDEO_FILE;
}
}
// This writeable Node.js stream will process every data that is received from sequential WebTorrent streams
thisRequest.MyWriteableStream = function(highWaterMark){
readableStream.Writable.call(this, highWaterMark);
};
util.inherits(thisRequest.MyWriteableStream, readableStream.Writable);
thisRequest.MyWriteableStream.prototype._write = function(chunk, encoding, done){
//console.log("A chunk from the WebTorrent network has been received. It's size is: " + chunk.length);
////console.log("MyWriteableStream _write is called");
////console.log("A byte range request to the WebTorrent network received a chunk");
if(thisRequest.start-thisRequest.oldStartWebTorrent < chunk.length){
////////////console.log("MyWriteableStream _write: pushing received data in answerStream")
bytesTakenFromWebTorrent += chunk.length - (thisRequest.start-thisRequest.oldStartWebTorrent);
var streamHasMemoryLeft = thisRequest.answerStream.push(chunk.slice(thisRequest.start-thisRequest.oldStartWebTorrent, chunk.length));
thisRequest.bytesInAnswerStream += chunk.length - (thisRequest.start-thisRequest.oldStartWebTorrent);
thisRequest.start += chunk.length - (thisRequest.start-thisRequest.oldStartWebTorrent);
if(streamHasMemoryLeft){
if(thisRequest.currentlyExpectedCallback !== null && thisRequest.start >= thisRequest.end){
var theCallbackFunction = thisRequest.currentlyExpectedCallback;
thisRequest.currentlyExpectedCallback = null;
thisRequest.answerStream.push(null);
thisRequest.bytesInAnswerStream = 0;
var res = thisRequest.answerStream;
thisRequest.answerStream = new MyReadableStream({highWaterMark: WATERMARK_HEIGHT_OF_ANSWERSTREAM});
//////console.log("called CB with data out of answerStream from videostreamRequest number " + thisRequest.createReadStreamNumber);
if (thisRequest.webTorrentStream){
//thisRequest.webTorrentStream.pause(); 11.07.16 more a try Sollte höchst wahrscheinlich aus code raus
}
theCallbackFunction(null, res);
} else {
ceckIfAnswerStreamReady(thisRequest);
}
} else {
if(thisRequest.currentlyExpectedCallback === null){
if(thisRequest.webTorrentStream){
//thisRequest.webTorrentStream.pause();
}
thisRequest.noMoreData = true;
} else {
var theCallbackFunction = thisRequest.currentlyExpectedCallback;
thisRequest.currentlyExpectedCallback = null;
thisRequest.answerStream.push(null);
thisRequest.bytesInAnswerStream = 0;
var res = thisRequest.answerStream;
thisRequest.answerStream = new MyReadableStream({highWaterMark: WATERMARK_HEIGHT_OF_ANSWERSTREAM});
//////console.log("called CB with data out of answerStream from videostreamRequest number " + thisRequest.createReadStreamNumber);
if (thisRequest.webTorrentStream){
//thisRequest.webTorrentStream.pause();
}
theCallbackFunction(null, res);
}
}
}
thisRequest.oldStartWebTorrent += chunk.length;
//ceckIfAnswerStreamReady(thisRequest);
done();
};
thisRequest.collectorStreamForWebtorrent = new thisRequest.MyWriteableStream({highWaterMark: 16});
videostreamRequestHandlers.push(thisRequest);
if(webTorrentFile){ // Um Einhaltung des Upload limits kümmert sich doch chokeIfNecessary && theTorrent.uploaded <= UPLOAD_LIMIT * theTorrent.downloaded + ADDITION_TO_UPLOAD_LIMIT){
////////////console.log("after new videostreamRequest creating a corresponding webtorrent stream");
////////console.log("opts.start: " + opts.start);
////////console.log("webTorrentFile.length: " + webTorrentFile.length);
if(myVideo.duration){
timeOfLastWebTorrentRequest = myVideo.currentTime;
} else {
timeOfLastWebTorrentRequest = 0;
}
var endCreateReadStream;
if(thisRequest.start + CREATE_READSTREAM_REQUEST_SIZE >= webTorrentFile.length-1){
endCreateReadStream = webTorrentFile.length-1;
} else {
endCreateReadStream = thisRequest.start + CREATE_READSTREAM_REQUEST_SIZE;
}
thisRequest.webTorrentStream = webTorrentFile.createReadStream({"start" : thisRequest.start, "end" : endCreateReadStream});
thisRequest.lastEndCreateReadStream = endCreateReadStream;
thisRequest.oldStartWebTorrent = thisRequest.start;
thisRequest.webTorrentStream.pipe(thisRequest.collectorStreamForWebtorrent);
}
function factoryFunctionForStreamCreation(cb){
//console.log("ReadableStream request number " + thisRequest.createReadStreamNumber + " does a cb request");
if(thisRequest.end >= 0 && thisRequest.start >= thisRequest.end){
////console.log("called cb(null,null) from " + thisRequest.createReadStreamNumber);
if (thisRequest.req) {
thisRequest.req.destroy();
thisRequest.req = null;
}
////console.log("cb(null, null) is called");
return cb(null, null);
}
thisRequest.callbackNumber++;
//////////console.log(thisRequest.callbackNumber + ". call of function(cb) from " + videostreamRequestNumber);
////////////console.log(start: " + thisRequest.start);
thisRequest.currentlyExpectedCallback = cb;
thisRequest.noMoreData = false;
/* Erstmal rausgenommen, da ich mich beim XHRHanlder part dazu irgendwie verhäddert habe
if(firstBytesOfVideo && (thisRequest.start < firstBytesOfVideo.length - 200) && thisRequest.createReadStreamNumber < 5){ // Beim 10 mins big buck bunny video ist eben der erste lange createReadStream (und einzige?) createReadStream Nummer 5
thisRequest.answerStream.push(firstBytesOfVideo.slice(thisRequest.start, firstBytesOfVideo.length));
bytesTakenFromServer += firstBytesOfVideo.length - thisRequest.start;
thisRequest.start += firstBytesOfVideo.length - thisRequest.start;
var theCallbackFunction = thisRequest.currentlyExpectedCallback;
thisRequest.currentlyExpectedCallback = null;
thisRequest.answerStream.push(null);
thisRequest.bytesInAnswerStream = 0;
var res = thisRequest.answerStream;
if(thisRequest.createReadStreamNumber < 4){
thisRequest.answerStream = new MyReadableStream({highWaterMark: 2000});
} else {
thisRequest.answerStream = new MyReadableStream({highWaterMark: WATERMARK_HEIGHT_OF_ANSWERSTREAM});
}
//console.log("A CB has been answered with a part of firstBytesOfVideo for readstream number " + thisRequest.createReadStreamNumber + " with callback number " + thisRequest.callbackNumber);
theCallbackFunction(null, res);
return;
}
*/
if(!ceckIfAnswerStreamReady(thisRequest)){
if(thisRequest.webTorrentStream){
// thisRequest.webTorrentStream.resume(); 11.07.16 more a try
} else if(webTorrentFile){
////////////console.log("New cb function was called and I subsequently create a new torrentStream for it because non existed before for this videostreamRequest");
////////console.log("After new Multistream. thisRequest.start: " + thisRequest.start);
////////console.log("webTorrentFile.length: " + webTorrentFile.length);
var endCreateReadStream;
if(thisRequest.start + CREATE_READSTREAM_REQUEST_SIZE >= webTorrentFile.length-1){
endCreateReadStream = webTorrentFile.length-1;
} else {
endCreateReadStream = thisRequest.start + CREATE_READSTREAM_REQUEST_SIZE;
}
thisRequest.webTorrentStream = webTorrentFile.createReadStream({"start" : thisRequest.start, "end" : endCreateReadStream});
thisRequest.lastEndCreateReadStream = endCreateReadStream;
thisRequest.oldStartWebTorrent = thisRequest.start;
thisRequest.webTorrentStream.pipe(thisRequest.collectorStreamForWebtorrent);
}
if(deliveryByServer && inCritical && !thisRequest.XHRConducted){
if(!webTorrentFile || !VideoCompletelyLoadedByWebtorrent){
conductXHR(thisRequest);
}
}
}
}
// A new Multistream gets created which will be the answer the the request from the VideoStream request
var multi = new MultiStream(factoryFunctionForStreamCreation);
var deconstructorAlreadyCalled = false;
////////////console.log(" terminate createReadStream");
var destroy = multi.destroy;
multi.destroy = function(){
if(deconstructorAlreadyCalled){
////console.log("Deconstructor of " + thisRequest.createReadStreamNumber + " has already been called");
return;
}
//console.log("Deconstructor of " + thisRequest.createReadStreamNumber + " is called");
////console.log("In deconstructor call thisRequest.start has value: " + thisRequest.start);
////console.log("In deconstructor call thisRequest.end has value: " + thisRequest.end);
////console.log("In deconstructor call thisRequest.currentlyExpectedCallback === null: " + (thisRequest.currentlyExpectedCallback === null));
deconstructorAlreadyCalled = true;
if (thisRequest.req) {
thisRequest.req.destroy();
}
var theCallback = thisRequest.currentlyExpectedCallback;
thisRequest.currentlyExpectedCallback = null;
thisRequest.noMoreData = true;
if(thisRequest.webTorrentStream){
thisRequest.webTorrentStream.pause();
thisRequest.webTorrentStream.unpipe();
}
for(var i=0; i<videostreamRequestHandlers.length; i++){
if(videostreamRequestHandlers[i] === thisRequest){
videostreamRequestHandlers.splice(i, 1);
}
}
/*
if(theCallback){
theCallback(null,null);
}
*/
// thisRequest.webTorrentStream.destroy(); Glaube ich unnötig!!!!
thisRequest.answerStream.resume(); // To avoid memory leaks. Ich sammel ja schon datan während ich auf den nächsten call von der streamFactoryFunction warte
// thisRequest.collectorStreamForWebtorrent.destroy(); Verbraucht ja eh nur ein paar byte
destroy.call(multi);
};
return multi;
};
// This function frequently checks if less than DOWNLOAD_FROM_P2P_TIME_RANGE seconds of video data is buffered in advance.
// If it is the case this function conducts a new sequential byte range request to the WebTorrent network
function frequentlyCheckIfNewCreateReadStreamNecessary(){
if(videoCompletelyLoadedByVideoPlayer){
return;
}
////console.log("frequentlyCheckIfNewCreateReadStreamNecessary gets executed");
/* Working version where only a minimal time limit is set when a new createReadStream to WebTorrent network is conducted
if(myVideo.duration){
////console.log("In if(myvideo.duration)");
if(theTorrent && (myVideo.currentTime - timeOfLastWebTorrentRequest >= MINIMAL_TIMESPAN_BEFORE_NEW_WEBTORRENT_REQUEST)){
for (var j = 0, length = videostreamRequestHandlers.length; j < length; j++) {
var thisRequest = videostreamRequestHandlers[j];
//console.log("createReadStream enlargement for request " + thisRequest.createReadStreamNumber);
if(thisRequest.currentlyExpectedCallback !== null && thisRequest.start > thisRequest.lastEndCreateReadStream && thisRequest.start < SIZE_OF_VIDEO_FILE){
timeOfLastWebTorrentRequest = myVideo.currentTime;
var endCreateReadStream;
if(thisRequest.start + CREATE_READSTREAM_REQUEST_SIZE >= webTorrentFile.length-1){
endCreateReadStream = webTorrentFile.length-1;
} else {
endCreateReadStream = thisRequest.start + CREATE_READSTREAM_REQUEST_SIZE;
}
//console.log("I set a new createReadstream for videostream request number " + thisRequest.createReadStreamNumber);
thisRequest.webTorrentStream.unpipe();
thisRequest.webTorrentStream = webTorrentFile.createReadStream({"start" : thisRequest.start, "end" : endCreateReadStream});
thisRequest.lastEndCreateReadStream = endCreateReadStream;
thisRequest.oldStartWebTorrent = thisRequest.start;
thisRequest.collectorStreamForWebtorrent = new thisRequest.MyWriteableStream({highWaterMark:16});
thisRequest.webTorrentStream.pipe(thisRequest.collectorStreamForWebtorrent);
}
}
}
}
*/
// Alte Variante wo ich mir noch die Buffer Bereiche des video players angeschaut habe
var timeRanges = myVideo.buffered;
for (var i = 0, length = timeRanges.length; i < length; i++){
if (myVideo.currentTime >= timeRanges.start(i) && myVideo.currentTime <= timeRanges.end(i)+1){ // war vorher timeRanges.end(i)+3
if (timeRanges.end(i) - myVideo.currentTime <= DOWNLOAD_FROM_P2P_TIME_RANGE) {
for (var j = 0, length2 = videostreamRequestHandlers.length; j < length2; j++) {
var thisRequest = videostreamRequestHandlers[j];
//console.log("createReadStream enlargement for request " + thisRequest.createReadStreamNumber);
if(theTorrent && thisRequest.currentlyExpectedCallback !== null && thisRequest.start > thisRequest.lastEndCreateReadStream && thisRequest.start < SIZE_OF_VIDEO_FILE){
var endCreateReadStream;
if(thisRequest.start + CREATE_READSTREAM_REQUEST_SIZE >= webTorrentFile.length-1){
endCreateReadStream = webTorrentFile.length-1;
} else {
endCreateReadStream = thisRequest.start + CREATE_READSTREAM_REQUEST_SIZE;
}
//console.log("I set a new createReadstream for videostream request number " + thisRequest.createReadStreamNumber);
thisRequest.webTorrentStream.unpipe();
thisRequest.webTorrentStream = webTorrentFile.createReadStream({"start" : thisRequest.start, "end" : endCreateReadStream});
thisRequest.lastEndCreateReadStream = endCreateReadStream;
thisRequest.oldStartWebTorrent = thisRequest.start;
thisRequest.collectorStreamForWebtorrent = new thisRequest.MyWriteableStream({highWaterMark:16});
thisRequest.webTorrentStream.pipe(thisRequest.collectorStreamForWebtorrent);
}
}
}
}
}
//
setTimeout(frequentlyCheckIfNewCreateReadStreamNecessary, CHECK_IF_NEW_CREATE_READSTREAM_NECESSARY_INTERVAL);
}
// The final version of the library should not include this function. This function updates the statistics that are shown above the video. This version shows values which are not intended for end user use.
/* NICHT LÖSCHEN!!!!!!!!!!!!!!!!!
function updateChart(){
if(endStreaming){
return;
}
if(theTorrent && webTorrentFile){
document.getElementById("WebTorrent-received").innerHTML = "webTorrentFile.length: " + webTorrentFile.length + "\n torrent.downloaded: " + theTorrent.downloaded + "\n torrent.received: " + theTorrent.downloaded + "\n torrent.uploaded: " + theTorrent.uploaded + "\n torrent.progress: " + theTorrent.progress + "\n Bytes received from server: " + bytesReceivedFromServer + "\n Bytes taken from server delivery: " + bytesTakenFromServer + "\n Bytes taken from WebTorrent delivery: " + bytesTakenFromWebTorrent;
}
setTimeout(updateChart, UPDATE_CHART_INTERVAL);
}
*/
// This function checks for a given videostreamRequestHandler if we have called enough video data to call the callback function.
// If it is the case, the callback function gets called togehter with the buffered data.
function ceckIfAnswerStreamReady(thisRequest){
////////////console.log("At the beginning of thisRequest.bytesInAnswerStream: " + thisRequest.bytesInAnswerStream);
////////////console.log("In ceckIfAnswerStreamReady of videostreamRequest number " + thisRequest.createReadStreamNumber + ". thisRequest.bytesInAnswerStream: " + thisRequest.bytesInAnswerStream + " thisRequest.currentlyExpectedCallback: " + thisRequest.currentlyExpectedCallback);
if ((thisRequest.createReadStreamNumber < 4 && thisRequest.currentlyExpectedCallback && thisRequest.bytesInAnswerStream >= 2000) || (thisRequest.currentlyExpectedCallback && ((thisRequest.bytesInAnswerStream >= THRESHOLD_FOR_RETURNING_OF_ANSWER_STREAM) || (thisRequest.start >= SIZE_OF_VIDEO_FILE)))){
////////////console.log("answerStream from videostream Request number " + thisRequest.createReadStreamNumber + " and CB number " + thisRequest.callbackNumber + " gets returned");
// //////////console.log("Returing answerStream out of ceckIfAnswerStreamReady()");
var theCallbackFunction = thisRequest.currentlyExpectedCallback;
thisRequest.currentlyExpectedCallback = null;
thisRequest.answerStream.push(null);
if (thisRequest.webTorrentStream){
//thisRequest.webTorrentStream.pause();
}
thisRequest.bytesInAnswerStream = 0;
var res = thisRequest.answerStream;
thisRequest.answerStream = new MyReadableStream({highWaterMark: WATERMARK_HEIGHT_OF_ANSWERSTREAM});
//////console.log("called CB with data out of answerStream from videostreamRequest number " + thisRequest.createReadStreamNumber);
theCallbackFunction(null, res);
return true;
}
return false;
}
// This function frequently checks if video data upload should be throttled because the peer_upload_limit is reached
// If it should be throttled, then every peer gets choked which means there will be no more data send to other peers for approximately 800 milliseconds.
function chokeIfNecessary(){
if (theTorrent && theTorrent.uploaded >= theTorrent.downloaded * UPLOAD_LIMIT + ADDITION_TO_UPLOAD_LIMIT) {
/* mache ich schon in einer anderen frequent methode
if(videoCompletelyLoaded){
theTorrent.destroy();
webTorrentClient = undefined;
endStreaming = true;
return;
}
*/
for (var i = 0, length = wires.length; i < length; i++){
//////console.log("I choked a peer");
wires[i].choke();
}
}
setTimeout(chokeIfNecessary, CHOKE_IF_NECESSARY_INTERVAL);
}
function VideostreamRequestHandler(createReadStreamNumber, opts, self){
this.createReadStreamNumber = createReadStreamNumber;
this.opts = opts;
this.start = opts.start || 0;
this.oldStartWebTorrent = -42;
this.oldStartServer = -42;
this.currentlyExpectedCallback = null;
this.callbackNumber = 0;
this.webTorrentStream = null;
if(createReadStreamNumber < 4){ // war vorher === 1 stat < 4
this.answerStream = new MyReadableStream({highWaterMark: 2000});
} else {
this.answerStream = new MyReadableStream({highWaterMark: WATERMARK_HEIGHT_OF_ANSWERSTREAM});
}
this.bytesInAnswerStream = 0;
this.collectorStreamForWebtorrent = null;
this.XHRConducted = false;
this.end = -42;
this.self = self;
this.bytesTakenFromWebTorrent = 0;
this.bytesTakenFromServer = 0;
this.noMoreData = false;
this.req = null;
this.lastEndCreateReadStream = -42;
this.XHR_filesize = -42;
this.MyWriteableStream = null;
}
// This function frequently checks for every videostreamRequestHandler if there is enough data buffer to call the corresponding callback function with the buffered data
/* Brauche ich soviel ich weiß nicht
function frequentlyCeckIfAnswerStreamReady(){
if(videoCompletelyLoadedByVideoPlayer){
return;
}
for (var i = 0, length = videostreamRequestHandlers.length; i < length; i++) {
ceckIfAnswerStreamReady(videostreamRequestHandlers[i]);
}
setTimeout(frequentlyCeckIfAnswerStreamReady, CHECK_IF_ANSWERSTREAM_READY_INTERVAL);
}
*/
// The job of this function is to frequently check to things.
// First, if the video is completely loaded.
// Second, if less than DOWNLOAD_FROM_SERVER_TIME_RANGE seconds of video playback are buffered in advance.
function checkIfBufferFullEnough(justOnce){
//////console.log("checkIfBufferFullEnough is called");
if(videoCompletelyLoadedByVideoPlayer){
return;
}
//////console.log("video.duration: " + myVideo.duration);
if(myVideo.duration){
var timeRanges = myVideo.buffered;
if(timeRanges.length >= 1){
//////console.log("timeRanges.start(0): " + timeRanges.start(0));
//////console.log("timeRanges.end(0): " + timeRanges.end(0));
if(timeRanges.start(0) == 0 && timeRanges.end(0) == myVideo.duration){
// ////console.log("In checkIfBufferFullEnough: callback should be called");
videoCompletelyLoadedByVideoPlayer = true; // brauche da verschiende boolean werte
//console.log("My program thinks the wohle video has been loaded into the video Player buffer");
if(callback){
if(end_streaming_when_video_loaded){
callback();
} else {
callback(theTorrent);
}
}
if(end_streaming_when_video_loaded){
if(theTorrent){
theTorrent.destroy();
webTorrentClient = null;
}
endStreaming = true;
return;
}
}
}
// From here on it is checked wether there are less seconds buffered than DOWNLOAD_FROM_SERVER_TIME_RANGE
inCritical = true;
for (var i = 0, length = timeRanges.length; i < length; i++) {
////////////console.log("Time range number " + i + ": start(" + timeRanges.start(i) + ") end(" + timeRanges.end(i) + ")");
if (myVideo.currentTime >= timeRanges.start(i) && myVideo.currentTime <= timeRanges.end(i)+1) {
if (timeRanges.end(i) - myVideo.currentTime >= DOWNLOAD_FROM_SERVER_TIME_RANGE) {
inCritical = false;
////////////console.log("I set inCritical to false");
}
}
}
if (deliveryByServer && inCritical) {
for (var j = 0, length = videostreamRequestHandlers.length; j < length; j++) {
if ((!webTorrentFile || !VideoCompletelyLoadedByWebtorrent) && videostreamRequestHandlers[j].currentlyExpectedCallback !== null && videostreamRequestHandlers[j].XHRConducted === false) {
conductXHR(videostreamRequestHandlers[j]);
}
}
}
}
if(!justOnce){
setTimeout(checkIfBufferFullEnough, CHECK_IF_BUFFER_FULL_ENOUGH_INTERVAL);
}
}
// This function conductes a XHR reuqest for the videostreamRequestHandler which is handed over to the function as its first and only paramter.
function conductXHR(thisRequest) {
//console.log("In conductXHR");
if(thisRequest.currentlyExpectedCallback === null){
return;
}
thisRequest.XHRConducted = true;
var reqStart = thisRequest.start;
if(thisRequest.createReadStreamNumber < 4){ // War vorher === 1 statt < 4
//console.log("Because createReadStreamNumber <4 only a 2000byte XHR is conducted");
var reqEnd = reqStart + 2000;
} else {
var reqEnd = reqStart + XHR_REQUEST_SIZE;
}
if(thisRequest.XHR_filesize > 0 && reqEnd > thisRequest.XHR_filesize){
reqEnd = thisRequest.XHR_filesize;
} else if (thisRequest.end >= 0 && reqEnd > thisRequest.end) {
reqEnd = thisRequest.end;
}
if (reqStart >= reqEnd){
//////console.log("called cb(null,null)");
// !!!!!!!!! dieser if block neu fix versuch
if(thisRequest.req){
thisRequest.req.destroy();
}
thisRequest.XHRConducted = false;
if(thisRequest.currentlyExpectedCallback){
return thisRequest.currentlyExpectedCallback(null, null);
} else {
return;
}
}
/* glaube ich unnötiger und/oder gefährlicher müll
if (reqStart >= reqEnd) {
req = null;
return thisRequest.currentlyExpectedCallback(null, null);
}
*/
if (consoleCounter < 10000000) {
//////////////console.log(consoleCounter++ + " videoStream " + thisRequest.createReadStreamNumber + " CB number " + thisRequest.callbackNumber + " reqStart: " + reqStart);
//////////////console.log(consoleCounter++ + " Multistream " + thisRequest.createReadStreamNumber + " CB number " + thisRequest.callbackNumber + " reqEnd: " + reqEnd);
}
var XHRDataHandler = function (chunk){
bytesReceivedFromServer += chunk.length;
// thisRequest.oldStartServer += chunk.length; War noch vom meinem 2000 byte buffer versuch
//////console.log("ReadableStream request number " + thisRequest.createReadStreamNumber + " received a chunk of length " + chunk.length);
/* Erstmal rausgenommen weil ich darauf net klar kam. Etwas zu verwirrend
if(numberBytesInfirst2000BytesOfVideo < 2000 && thisRequest.start == numberBytesInfirst2000BytesOfVideo){
//console.log("Size of firstBytesOfVideo in bytes: " + chunk.length);
numberBytesInFirst2000BytesOfVideo += chunk.length; //<= (2000-numberBytesInFirst2000BytesOfVideo) ? chunk.length : 2000;
first2000BytesOfVideo = Buffer.concat([first2000BytesOfVideo, chunk]); // chunk.slice(0, 2000-numberBytesInFirst2000BytesOfVideo)
}
if(thisRequest.currentlyExpectedCallback){
if(thisRequest.createReadStreamNumber<5 && thisRequest.callbackNumber <5){
if(thisRequest.bytesInAnswerStream > 0){
thisRequest.answerStream.push(chunk);
thisRequest.start += chunk.length;
thisRequest.bytesTakenFromServer += chunk.length;
var theCallbackFunction = thisRequest.currentlyExpectedCallback;
thisRequest.currentlyExpectedCallback = null;
thisRequest.answerStream.push(null);
thisRequest.bytesInAnswerStream = 0;
var res = thisRequest.answerStream;
if(thisRequest.createReadStreamNumber <4){
thisRequest.answerStream = new MyReadableStream({highWaterMark: 2000});
} else {
thisRequest.answerStream = new MyReadableStream({highWaterMark: WATERMARK_HEIGHT_OF_ANSWERSTREAM});
}
theCallbackFunction(null, res);
} else {
var returnStream = new MyReadableStream(numberBytesInfirst2000BytesOfVideo - thisRequest.start);
returnStream.push(first2000BytesOfVideo.slice(this.start, numberBytesInFirst2000BytesOfVideo);
returnStream.push(null);
bytesTakenFromServer += numberBytesInfirst2000BytesOfVideo-thisRequest.start;
thisRequest.start += numberBytesInfirst2000BytesOfVideo;
var theCallbackFunction = thisRequest.currentlyExpectedCallback;
thisRequest.currentlyExpectedCallback = null;
theCallbackFunction(null, returnStream);
}
}
}
*/
if(thisRequest.noMoreData){
thisRequest.oldStartServer += chunk.length;
return;
}
if (thisRequest.start - thisRequest.oldStartServer < chunk.length){
bytesTakenFromServer += chunk.length - (thisRequest.start - thisRequest.oldStartServer);
thisRequest.bytesInAnswerStream += chunk.length - (thisRequest.start - thisRequest.oldStartServer);
var myBuffer = chunk.slice(thisRequest.start - thisRequest.oldStartServer, chunk.length);
//////console.log("In XHRDataHandler myBuffer.length: " + myBuffer.length);
var StreamHasMemoryLeft = thisRequest.answerStream.push(myBuffer);
if(!StreamHasMemoryLeft){
if(thisRequest.currentlyExpectedCallback !== null){
var theCallbackFunction = thisRequest.currentlyExpectedCallback;
thisRequest.currentlyExpectedCallback = null;
thisRequest.answerStream.push(null);
thisRequest.bytesInAnswerStream = 0;
var res = thisRequest.answerStream;
thisRequest.answerStream = new MyReadableStream({highWaterMark: WATERMARK_HEIGHT_OF_ANSWERSTREAM});
//////console.log("called CB with data out of answerStream from videostreamRequest number " + thisRequest.createReadStreamNumber);
if (thisRequest.webTorrentStream){
//thisRequest.webTorrentStream.pause();
}
theCallbackFunction(null, res);
} else {
thisRequest.noMoreData = true;
if(thisRequest.webTorrentStream){
//thisRequest.webTorrentStream.pause();
}
}
} else {
if (thisRequest.start >= thisRequest.end && thisRequest.currentlyExpectedCallback !== null){
var theCallbackFunction = thisRequest.currentlyExpectedCallback;
thisRequest.currentlyExpectedCallback = null;
thisRequest.answerStream.push(null);
thisRequest.bytesInAnswerStream = 0;
var res = thisRequest.answerStream;
thisRequest.answerStream = new MyReadableStream({highWaterMark: WATERMARK_HEIGHT_OF_ANSWERSTREAM});
//////console.log("called CB with data out of answerStream from videostreamRequest number " + thisRequest.createReadStreamNumber);
if (thisRequest.webTorrentStream){
//thisRequest.webTorrentStream.pause();
}
theCallbackFunction(null, res);
}
}
thisRequest.start += chunk.length - (thisRequest.start - thisRequest.oldStartServer);
}
thisRequest.oldStartServer += chunk.length;
}
var XHREnd = function (){
////console.log("ReadableStream request number " + thisRequest.createReadStreamNumber + " XHREnd");
if (consoleCounter <<PHONE_NUMBER>000){
//////////////console.log("XHREnd from videostreamRequest number " + thisRequest.createReadStreamNumber);
}
if(thisRequest.createReadStreamNumber < 4 && thisRequest.currentlyExpectedCallback){
var theCallbackFunction = thisRequest.currentlyExpectedCallback;
thisRequest.currentlyExpectedCallback = null;
thisRequest.answerStream.push(null);
thisRequest.bytesInAnswerStream = 0;
var res = thisRequest.answerStream;
if(thisRequest.createReadStreamNumber <3){
thisRequest.answerStream = new MyReadableStream({highWaterMark: 2000});
} else {
thisRequest.answerStream = new MyReadableStream({highWaterMark: WATERMARK_HEIGHT_OF_ANSWERSTREAM});
}
theCallbackFunction(null, res);
thisRequest.XHRConducted = false;
return;
}
/* Want to solve example_application.js:14013 Uncaught Error: Data too short Daher das hier auskommentiert
if(thisRequest.bytesInAnswerStream > 0 && thisRequest.currentlyExpectedCallback !== null){
thisRequest.answerStream.push(null);
thisRequest.bytesInAnswerStream = 0;
var res = thisRequest.answerStream;
thisRequest.answerStream = new MyReadableStream({highWaterMark: WATERMARK_HEIGHT_OF_ANSWERSTREAM});
var theCallbackFunction = thisRequest.currentlyExpectedCallback;
thisRequest.currentlyExpectedCallback = null;
//////console.log("XHREnd: called CB with data out of answerStream from videostreamRequest number " + thisRequest.createReadStreamNumber);
theCallbackFunction(null, res);
}
*/
/*
if(!noMoreData){
if(thisRequest.start < SIZE_OF_VIDEO_FILE && thisRequest.start < thisRequest.XHR_filesize){
if(thisRequest.end > 0 ){
if(thisRequest.start < thisRequest.end){
conductXHR(thisRequest); // try to solve example_application.js:14013 Uncaught Error: Data too short
} else {
thisRequest.XHRConducted = false
}
} else {
conductXHR(thisRequest);
}
} else {
thisRequest.XHRConducted = false;
}
} else {
thisRequest.XHRConducted = false;
}
*/
//////console.log("XHREnd from videostreamRequest number " + thisRequest.createReadStreamNumber + " thisRequest.currentlyExpectedCallback === null : " + (thisRequest.currentlyExpectedCallback === null));
thisRequest.XHRConducted = false;
ceckIfAnswerStreamReady(thisRequest);
checkIfBufferFullEnough(true);
//ceckIfAnswerStreamReady(thisRequest); // Unsicher ob es drinn bleiben soll
//}
};
thisRequest.oldStartServer = reqStart;
//////console.log("At htto.get reqStart: " + reqStart + " reqEnd: " + reqEnd);
var XHROptionObject = {
path: thisRequest.self.pathToFileOnXHRServer,
headers: {
range: 'bytes=' + reqStart + '-' + (reqEnd-1),
connection : 'keep-alive',
//protocol: 'http:'
//???? method: 'CONNECT',
}
};
if(XHRServerURL){
XHROptionObject.hostname = XHRServerURL;
XHROptionObject.port = XHR_PORT;
}
thisRequest.req = http.get(XHROptionObject, function (res){
var contentRange = res.headers['content-range'];
if (contentRange) {
//////console.log("parseInt(contentRange.split('/')[1], 10) XHR: " + parseInt(contentRange.split('/')[1], 10));
// Hat zu bugs geführt. Hat geringe priorität einzubauen das file_size auch vom XHR server erfragt wird.
//SIZE_OF_VIDEO_FILE = parseInt(contentRange.split('/')[1], 10);
//if(thisRequest.end === 0){
thisRequest.XHR_filesize = parseInt(contentRange.split('/')[1], 10);
//}
}
//////////////console.log("I return currentlyExpectedCallback with http response stream");
////////////////console.log("function(res) is executed from readstream number " + createReadStreamCounter + " and CB number " + thiscallbackNumber);
res.on('end', XHREnd);
res.on('data', XHRDataHandler);
res.on('error', function(err){//console.log("The http.get response object has yield the following error"); console.error(err);});
});
thisRequest.req.on('error', function(err){
//console.log("thisRequest.req has yield the following error message: " + err.message);
});
});
}
frequentlyCheckIfNewCreateReadStreamNecessary();
chokeIfNecessary();
// updateChart(); NIcht löschen. Aber gehört nicht in production!!
// frequentlyCeckIfAnswerStreamReady(); Am 17.07 entschlossen das rauszunehmen. Ich hatte mir das ja schon mehrmals überlegt
checkIfBufferFullEnough();
//////////console.log("I call Videostream constructor");
if(hashValue){
Videostream(new fileLikeObject(hashValue), myVideo);
} else {
Videostream(new fileLikeObject(pathToFileOnXHRServer), myVideo);
}
}
// This function adds a simple-peer connection to the WebTorrent swarm of the OakStreaming instance.
// A simple-peer is a wrapper for a Web-RTC connection.
function addSimplePeerInstance(simplePeerInstance, options, callback){
// The method add a simplePeer to the WebTorrent swarm instance
if(theTorrent){
if(theTorrent.infoHash){
theTorrent.addPeer(simplePeerInstance);
if(callback){
callback();
}
} else {
theTorrent.on('infoHash', function() {theTorrent.addPeer(simplePeerInstance); if(callback){callback()}});
}
} else {
var pair = [];
pair.push(simplePeerInstance);
pair.push(callback);
peersToAdd.push(pair);
}
}
})();
}


if sys.argv[-1] == "publish":
os.system("python setup.py sdist upload")
sys.exit()
class PyTest(TestCommand):
author="Joel Akeret",
url="https://github.com/jakeret/tf_unet",
packages=find_packages(PACKAGE_PATH, "test"),
package_dir={"tf_unet": "tf_unet"},
include_package_data=True,
install_requires=requires,
license="GPLv3",


| Attribute |
Public none
|
Details:
| Alias: |
|
| Initial: |
|
| Stereotype: |
<<enum>> |
| Ordered: |
|
| Range: |
|
| Transient: |
False |
| Derived: |
False |
| IsID: |
False |
|
Public lightMist
|
Details:
| Alias: |
|
| Initial: |
|
| Stereotype: |
<<enum>> |
| Ordered: |
|
| Range: |
|
| Transient: |
False |
| Derived: |
False |
| IsID: |
False |
|
Public heavyMist
|
Details:
| Alias: |
|
| Initial: |
|
| Stereotype: |
<<enum>> |
| Ordered: |
|
| Range: |
|
| Transient: |
False |
| Derived: |
False |
| IsID: |
False |
|
Public lightRainOrDrizzle
|
Details:
| Alias: |
|
| Initial: |
|
| Stereotype: |
<<enum>> |
| Ordered: |
|
| Range: |
|
| Transient: |
False |
| Derived: |
False |
| IsID: |
False |
|
Public rain
|
Details:
| Alias: |
|
| Initial: |
|
| Stereotype: |
<<enum>> |
| Ordered: |
|
| Range: |
|
| Transient: |
False |
| Derived: |
False |
| IsID: |
False |
|
Public moderateRain
|
Details:
| Alias: |
|
| Initial: |
|
| Stereotype: |
<<enum>> |
| Ordered: |
|
| Range: |
|
| Transient: |
False |
| Derived: |
False |
| IsID: |
False |
|
Public heavyRain
|
Details:
| Alias: |
|
| Initial: |
|
| Stereotype: |
<<enum>> |
| Ordered: |
|
| Range: |
|
| Transient: |
False |
| Derived: |
False |
| IsID: |
False |
|
Public heavyDownpour
|
Details:
| Alias: |
|
| Initial: |
|
| Stereotype: |
<<enum>> |
| Ordered: |
|
| Range: |
|
| Transient: |
False |
| Derived: |
False |
| IsID: |
False |
|


interface SigningServicePersistentCache
Defines a simplified caching layer on top of an append-only persistence. he cached and persistence items are the same. It's expected that the implementation is race condition with different instances safe.
findByAlias |
Returns the value associated with alias,first loading that value from the storage if necessary or null if the value is not found. abstract fun findByAlias(alias: Any): SigningPersistentKey? |
get |
Returns the value associated with the key, first loading that value from the storage if necessary or null if the value is not found. abstract fun get(key: Any): SigningPersistentKey? |
put |
Persist the specified value and associates it with the specified key in this map (in that order). If the underlying storage previously contained a value for the key, the behaviour is unpredictable and most likely will throw an error from the underlying storage. abstract fun put(key: Any, entity: SigningPersistentKey): SigningPersistentKey |
SigningServicePersistentCacheImpl |
class SigningServicePersistentCacheImpl : SimplePersistentCacheImpl<SigningPersistentKey, SigningPersistentKey>, SigningServicePersistentCache |


Autism is a developmental disorder that appears in early childhood and the most common condition is known as autism spectrum disorders (ASD). It can be difficult to diagnose, as symptoms and degree of impairment are different for every child.
One in 150 children in Singapore has autism. There were 4,400 such children in 2014, a 76 per cent jump from the 2,500 children in 2010.
Microbit allows the autistic kid to make certain movement or press certain buttons to express their feelings. For eg. When asking for help or getting emotionally overwhelmed, the kids are able to press respective buttons to call out to caregivers. The microbit also releases an alert sound to suggests immediate attention for the kids.
The autistic kid will be holding on to the microbit connected to a Raspberry Pi. The LED light connecting to the Raspberry Pi will light up if the child is a certain distance away from the child. This allows the caregiver to locate the distance between the child and them. This ensures that the caregivers are able to supervise their child from being exposed to unfamiliar surroundings.
When the autistic child is agitated and emotionally unstable, eg. Angry, Upset. The child can press the right button to notify the caregiver and request for their attention. Thus, a "help" signal from the microbit will be shown and the microbit will have a continuous "beep" sound to alert caregivers to come and attend to the autistic child.
Through the Raspberry Pi connected to the LED light and range sensor, we are able to simulate the heart rate of a autistic child. If it exceeds the normal heart rate range of their age, the LED light will light up and alert the caregiver. This is useful when the autistic child is too afraid to express their feelings and will also allow the caregivers to better understand their feelings and emotions.
A speaker would be needed if we want to alert the caregiver of the autistic kid to provide immediate attention. Without a speaker connected to the microbit, alert sounds cannot be produce therefore the caregiver might not take note that the autistic child actually requested for immediate help.
If the autistic child just happen to shake the microbit accidentally, it might result in a false alarm for the caregivers.
It would be better if the microbit could have calming/soothing music effects played to them.This would keep them occupied and calm them down for the time being before the caregiver arrive and attend to them.
It would be better if we can also implemment a location app inside the tracker as the range sensor is unable to capture a wide and long distance.Hence by implementing a location tracker, the caregiver of the autistic child could easily track and know where the kid is at all times to avoid unwanted mishaps.


Hi,
Yesterday some of input IDs have changed. Now old inputs are inactive and Nodes have duplicate inputs. What has happened?
Archived Forum |
|
Inputs IDs changed in emoncms.orgSubmitted by mauzer on Fri, 28/11/2014 - 08:50Hi, Yesterday some of input IDs have changed. Now old inputs are inactive and Nodes have duplicate inputs. What has happened? » |
Re: Inputs IDs changed in emoncms.org
I'm afraid I can't answer "why?" but I can confirm I have gained some duplicated inputs too. most of my nodes now have 3 complete sets of inputs and only the last set is active.
All the effected feeds stopped logging 27/11/2014 16:31:19.
Something odd about my oldest node is that only input 6 has replicated whilst input 1 to 5 have not. Input 1 - 5 have been there over a year where as 6 was only added recently, I don't know if that has any meaning, as older nodes with no new inputs have not duplicated so it seems to relate to the age of the input.
When i deleted the 2 "extra input 6's" the original 1st "input 6" then continued to update, unfortunatly I guess the data since 4:30 yesterday is lost as the new inputs are not logging to feeds. I have attached before and after "deletion of extra inputs" pics.
Paul
Re: Inputs IDs changed in emoncms.org
My apologies, I did a server upgrade and restart yesterday at 16:30 on emoncms.org. I mistakenly did not shut down and save redis in the right order which would have caused the new inputs to be created. I didnt see the effect of this at the time as I think it only affected a small proportion of accounts. I've deleted all these empty inputs using an automated script and an see they are now linked correctly again for you paul and mauzer.
My apologies!
Trystan
Re: Inputs IDs changed in emoncms.org
Thank you Trystan!
[Multiple posts deleted - Moderator (RW)]


|
cpp_redis
4.0.0
cpp_redis is a C++11 Asynchronous Multi-Platform Lightweight Redis Client, with support for synchronous operations and pipelining.
|
#include <redis_connection.hpp>
Public Types | |
| typedef std::function< void(redis_connection &)> | disconnection_handler_t |
| typedef std::function< void(redis_connection &, reply &)> | reply_callback_t |
Public Member Functions | |
| redis_connection (void) | |
| ctor | |
| redis_connection (const std::shared_ptr< tcp_client_iface > &tcp_client) | |
| ~redis_connection (void) | |
| dtor | |
| redis_connection (const redis_connection &)=delete | |
| copy ctor | |
| redis_connection & | operator= (const redis_connection &)=delete |
| assignment operator | |
| void | connect (const std::string &host="<IP_ADDRESS>", std::size_t port=6379, const disconnection_handler_t &disconnection_handler=nullptr, const reply_callback_t &reply_callback=nullptr, std::uint32_t timeout_ms=0) |
| void | disconnect (bool wait_for_removal=false) |
| bool | is_connected (void) const |
| redis_connection & | send (const std::vector< std::string > &redis_cmd) |
| redis_connection & | commit (void) |
tcp connection wrapper handling redis protocol
| typedef std::function<void(redis_connection&)> cpp_redis::network::redis_connection::disconnection_handler_t |
disconnection handler takes as parameter the instance of the redis_connection
| typedef std::function<void(redis_connection&, reply&)> cpp_redis::network::redis_connection::reply_callback_t |
reply handler takes as parameter the instance of the redis_connection and the built reply
|
explicit |
ctor allowing to specify custom tcp client (default ctor uses the default tacopie tcp client)
| tcp_client | tcp client to be used for network communications |
| redis_connection& cpp_redis::network::redis_connection::commit | ( | void | ) |
commit pipelined transaction that is, send to the network all commands pipelined by calling send()
| void cpp_redis::network::redis_connection::connect | ( | const std::string & | host = "<IP_ADDRESS>" , |
| std::size_t | port = 6379 , |
||
| const disconnection_handler_t & | disconnection_handler = nullptr , |
||
| const reply_callback_t & | reply_callback = nullptr , |
||
| std::uint32_t | timeout_ms = 0 |
||
| ) |
connect to the given host and port, and set both disconnection and reply callbacks
| host | host to be connected to |
| port | port to be connected to |
| disconnection_handler | handler to be called in case of disconnection |
| reply_callback | handler to be called once a reply is ready |
| timeout_ms | max time to connect (in ms) |
| void cpp_redis::network::redis_connection::disconnect | ( | bool | wait_for_removal = false | ) |
disconnect from redis server
| wait_for_removal | when sets to true, disconnect blocks until the underlying TCP client has been effectively removed from the io_service and that all the underlying callbacks have completed. |
| bool cpp_redis::network::redis_connection::is_connected | ( | void | ) | const |
| redis_connection& cpp_redis::network::redis_connection::send | ( | const std::vector< std::string > & | redis_cmd | ) |
send the given command the command is actually pipelined and only buffered, so nothing is sent to the network please call commit() to flush the buffer
| redis_cmd | command to be sent |


| Methods' Summary |
| setBreakPoint |
returns the source code line where the breakpoint was set.
|
| clearAllBreakPoints |
clears all breakpoints in the module set by "setBreakPoint".
|
| eval |
Evaluates an expression.
|
| getStackTrace |
Returns the engine's stack trace of the current execute position. Line break is the delimiter.
|
| getContextInformation |
returns more detailed information about a specified stack frame.
|
| dumpVariable |
returns the value of the variable at the given stack position.
|
| setVariable |
sets the value of the specified variable within the specified
stack frame.
|
| isVariable |
returns whether the given variable exists within the specified stack frame.
|
| stop |
stops the execution of the interpreter.
|
| stepOver |
executes the next and only the next statement.
|
| stepIn |
executes the next and only the next statement.
|
| stepOut |
executes the program until the next return from this stack frame.
|
| doContinue |
continues the program execution.
|
| Methods' Details |
setBreakPoint
| long |
| setBreakPoint( |
[in] string |
aModuleName, |
| [in] long |
nSourceCodeLine, |
| [in] boolean |
bOn ); |
- Description
- returns the source code line where the breakpoint was set.
The value can differ from the parameter
nSourceCodeLine when this is not a valid line to
place it. -1 indicates that the breakpoint cannot be set at
this position.
|
|
clearAllBreakPoints
| void |
| clearAllBreakPoints( |
[in] string |
aModuleName ); |
- Description
- clears all breakpoints in the module set by "setBreakPoint".
|
|
eval
| string |
| eval( |
[in] string |
aSourceCode, |
| [in] short |
nCallStackPos ); |
- Description
- Evaluates an expression.
- Returns
- the value of the expression as string.
- Parameter CallStackPos
- Position in the call stack for which the expression
should be evaluated. 0 is the top/actual position in the call
in the call stack, 1 the next and so on.
|
|
getStackTrace
| sequence< string > |
| getStackTrace(); |
- Description
- Returns the engine's stack trace of the current execute position. Line break is the delimiter.
|
|
getContextInformation
- Description
- returns more detailed information about a specified stack frame.
- Parameter nCallStackPos
- specifies the position in the call stack for the
variables that should be delivered.
|
|
dumpVariable
| string |
| dumpVariable( |
[in] string |
aVariableName, |
| [in] short |
nCallStackPos ); |
- Description
- returns the value of the variable at the given stack position.
|
|
setVariable
| void |
| setVariable( |
[in] string |
aVariableName, |
| [in] string |
aValue, |
| [in] short |
nCallStackPos ); |
- Description
- sets the value of the specified variable within the specified
stack frame.
|
|
isVariable
| boolean |
| isVariable( |
[in] string |
aVariableName, |
| [in] short |
nCallStackPos ); |
- Description
- returns whether the given variable exists within the specified stack frame.
|
|
stop
- Description
- stops the execution of the interpreter.
To continue with the execution, call XDebugging::doContinue.
|
|
stepOver
- Description
- executes the next and only the next statement.
If the next statement is a function call, the function is
executed completely.
|
|
stepIn
- Description
- executes the next and only the next statement.
If the next statement is a function call, only the function
entered.
|
|
stepOut
- Description
- executes the program until the next return from this stack frame.
|
|
doContinue
- Description
- continues the program execution.
|
|
Copyright © 2013, The Apache Software Foundation, Licensed under the Apache License, Version 2.0. Apache, the Apache feather logo, Apache OpenOffice and OpenOffice.org are trademarks of The Apache Software Foundation. Other names may be trademarks of their respective owners.


Subject: {{!model.draft.subject}}
From: {{!model.draft.from}}@{{!model.mailSuffix}}
To: {{!model.draft.to.join(", ")}}
CC: {{!model.draft.cc.join(", ")}}
BCC: {{!model.draft.bcc.join(", ")}}
From: {{!model.draft.from}}@{{!model.mailSuffix}}
To: {{!model.draft.to.join(", ")}}
CC: {{!model.draft.cc.join(", ")}}
BCC: {{!model.draft.bcc.join(", ")}}


We intend to greatly expand the data available in RiboVision. We will release the data we use for our own research. We are also soliciting data from the ribosomal community at large. The usefulness of RiboVision goes up exponentially with the amount of data inside it. We will make sure people get the scientific citation to go with any data that they use.
We have rainbow, which is just colored from 5' end to 3' end. No citation needed, other than for the secondary structure itself.
We have onion which corresponds to our Onion model. An onion, as seen here, is basically dividing a ribosomal subunit rRNA into 20Å wide concentric shells. For the large subunit, HM and TT, the center is identical to the one used in (Hsiao et al., 2009). For EC and SC, the center is approximatly in the same relative place. For the small subunit, we use N9 of A1493(TT and EC) or A1756 (SC). If you re-publish this data, please cite (Hsiao et al., 2009). Hsiao et al., (2009) "Peeling the Onion: Ribosomes Are Ancient Molecular Fossils", Mol. Biol. Evol. 26, 2415-2425, doi 10.1093/molbev/msp163
B factors is just the B factors, an experimental property obtained through x-ray crystallography. If you use this, please cite the reference for the crystal structure that you used.
These are the domains of the rRNA. For the phylogeny-based structures, these are the traditional domains with edges chosen by Noeller and Steitz. For the 3D-based structures, these are our new domains as defined in (Petrov et al., 2013). Feel free to recolor them to fit your purposes. If you use our new domains, please cite (Petrov et al., 2013). It is currently in press, we will put the full reference and link up as soon as possible. Petrov et al., (2013) "Secondary Structure and Domain Architecture of the 23S rRNA", Nucleic Acids Res. doi: 10.1093/nar/gkt513
We have partitioned all nucleotides into "helicoids". A helicoid is a helix plus the most closely associated single-stranded rRNA. Each nucleotide is uniquely contained in one helicoid. The collection of all helicoids contain all rRNA. A helicoid is an in-house construct invented by Eli Hershkovits that is useful networking analysis, evolutionary pathways, etc. We have not included these in any publication yet. We are providing them for convience. The colors for the helices are arbitrary. We used 4 contrasting colors so that the boundary between them is always visible.
We have Mg contacts, computed at distances of 2.4Å, 2.6Å, and 6.0Å. These are computed directly from the crystal structures. If the distance between an rRNA nucleotide and a Mg ion is less than these distance, it is called a Mg contact. Using a distance of 6.0Å is partially useful, as this includes Mg ions which are coordinated through a water molecule. If you use this data, please cite the reference for the crystal structure that you used. Unfortunately, we do not have automatic selection of these Mg in Jmol yet. You can use the Jmol console to turn them on, or export your selections to PyMOL.


UTM means 'Universe Transverse Mercator'. The system was introcuced in 1947.Iit is now established worlwide with exception of the polar regions. The coordinates are found in a similar way as Gauss-Krueger-coordinates, but a meridian strip has a width of 6°. The coordinate axes are also called easting and northing. The earth's surface is divided up.into grid squares (UTM-zone fields).


iEAT
Sign-Up Form
Email address
Password
Full Name
Caloric Intake
What kind of dietary restrictions do you have?
Dairy
Egg
Gluten
Grain
Peanut
Seafood
Sesame
Shellfish
Soy
Sulfite
Tree Nut
Wheat
What kind of diet are you interested in?
Gluten Free
Ketogenic
Vegetarian
Lacto-Vegetarian
Ovo-Vegetarian
Vegan
Pescatarian
Paleo
Primal
Whole30
Address
City
State
Zip Code
Error:
Sign Up
Log-in
here


Python TkinterPython provides the standard library for creating the graphical user interface for desktop based applications. Developing desktop based applications with python Tkinter is not a complex task. An empty Tkinter top-level window can be created by using the following steps.
ExampleOutput: Tkinter widgetsThere are various widgets like button, canvas, checkbutton, entry, etc. that are used to build the python GUI applications.
Python Tkinter GeometryThe Tkinter geometry specifies the method by using which, the widgets are represented on display. The python Tkinter provides the following geometry methods.
Let's discuss each one of them in detail. Python Tkinter pack() methodThe pack() widget is used to organize widget in the block. The positions widgets added to the python application using the pack() method can be controlled by using the various options specified in the method call. However, the controls are less and widgets are generally added in the less organized manner. The syntax to use the pack() is given below. syntaxA list of possible options that can be passed in pack() is given below.
ExampleOutput: Python Tkinter grid() methodThe grid() geometry manager organizes the widgets in the tabular form. We can specify the rows and columns as the options in the method call. We can also specify the column span (width) or rowspan(height) of a widget. This is a more organized way to place the widgets to the python application. The syntax to use the grid() is given below. SyntaxA list of possible options that can be passed inside the grid() method is given below.
ExampleOutput: Python Tkinter place() methodThe place() geometry manager organizes the widgets to the specific x and y coordinates. SyntaxA list of possible options is given below.
ExampleOutput:
Next TopicPython Tkinter Button
|
Python TkinterPython provides the standard library for creating the graphical user interface for desktop based applications. Developing desktop based applications with python Tkinter is not a complex task. An empty Tkinter top-level window can be created by using the following steps.
ExampleOutput: Tkinter widgetsThere are various widgets like button, canvas, checkbutton, entry, etc. that are used to build the python GUI applications.
Python Tkinter GeometryThe Tkinter geometry specifies the method by using which, the widgets are represented on display. The python Tkinter provides the following geometry methods.
Let's discuss each one of them in detail. Python Tkinter pack() methodThe pack() widget is used to organize widget in the block. The positions widgets added to the python application using the pack() method can be controlled by using the various options specified in the method call. However, the controls are less and widgets are generally added in the less organized manner. The syntax to use the pack() is given below. syntaxA list of possible options that can be passed in pack() is given below.
ExampleOutput: Python Tkinter grid() methodThe grid() geometry manager organizes the widgets in the tabular form. We can specify the rows and columns as the options in the method call. We can also specify the column span (width) or rowspan(height) of a widget. This is a more organized way to place the widgets to the python application. The syntax to use the grid() is given below. SyntaxA list of possible options that can be passed inside the grid() method is given below.
ExampleOutput: Python Tkinter place() methodThe place() geometry manager organizes the widgets to the specific x and y coordinates. SyntaxA list of possible options is given below.
ExampleOutput:
Next TopicPython Tkinter Button
|
Python TkinterPython provides the standard library for creating the graphical user interface for desktop based applications. Developing desktop based applications with python Tkinter is not a complex task. An empty Tkinter top-level window can be created by using the following steps.
ExampleOutput: Tkinter widgetsThere are various widgets like button, canvas, checkbutton, entry, etc. that are used to build the python GUI applications.
Python Tkinter GeometryThe Tkinter geometry specifies the method by using which, the widgets are represented on display. The python Tkinter provides the following geometry methods.
Let's discuss each one of them in detail. Python Tkinter pack() methodThe pack() widget is used to organize widget in the block. The positions widgets added to the python application using the pack() method can be controlled by using the various options specified in the method call. However, the controls are less and widgets are generally added in the less organized manner. The syntax to use the pack() is given below. syntaxA list of possible options that can be passed in pack() is given below.
ExampleOutput: Python Tkinter grid() methodThe grid() geometry manager organizes the widgets in the tabular form. We can specify the rows and columns as the options in the method call. We can also specify the column span (width) or rowspan(height) of a widget. This is a more organized way to place the widgets to the python application. The syntax to use the grid() is given below. SyntaxA list of possible options that can be passed inside the grid() method is given below.
ExampleOutput: Python Tkinter place() methodThe place() geometry manager organizes the widgets to the specific x and y coordinates. SyntaxA list of possible options is given below.
ExampleOutput:
Next TopicPython Tkinter Button
|
Python TkinterPython provides the standard library for creating the graphical user interface for desktop based applications. Developing desktop based applications with python Tkinter is not a complex task. An empty Tkinter top-level window can be created by using the following steps.
ExampleOutput: Tkinter widgetsThere are various widgets like button, canvas, checkbutton, entry, etc. that are used to build the python GUI applications.
Python Tkinter GeometryThe Tkinter geometry specifies the method by using which, the widgets are represented on display. The python Tkinter provides the following geometry methods.
Let's discuss each one of them in detail. Python Tkinter pack() methodThe pack() widget is used to organize widget in the block. The positions widgets added to the python application using the pack() method can be controlled by using the various options specified in the method call. However, the controls are less and widgets are generally added in the less organized manner. The syntax to use the pack() is given below. syntaxA list of possible options that can be passed in pack() is given below.
ExampleOutput: Python Tkinter grid() methodThe grid() geometry manager organizes the widgets in the tabular form. We can specify the rows and columns as the options in the method call. We can also specify the column span (width) or rowspan(height) of a widget. This is a more organized way to place the widgets to the python application. The syntax to use the grid() is given below. SyntaxA list of possible options that can be passed inside the grid() method is given below.
ExampleOutput: Python Tkinter place() methodThe place() geometry manager organizes the widgets to the specific x and y coordinates. SyntaxA list of possible options is given below.
ExampleOutput:
Next TopicPython Tkinter Button
|


This part of the website is meant to inform you all of important things.
Since making a kind of message board would be difficult there will be three separate buttons
each leading to a platform where important messages will be posted.
One will lead you to Twitter, one to Discord and one to Facebook


vas - go, vie - life, chi - force
Go · Life · Force
Releasing life's true potential,
by restoring the focus of healthcare
from chronic to curative care.
Giving · Life · Hope
Vavici Foundation
A non-profit dedicated to working with a coalition of top academic advisors to identify and validate health care models and treatments that will have the greatest impact on healthcare by addressing root cause paradigms.
more
Vavici Ventures
A for-profit venture activist company that enables a public to private initiative with the Vavici Foundation to transform the best business models and treatments into profitable businesses. Vavici Ventures will fund these through proof of concept.
more
Vavici Company
The first portfolio company for Vavici Ventures, the Vavici Company, will strive to eliminate obesity and type 2 diabetes by enabling all primary-care doctors to cure these diseases by treating their root causes.
more


R/densityPlot.R
densityPlot.Rd
Constructs a density plot for a calculated variable in e_meta
portion of ftmsData
object
densityPlot(ftmsObj, variable, samples = NA, groups = FALSE, title = NA, yaxis = "density", plot_hist = FALSE, plot_curve = TRUE, curve_colors = NA, hist_color = "gray", xlabel = NA, ylabel = paste0(toupper(substring(yaxis, 1, 1)), substring(yaxis, 2), sep = ""))
| ftmsObj | ftmsData object of class peakData or compoundData |
|---|---|
| variable | column name of column in |
| samples | vector of sample names to plot. Default of |
| groups | vector of group names to plot. Value of |
| title | plot title, default is NA (no title) |
| yaxis | what should the y-axis represent, "density" or "count"? |
| plot_hist | TRUE/FALSE should a histogram be added to the plot? A histogram may only be added if a single sample or groups is plotted. |
| plot_curve | TRUE/FALSE should a smoothed curve be added to the plot? |
| curve_colors | named vector of colors for curves, where names correspond to samples or groups chosen. Named colors or hex codes are accepted. |
| hist_color | (single) color for histogram bars. Named colors or hex codes are accepted. |
| xlabel | x axis label, default is NULL, which will result in the same name as "variable" |
| ylabel | y axis label, default is "Density" |
a plotly object
If a histogram is produced, the histogram bar data as a data frame is attached to the output as the attribute hist_data
.
This may be useful for adding additional data to the plot, e.g. for linking selections in multiple plots.
plot_ly


Edit properties
Save
Scripting is disabled.
[List files by
URI
,
Public ID
,
Editor
,
Tag
] [
Add file
] [
Information
(
Edit
)] [Cache (
annotated
,
original
)]


Portal
Register
Name
Username
Staff ID
E-mail
Password
Confirm Password
Register
Already registered?
Login now


NAME
zip_stat,
zip_stat_index —
get information about file
LIBRARY
libzip (-lzip)
SYNOPSIS
#include <zip.h>
int
zip_stat(
zip_t
*archive,
const char
*fname,
zip_flags_t
flags,
zip_stat_t
*sb);
int
zip_stat_index(
zip_t
*archive,
zip_uint64_t
index,
zip_flags_t
flags,
zip_stat_t
*sb);
DESCRIPTION
The
zip_stat() function obtains information about the file
named
fname in
archive. The
flags argument specifies how the name lookup should be
done. Its values are described in
zip_name_locate(3). Also,
ZIP_FL_UNCHANGED
may be
or'ed to it to request information
about the original file in the archive, ignoring any changes made.
The
zip_stat_index() function obtains information about the
file at position
index.
The
sb argument is a pointer to a
struct
zip_stat (shown below), into which information about the file is placed.
struct zip_stat {
zip_uint64_t valid; /* which fields have valid values */
const char *name; /* name of the file */
zip_uint64_t index; /* index within archive */
zip_uint64_t size; /* size of file (uncompressed) */
zip_uint64_t comp_size; /* size of file (compressed) */
time_t mtime; /* modification time */
zip_uint32_t crc; /* crc of file data */
zip_uint16_t comp_method; /* compression method used */
zip_uint16_t encryption_method; /* encryption method used */
zip_uint32_t flags; /* reserved for future use */
};
The structure pointed to by
sb must be allocated before
calling
zip_stat() or
zip_stat_index().
The
valid field of the structure specifies which other
fields are valid. Check if the flag defined by the following defines are in
valid before accessing the fields:
ZIP_STAT_NAME
- name
ZIP_STAT_INDEX
- index
ZIP_STAT_SIZE
- size
ZIP_STAT_COMP_SIZE
- comp_size
ZIP_STAT_MTIME
- mtime
ZIP_STAT_CRC
- crc
ZIP_STAT_COMP_METHOD
- comp_method
ZIP_STAT_ENCRYPTION_METHOD
- encryption_method
ZIP_STAT_FLAGS
- flags
RETURN VALUES
Upon successful completion 0 is returned. Otherwise, -1 is returned and the
error information in
archive is set to indicate the
error.
ERRORS
The function
zip_stat() can fail for any of the errors
specified for the routine
zip_name_locate(3).
The function
zip_stat_index() fails and sets the error
information to
ZIP_ER_INVAL
if
index is invalid. If
ZIP_FL_UNCHANGED
is not set and no information can be
obtained from the source callback, the error information is set to
ZIP_ER_CHANGED
.
SEE ALSO
libzip(3),
zip_get_num_entries(3),
zip_name_locate(3),
zip_stat_init(3)
HISTORY
zip_stat() was added in libzip 0.6. In libzip 0.11 the type of
flags was changed from
int to
zip_flags_t.
zip_stat_index() was added in libzip 0.6. In libzip 0.10 the
type of
index was changed from
int
to
zip_uint64_t. In libzip 0.11 the type of
flags was changed from
int to
zip_flags_t.
AUTHORS
Dieter Baron
<
<EMAIL_ADDRESS>and
Thomas Klausner
<
tk@giga.or.at>