mirror of
https://github.com/Ylianst/MeshCentral.git
synced 2024-09-11 08:55:32 +03:00
Added session recording indexing tool and player support.
This commit is contained in:
parent
9a9c6dde65
commit
421c0fc79e
@ -104,6 +104,7 @@
|
|||||||
<Compile Include="apfserver.js" />
|
<Compile Include="apfserver.js" />
|
||||||
<Compile Include="exeHandler.js" />
|
<Compile Include="exeHandler.js" />
|
||||||
<Compile Include="letsencrypt.js" />
|
<Compile Include="letsencrypt.js" />
|
||||||
|
<Compile Include="mcrec.js" />
|
||||||
<Compile Include="meshaccelerator.js" />
|
<Compile Include="meshaccelerator.js" />
|
||||||
<Compile Include="meshctrl.js" />
|
<Compile Include="meshctrl.js" />
|
||||||
<Compile Include="meshmail.js" />
|
<Compile Include="meshmail.js" />
|
||||||
|
294
mcrec.js
Normal file
294
mcrec.js
Normal file
@ -0,0 +1,294 @@
|
|||||||
|
/**
|
||||||
|
* @description MeshCentral MeshAgent
|
||||||
|
* @author Ylian Saint-Hilaire
|
||||||
|
* @copyright Intel Corporation 2019-2020
|
||||||
|
* @license Apache-2.0
|
||||||
|
* @version v0.0.1
|
||||||
|
*/
|
||||||
|
|
||||||
|
var fs = require('fs');
|
||||||
|
var path = require('path');
|
||||||
|
|
||||||
|
var worker = null;
|
||||||
|
const NodeJSVer = Number(process.version.match(/^v(\d+\.\d+)/)[1]);
|
||||||
|
var directRun = (require.main === module);
|
||||||
|
function log() { if (directRun) { console.log(...arguments); } else { if (worker != null) { worker.parentPort.postMessage({ msg: arguments[0] }); } } }
|
||||||
|
if (directRun && (NodeJSVer >= 12)) { const xworker = require('worker_threads'); try { if (xworker.isMainThread == false) { worker = xworker; } } catch (ex) { log(ex); } }
|
||||||
|
function start() { startEx(process.argv); }
|
||||||
|
if (directRun) { setup(); }
|
||||||
|
|
||||||
|
function setup() { InstallModules(['image-size'], start); }
|
||||||
|
function start() { startEx(process.argv); }
|
||||||
|
|
||||||
|
function startEx(argv) {
|
||||||
|
var state = { recFileName: null, recFile: null, recFileSize: 0, recFilePtr: 0 };
|
||||||
|
var infile = null;
|
||||||
|
if (argv.length > 2) { infile = argv[2]; } else {
|
||||||
|
log('MeshCentral Session Recodings Processor');
|
||||||
|
log('This tool will index a .mcrec file so that the player can seek thru the file.');
|
||||||
|
log('');
|
||||||
|
log(' Usage: node mcrec [file]');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (fs.existsSync(infile) == false) { log("Missing file: " + infile); return; }
|
||||||
|
state.recFileName = infile;
|
||||||
|
state.recFileSize = fs.statSync(infile).size;
|
||||||
|
if (state.recFileSize < 32) { log("Invalid file: " + infile); return; }
|
||||||
|
log("Processing file: " + infile + ", " + state.recFileSize + " bytes.");
|
||||||
|
state.recFile = fs.openSync(infile, 'r');
|
||||||
|
state.indexTime = 10; // Interval between indexes in seconds
|
||||||
|
state.lastIndex = 0; // Last time an index was writen in seconds
|
||||||
|
state.indexes = [];
|
||||||
|
state.width = 0;
|
||||||
|
state.height = 0;
|
||||||
|
state.basePtr = null;
|
||||||
|
readLastBlock(state, function (state, result) {
|
||||||
|
if (result == false) { log("Invalid file: " + infile); return; }
|
||||||
|
readNextBlock(state, processBlock);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createIndex(state, ptr) {
|
||||||
|
var index = [];
|
||||||
|
for (var i in state.screen) { if (index.indexOf(state.screen[i]) == -1) { index.push(state.screen[i]); } }
|
||||||
|
index.sort(function (a, b) { return a - b });
|
||||||
|
index.unshift(state.height);
|
||||||
|
index.unshift(state.width);
|
||||||
|
index.unshift(ptr - state.basePtr);
|
||||||
|
state.indexes.push(index); // Index = [ Ptr, Width, Height, Block Pointers... ]
|
||||||
|
//log('Index', state.lastIndex, index.length);
|
||||||
|
//log('Index', index);
|
||||||
|
state.lastIndex += 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
function processBlock(state, block) {
|
||||||
|
if (block == null) { writeIndexedFile(state, function () { log("Done."); }); return; }
|
||||||
|
var elapseMilliSeconds = 0;
|
||||||
|
if (state.startTime != null) { elapseMilliSeconds = (block.time - state.startTime); }
|
||||||
|
var flagBinary = (block.flags & 1) != 0;
|
||||||
|
var flagUser = (block.flags & 2) != 0;
|
||||||
|
|
||||||
|
// Start indexing at the first type 2 block
|
||||||
|
if ((state.basePtr == null) && (block.type == 2)) { state.basePtr = block.ptr; state.startTime = block.time; }
|
||||||
|
|
||||||
|
// Check if we need to create one or more indexes
|
||||||
|
while (((state.lastIndex + state.indexTime) * 1000) < elapseMilliSeconds) { createIndex(state, block.ptr); }
|
||||||
|
|
||||||
|
if (block.type == 1) {
|
||||||
|
// Metadata
|
||||||
|
state.metadata = JSON.parse(block.data.toString());
|
||||||
|
if (state.metadata.indexInterval != null) { log("This file is already indexed."); return; }
|
||||||
|
if (state.metadata.protocol != 2) { log("Only remote desktop sessions can currently be indexed."); return; }
|
||||||
|
state.metadataFlags = block.flags;
|
||||||
|
state.metadataTime = block.time;
|
||||||
|
state.recFileProtocol = state.metadata.protocol;
|
||||||
|
state.dataStartPtr = state.recFilePtr;
|
||||||
|
if (typeof state.recFileProtocol == 'string') { state.recFileProtocol = parseInt(state.recFileProtocol); }
|
||||||
|
} else if ((block.type == 2) && flagBinary && !flagUser) {
|
||||||
|
// Device --> User data
|
||||||
|
if (state.recFileProtocol == 1) {
|
||||||
|
// MeshCentral Terminal
|
||||||
|
// TODO
|
||||||
|
log('Terminal');
|
||||||
|
} else if (state.recFileProtocol == 2) {
|
||||||
|
// MeshCentral Remote Desktop
|
||||||
|
// TODO
|
||||||
|
if (block.data.length >= 4) {
|
||||||
|
var command = block.data.readInt16BE(0);
|
||||||
|
var cmdsize = block.data.readInt16BE(2);
|
||||||
|
if ((command == 27) && (cmdsize == 8)) {
|
||||||
|
// Jumbo packet
|
||||||
|
if (block.data.length >= 12) {
|
||||||
|
command = block.data.readInt16BE(8);
|
||||||
|
cmdsize = block.data.readInt32BE(4);
|
||||||
|
if (block.data.length == (cmdsize + 8)) {
|
||||||
|
block.data = block.data.slice(8, block.data.length);
|
||||||
|
} else {
|
||||||
|
console.log('TODO-PARTIAL-JUMBO', command, cmdsize, block.data.length);
|
||||||
|
return; // TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (command) {
|
||||||
|
case 3: // Tile
|
||||||
|
var x = block.data.readInt16BE(4);
|
||||||
|
var y = block.data.readInt16BE(6);
|
||||||
|
var dimensions = require('image-size')(block.data.slice(8));
|
||||||
|
//log("Tile", x, y, dimensions.width, dimensions.height, block.ptr);
|
||||||
|
//console.log(elapseSeconds);
|
||||||
|
|
||||||
|
// Update the screen with the correct pointers.
|
||||||
|
var sx = x/16, sy = y/16, sw = dimensions.width/16, sh = dimensions.height/16;
|
||||||
|
for (var i = 0; i < sw; i++) {
|
||||||
|
for (var j = 0; j < sh; j++) {
|
||||||
|
var k = ((state.swidth * (j + sy)) + (i + sx));
|
||||||
|
state.screen[k] = (block.ptr - state.basePtr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 4: // Tile copy
|
||||||
|
var x = block.data.readInt16BE(4);
|
||||||
|
var y = block.data.readInt16BE(6);
|
||||||
|
//log("TileCopy", x, y);
|
||||||
|
break;
|
||||||
|
case 7: // Screen Size, clear the screen state and computer the tile count
|
||||||
|
state.width = block.data.readInt16BE(4);
|
||||||
|
state.height = block.data.readInt16BE(6);
|
||||||
|
state.swidth = state.width / 16;
|
||||||
|
state.sheight = state.height / 16;
|
||||||
|
if (Math.floor(state.swidth) != state.swidth) { state.swidth = Math.floor(state.swidth) + 1; }
|
||||||
|
if (Math.floor(state.sheight) != state.sheight) { state.sheight = Math.floor(state.sheight) + 1; }
|
||||||
|
state.screen = {};
|
||||||
|
//log("ScreenSize", state.width, state.height, state.swidth, state.sheight, state.swidth * state.sheight);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
//log('Desktop', command, cmdsize);
|
||||||
|
}
|
||||||
|
} else if (state.recFileProtocol == 101) {
|
||||||
|
// Intel AMT KVM
|
||||||
|
// TODO
|
||||||
|
log('AMTKVM');
|
||||||
|
}
|
||||||
|
} else if ((block.type == 2) && flagBinary && flagUser) {
|
||||||
|
// User --> Device data
|
||||||
|
if (state.recFileProtocol == 101) {
|
||||||
|
// Intel AMT KVM
|
||||||
|
//if (rstr2hex(data) == '0000000008080001000700070003050200000000') { amtDesktop.bpp = 1; } // Switch to 1 byte per pixel.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//console.log(block);
|
||||||
|
readNextBlock(state, processBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeIndexedFile(state, func) {
|
||||||
|
var outfile = state.recFileName;
|
||||||
|
if (outfile.endsWith('.mcrec')) { outfile = outfile.substring(0, outfile.length - 6) + '-ndx.mcrec'; } else { outfile += '-ndx.mcrec'; }
|
||||||
|
if (fs.existsSync(outfile)) { log("File already exists: " + outfile); return; }
|
||||||
|
log("Writing file: " + outfile);
|
||||||
|
state.writeFile = fs.openSync(outfile, 'w');
|
||||||
|
state.metadata.indexInterval = state.indexTime;
|
||||||
|
state.metadata.indexStartTime = state.startTime;
|
||||||
|
state.metadata.indexes = state.indexes;
|
||||||
|
var firstBlock = JSON.stringify(state.metadata);
|
||||||
|
recordingEntry(state.writeFile, 1, state.metadataFlags, state.metadataTime, firstBlock, function (state) {
|
||||||
|
var len = 0, buffer = Buffer.alloc(4096), ptr = state.dataStartPtr;
|
||||||
|
while (ptr < state.recFileSize) {
|
||||||
|
len = fs.readSync(state.recFile, buffer, 0, 4096, ptr);
|
||||||
|
fs.writeSync(state.writeFile, buffer, 0, len);
|
||||||
|
ptr += len;
|
||||||
|
}
|
||||||
|
func(state);
|
||||||
|
}, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record a new entry in a recording log
|
||||||
|
function recordingEntry(fd, type, flags, time, data, func, tag) {
|
||||||
|
try {
|
||||||
|
if (typeof data == 'string') {
|
||||||
|
// String write
|
||||||
|
var blockData = Buffer.from(data), header = Buffer.alloc(16); // Header: Type (2) + Flags (2) + Size(4) + Time(8)
|
||||||
|
header.writeInt16BE(type, 0); // Type (1 = Header, 2 = Network Data)
|
||||||
|
header.writeInt16BE(flags, 2); // Flags (1 = Binary, 2 = User)
|
||||||
|
header.writeInt32BE(blockData.length, 4); // Size
|
||||||
|
header.writeIntBE(time, 10, 6); // Time
|
||||||
|
var block = Buffer.concat([header, blockData]);
|
||||||
|
fs.write(fd, block, 0, block.length, function () { func(tag); });
|
||||||
|
} else {
|
||||||
|
// Binary write
|
||||||
|
var header = Buffer.alloc(16); // Header: Type (2) + Flags (2) + Size(4) + Time(8)
|
||||||
|
header.writeInt16BE(type, 0); // Type (1 = Header, 2 = Network Data)
|
||||||
|
header.writeInt16BE(flags | 1, 2); // Flags (1 = Binary, 2 = User)
|
||||||
|
header.writeInt32BE(data.length, 4); // Size
|
||||||
|
header.writeIntBE(time, 10, 6); // Time
|
||||||
|
var block = Buffer.concat([header, data]);
|
||||||
|
fs.write(fd, block, 0, block.length, function () { func(tag); });
|
||||||
|
}
|
||||||
|
} catch (ex) { console.log(ex); func(state, tag); }
|
||||||
|
}
|
||||||
|
|
||||||
|
function readLastBlock(state, func) {
|
||||||
|
var buf = Buffer.alloc(32);
|
||||||
|
fs.read(state.recFile, buf, 0, 32, state.recFileSize - 32, function (err, bytesRead, buf) {
|
||||||
|
var type = buf.readInt16BE(0);
|
||||||
|
var flags = buf.readInt16BE(2);
|
||||||
|
var size = buf.readInt32BE(4);
|
||||||
|
var time = (buf.readInt32BE(8) << 32) + buf.readInt32BE(12);
|
||||||
|
var magic = buf.toString('utf8', 16, 32);
|
||||||
|
func(state, (type == 3) && (size == 16) && (magic == 'MeshCentralMCREC'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function readNextBlock(state, func) {
|
||||||
|
if ((state.recFilePtr + 16) > state.recFileSize) { func(state, null); return; }
|
||||||
|
var r = {}, buf = Buffer.alloc(16);
|
||||||
|
fs.read(state.recFile, buf, 0, 16, state.recFilePtr, function (err, bytesRead, buf) {
|
||||||
|
r.type = buf.readInt16BE(0);
|
||||||
|
r.flags = buf.readInt16BE(2);
|
||||||
|
r.size = buf.readInt32BE(4);
|
||||||
|
r.time = buf.readIntBE(8, 8);
|
||||||
|
r.date = new Date(r.time);
|
||||||
|
r.ptr = state.recFilePtr;
|
||||||
|
if ((state.recFilePtr + 16 + r.size) > state.recFileSize) { func(state, null); return; }
|
||||||
|
if (r.size == 0) {
|
||||||
|
r.data = null;
|
||||||
|
func(state, r);
|
||||||
|
} else {
|
||||||
|
r.data = Buffer.alloc(r.size);
|
||||||
|
fs.read(state.recFile, r.data, 0, r.size, state.recFilePtr + 16, function (err, bytesRead, buf) {
|
||||||
|
state.recFilePtr += (16 + r.size);
|
||||||
|
func(state, r);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function isNumber(x) { return (('' + parseInt(x)) === x) || (('' + parseFloat(x)) === x); }
|
||||||
|
function format(format) { var args = Array.prototype.slice.call(arguments, 1); return format.replace(/{(\d+)}/g, function (match, number) { return typeof args[number] != 'undefined' ? args[number] : match; }); };
|
||||||
|
|
||||||
|
// Check if a list of modules are present and install any missing ones
|
||||||
|
var InstallModuleChildProcess = null;
|
||||||
|
var previouslyInstalledModules = {};
|
||||||
|
function InstallModules(modules, func) {
|
||||||
|
var missingModules = [];
|
||||||
|
if (previouslyInstalledModules == null) { previouslyInstalledModules = {}; }
|
||||||
|
if (modules.length > 0) {
|
||||||
|
for (var i in modules) {
|
||||||
|
try {
|
||||||
|
var xxmodule = require(modules[i]);
|
||||||
|
} catch (e) {
|
||||||
|
if (previouslyInstalledModules[modules[i]] !== true) { missingModules.push(modules[i]); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (missingModules.length > 0) { InstallModule(missingModules.shift(), InstallModules, modules, func); } else { func(); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if a module is present and install it if missing
|
||||||
|
function InstallModule(modulename, func, tag1, tag2) {
|
||||||
|
log('Installing ' + modulename + '...');
|
||||||
|
var child_process = require('child_process');
|
||||||
|
var parentpath = __dirname;
|
||||||
|
|
||||||
|
// Get the working directory
|
||||||
|
if ((__dirname.endsWith('/node_modules/meshcentral')) || (__dirname.endsWith('\\node_modules\\meshcentral')) || (__dirname.endsWith('/node_modules/meshcentral/')) || (__dirname.endsWith('\\node_modules\\meshcentral\\'))) { parentpath = require('path').join(__dirname, '../..'); }
|
||||||
|
|
||||||
|
// Looks like we need to keep a global reference to the child process object for this to work correctly.
|
||||||
|
InstallModuleChildProcess = child_process.exec('npm install --no-optional --save ' + modulename, { maxBuffer: 512000, timeout: 120000, cwd: parentpath }, function (error, stdout, stderr) {
|
||||||
|
InstallModuleChildProcess = null;
|
||||||
|
if ((error != null) && (error != '')) {
|
||||||
|
log('ERROR: Unable to install required module "' + modulename + '". May not have access to npm, or npm may not have suffisent rights to load the new module. Try "npm install ' + modulename + '" to manualy install this module.\r\n');
|
||||||
|
process.exit();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
previouslyInstalledModules[modulename] = true;
|
||||||
|
func(tag1, tag2);
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export table
|
||||||
|
module.exports.startEx = startEx;
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "meshcentral",
|
"name": "meshcentral",
|
||||||
"version": "0.4.9-b",
|
"version": "0.4.9-c",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"Remote Management",
|
"Remote Management",
|
||||||
"Intel AMT",
|
"Intel AMT",
|
||||||
@ -37,6 +37,7 @@
|
|||||||
"express": "^4.17.0",
|
"express": "^4.17.0",
|
||||||
"express-handlebars": "^3.1.0",
|
"express-handlebars": "^3.1.0",
|
||||||
"express-ws": "^4.0.0",
|
"express-ws": "^4.0.0",
|
||||||
|
"image-size": "^0.8.3",
|
||||||
"ipcheck": "^0.1.0",
|
"ipcheck": "^0.1.0",
|
||||||
"minimist": "^1.2.0",
|
"minimist": "^1.2.0",
|
||||||
"multiparty": "^4.2.1",
|
"multiparty": "^4.2.1",
|
||||||
|
@ -1256,6 +1256,12 @@
|
|||||||
"agentinvite.handlebars->3->4"
|
"agentinvite.handlebars->3->4"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"en": "<<",
|
||||||
|
"xloc": [
|
||||||
|
"player.handlebars->p11->deskarea0->deskarea4->3"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"cs": "<a href=\\\"https://www.yubico.com/\\\" rel=\\\"noreferrer noopener\\\" target=\\\"_blank\\\">Hardwarové klíče</a> jsou použity jako druhý faktor ověřování.",
|
"cs": "<a href=\\\"https://www.yubico.com/\\\" rel=\\\"noreferrer noopener\\\" target=\\\"_blank\\\">Hardwarové klíče</a> jsou použity jako druhý faktor ověřování.",
|
||||||
"de": "<a href=\\\"https://www.yubico.com/\\\" rel=\\\"noreferrer noopener\\\" target=\\\"_blank\\\">Hardware-Schlüssel</a> werden für Zweifaktor-Anmeldung verwendet.",
|
"de": "<a href=\\\"https://www.yubico.com/\\\" rel=\\\"noreferrer noopener\\\" target=\\\"_blank\\\">Hardware-Schlüssel</a> werden für Zweifaktor-Anmeldung verwendet.",
|
||||||
@ -1321,6 +1327,12 @@
|
|||||||
"default-mobile.handlebars->9->23"
|
"default-mobile.handlebars->9->23"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"en": ">>",
|
||||||
|
"xloc": [
|
||||||
|
"player.handlebars->p11->deskarea0->deskarea4->3"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"cs": "ACM",
|
"cs": "ACM",
|
||||||
"de": "ACM",
|
"de": "ACM",
|
||||||
@ -6282,7 +6294,7 @@
|
|||||||
"pt": "Arraste e solte um arquivo .mcrec ou clique em \\\"Abrir arquivo...\\\"",
|
"pt": "Arraste e solte um arquivo .mcrec ou clique em \\\"Abrir arquivo...\\\"",
|
||||||
"ru": "Перетащите .mcrec файл или нажмите \\\"Открыть файл ... \\\"",
|
"ru": "Перетащите .mcrec файл или нажмите \\\"Открыть файл ... \\\"",
|
||||||
"xloc": [
|
"xloc": [
|
||||||
"player.handlebars->3->18"
|
"player.handlebars->3->20"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -8449,6 +8461,12 @@
|
|||||||
"default.handlebars->container->dialog->dialogBody->dialog7->d7amtkvm->3->1"
|
"default.handlebars->container->dialog->dialogBody->dialog7->d7amtkvm->3->1"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"en": "Indexed every {0} seconds",
|
||||||
|
"xloc": [
|
||||||
|
"player.handlebars->3->17"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"cs": "indonézština",
|
"cs": "indonézština",
|
||||||
"de": "Indonesisch",
|
"de": "Indonesisch",
|
||||||
@ -12920,7 +12938,7 @@
|
|||||||
"pt": "Abrir arquivo...",
|
"pt": "Abrir arquivo...",
|
||||||
"ru": "Открыть файл...",
|
"ru": "Открыть файл...",
|
||||||
"xloc": [
|
"xloc": [
|
||||||
"player.handlebars->3->19",
|
"player.handlebars->3->21",
|
||||||
"player.handlebars->p11->deskarea0->deskarea1->3"
|
"player.handlebars->p11->deskarea0->deskarea1->3"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -13882,8 +13900,8 @@
|
|||||||
"pt": "Pressione [espaço] para reproduzir / pausar.",
|
"pt": "Pressione [espaço] para reproduzir / pausar.",
|
||||||
"ru": "Нажмите [пробел] для проигрывания/паузы.",
|
"ru": "Нажмите [пробел] для проигрывания/паузы.",
|
||||||
"xloc": [
|
"xloc": [
|
||||||
"player.handlebars->3->16",
|
"player.handlebars->3->18",
|
||||||
"player.handlebars->3->17"
|
"player.handlebars->3->19"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -15317,6 +15335,12 @@
|
|||||||
"default.handlebars->25->1354"
|
"default.handlebars->25->1354"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"en": "Seeking",
|
||||||
|
"xloc": [
|
||||||
|
"player.handlebars->3->16"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"cs": "Vybrat vše",
|
"cs": "Vybrat vše",
|
||||||
"de": "Alle auswählen",
|
"de": "Alle auswählen",
|
||||||
|
@ -29,9 +29,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id=deskarea2 style="">
|
<div id=deskarea2 style="">
|
||||||
<div class="areaProgress"><div id="progressbar" style=""></div></div>
|
<div class="areaProgress" style="cursor:pointer" onclick="progressBarSeek(event)"><div id="progressbar" style="height:6px;cursor:pointer"></div></div>
|
||||||
</div>
|
</div>
|
||||||
<div id=deskarea3x style="max-height:calc(100vh - 54px);height:calc(100vh - 54px);" onclick="togglePause()">
|
<div id=deskarea3x style="max-height:calc(100vh - 58px);height:calc(100vh - 58px);" onclick="togglePause()">
|
||||||
<div id="bigok" style="display:none;left:calc((100vh / 2))"><b>✓</b></div>
|
<div id="bigok" style="display:none;left:calc((100vh / 2))"><b>✓</b></div>
|
||||||
<div id="bigfail" style="display:none;left:calc((100vh / 2))"><b>✗</b></div>
|
<div id="bigfail" style="display:none;left:calc((100vh / 2))"><b>✗</b></div>
|
||||||
<div id="metadatadiv" style="padding:20px;color:lightgrey;text-align:left;display:none"></div>
|
<div id="metadatadiv" style="padding:20px;color:lightgrey;text-align:left;display:none"></div>
|
||||||
@ -203,8 +203,13 @@
|
|||||||
if (recFileMetadata.indexInterval) {
|
if (recFileMetadata.indexInterval) {
|
||||||
recFileIndexBasePtr = recFilePtr;
|
recFileIndexBasePtr = recFilePtr;
|
||||||
x += addInfoNoEsc("Seeking", format("Indexed every {0} seconds", recFileMetadata.indexInterval));
|
x += addInfoNoEsc("Seeking", format("Indexed every {0} seconds", recFileMetadata.indexInterval));
|
||||||
|
QV('SeekBackwardButton', true);
|
||||||
|
QV('SeekForwardButton', true);
|
||||||
QE('SeekBackwardButton', true);
|
QE('SeekBackwardButton', true);
|
||||||
QE('SeekForwardButton', true);
|
QE('SeekForwardButton', true);
|
||||||
|
} else {
|
||||||
|
QV('SeekBackwardButton', false);
|
||||||
|
QV('SeekForwardButton', false);
|
||||||
}
|
}
|
||||||
QV('DeskParent', true);
|
QV('DeskParent', true);
|
||||||
QV('TermParent', false);
|
QV('TermParent', false);
|
||||||
@ -247,6 +252,7 @@
|
|||||||
QV('metadatadiv', true);
|
QV('metadatadiv', true);
|
||||||
QH('metadatadiv', x);
|
QH('metadatadiv', x);
|
||||||
QH('deskstatus', recFile.name);
|
QH('deskstatus', recFile.name);
|
||||||
|
QS('progressbar').width = '0px';
|
||||||
}
|
}
|
||||||
|
|
||||||
function processBlock(type, flags, time, data) {
|
function processBlock(type, flags, time, data) {
|
||||||
@ -256,7 +262,7 @@
|
|||||||
processBlockEx(type, flags, time, data);
|
processBlockEx(type, flags, time, data);
|
||||||
} else {
|
} else {
|
||||||
waitTimerArgs = [type, flags, time, data]
|
waitTimerArgs = [type, flags, time, data]
|
||||||
waitTimer = setTimeout(function () { waitTimer = null; processBlockEx(waitTimerArgs[0], waitTimerArgs[1], waitTimerArgs[2], waitTimerArgs[3]); }, waitTime);
|
waitTimer = setTimeout(function () { waitTimer = null; if (waitTimerArgs) { processBlockEx(waitTimerArgs[0], waitTimerArgs[1], waitTimerArgs[2], waitTimerArgs[3]); } }, waitTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -267,13 +273,12 @@
|
|||||||
// Update the clock
|
// Update the clock
|
||||||
var deltaTimeTotalSec = Math.floor((time - recFileStartTime) / 1000);
|
var deltaTimeTotalSec = Math.floor((time - recFileStartTime) / 1000);
|
||||||
if (currentDeltaTimeTotalSec != deltaTimeTotalSec) {
|
if (currentDeltaTimeTotalSec != deltaTimeTotalSec) {
|
||||||
|
// Hours, minutes and seconds
|
||||||
currentDeltaTimeTotalSec = deltaTimeTotalSec;
|
currentDeltaTimeTotalSec = deltaTimeTotalSec;
|
||||||
var deltaTimeHours = Math.floor(deltaTimeTotalSec / 3600);
|
var hrs = Math.floor(deltaTimeTotalSec / 3600);
|
||||||
deltaTimeTotalSec -= (deltaTimeHours * 3600)
|
var mins = Math.floor((deltaTimeTotalSec % 3600) / 60);
|
||||||
var deltaTimeMinutes = Math.floor(deltaTimeTotalSec / 60);
|
var secs = Math.floor(deltaTimeTotalSec % 60);
|
||||||
deltaTimeTotalSec -= (deltaTimeHours * 60)
|
QH('timespan', pad2(hrs) + ':' + pad2(mins) + ':' + pad2(secs))
|
||||||
var deltaTimeSeconds = Math.floor(deltaTimeTotalSec);
|
|
||||||
QH('timespan', pad2(deltaTimeHours) + ':' + pad2(deltaTimeMinutes) + ':' + pad2(deltaTimeSeconds))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((type == 2) && flagBinary && !flagUser) {
|
if ((type == 2) && flagBinary && !flagUser) {
|
||||||
@ -519,38 +524,66 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function seekBackward() {
|
function seekBackward() {
|
||||||
//console.log('seekBackward');
|
var ndxNumber = Math.round(currentDeltaTimeTotalSec / recFileMetadata.indexInterval);
|
||||||
seek(5);
|
if (ndxNumber < 2) {
|
||||||
|
pause(); restart();
|
||||||
|
} else {
|
||||||
|
if (recFileMetadata.indexes[ndxNumber - 2] != null) { seek(ndxNumber - 2); }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function seekForward() {
|
function seekForward() {
|
||||||
//console.log('seekForward');
|
var ndxNumber = Math.round(currentDeltaTimeTotalSec / recFileMetadata.indexInterval);
|
||||||
seek(10);
|
if (recFileMetadata.indexes[ndxNumber] != null) { seek(ndxNumber); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function progressBarSeek(event) {
|
||||||
|
var ndxNumber = Math.round((event.clientX / document.body.offsetWidth) * (recFileMetadata.indexes.length + 1)) - 1;
|
||||||
|
if (ndxNumber == -1) { pause(); restart(); } else { seek(ndxNumber); }
|
||||||
|
}
|
||||||
|
|
||||||
var SeekIndex;
|
var SeekIndex;
|
||||||
var SeekIndexPtr;
|
var SeekIndexPtr;
|
||||||
|
var SeekIndexTime;
|
||||||
|
var SeekPlayState;
|
||||||
function seek(indexNumber) {
|
function seek(indexNumber) {
|
||||||
//console.log('seek', indexNumber);
|
//console.log('seek', indexNumber);
|
||||||
if ((recFileMetadata.indexes == null) || (recFileMetadata.indexes[indexNumber] == null)) return null;
|
if ((recFileMetadata.indexes == null) || (recFileMetadata.indexes[indexNumber] == null)) return null;
|
||||||
|
SeekPlayState = playing;
|
||||||
|
pause();
|
||||||
restart();
|
restart();
|
||||||
//pause();
|
|
||||||
if (agentDesktop) { agentDesktop.Canvas.clearRect(0, 0, agentDesktop.CanvasId.width, agentDesktop.CanvasId.height); }
|
|
||||||
SeekIndex = recFileMetadata.indexes[indexNumber];
|
SeekIndex = recFileMetadata.indexes[indexNumber];
|
||||||
SeekIndexPtr = 3;
|
SeekIndexPtr = 3;
|
||||||
var ptr = SeekIndex[0];
|
recFileLastTime = SeekIndexTime = recFileStartTime + ((1 + indexNumber) * recFileMetadata.indexInterval * 1000);
|
||||||
|
recFilePtr = recFileIndexBasePtr + SeekIndex[0];
|
||||||
var width = SeekIndex[1];
|
var width = SeekIndex[1];
|
||||||
var height = SeekIndex[2];
|
var height = SeekIndex[2];
|
||||||
seekFetchNext();
|
|
||||||
|
if (recFileEndTime == 0) {
|
||||||
|
// File pointer progress bar
|
||||||
|
QS('progressbar').width = Math.floor(100 * (recFilePtr / recFile.size)) + '%';
|
||||||
|
} else {
|
||||||
|
// Time progress bar
|
||||||
|
QS('progressbar').width = Math.floor(((recFileLastTime - recFileStartTime) / (recFileEndTime - recFileStartTime)) * 100) + '%';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (agentDesktop) {
|
||||||
|
agentDesktop.Canvas.clearRect(0, 0, agentDesktop.CanvasId.width, agentDesktop.CanvasId.height);
|
||||||
|
agentDesktop.ProcessScreenMsg(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
QV('metadatadiv', false);
|
||||||
|
QV('Desk', false);
|
||||||
|
|
||||||
|
seekFetchNext(function () { QV('Desk', true); if (SeekPlayState) { play(); } });
|
||||||
}
|
}
|
||||||
|
|
||||||
function seekFetchNext() {
|
function seekFetchNext(func) {
|
||||||
if (SeekIndex[SeekIndexPtr] == null) { return; }
|
if (SeekIndex[SeekIndexPtr] == null) { func(); return; }
|
||||||
readBlockAt(recFileIndexBasePtr + SeekIndex[SeekIndexPtr], function (type, flags, time, data) {
|
readBlockAt(recFileIndexBasePtr + SeekIndex[SeekIndexPtr], function (type, flags, time, data) {
|
||||||
SeekIndexPtr++;
|
SeekIndexPtr++;
|
||||||
processBlockEx(type, flags, time, data, true);
|
processBlockEx(type, flags, SeekIndexTime, data, true);
|
||||||
seekFetchNext();
|
seekFetchNext(func);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user