Feature/kasm 3380 udp frame (#46)
* Handle the frame id in udp packets * refactor udp message buffer, add frame_id and rect cnt * refactor udp to work with new display.js * additional debug metrics, clear frame buffer on transitions tcp/udp * fix udp with display refactor, KASM-3541 cancelAnimationFrame Co-authored-by: Lauri Kasanen <cand@gmx.com> Co-authored-by: mattmcclaskey <matt@kasmweb.com>
This commit is contained in:
parent
3a6a63cde3
commit
437830d497
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* noVNC: HTML5 VNC client
|
* KasmVNC: HTML5 VNC client
|
||||||
|
* Copyright (C) 2020 Kasm Technologies
|
||||||
* Copyright (C) 2019 The noVNC Authors
|
* Copyright (C) 2019 The noVNC Authors
|
||||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||||
*
|
*
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* noVNC: HTML5 VNC client
|
* KasmVNC: HTML5 VNC client
|
||||||
|
* Copyright (C) 2020 Kasm Technologies
|
||||||
* Copyright (C) 2019 The noVNC Authors
|
* Copyright (C) 2019 The noVNC Authors
|
||||||
* (c) 2012 Michael Tinglof, Joe Balaz, Les Piech (Mercuri.ca)
|
* (c) 2012 Michael Tinglof, Joe Balaz, Les Piech (Mercuri.ca)
|
||||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* noVNC: HTML5 VNC client
|
* KasmVNC: HTML5 VNC client
|
||||||
* Copyright (C) 2019 The noVNC Authors
|
* Copyright (C) 2020 Kasm Technologies
|
||||||
* (c) 2012 Michael Tinglof, Joe Balaz, Les Piech (Mercuri.ca)
|
|
||||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||||
*
|
*
|
||||||
* See README.md for usage and integration instructions.
|
* See README.md for usage and integration instructions.
|
||||||
|
@ -15,6 +14,7 @@ export default class UDPDecoder {
|
||||||
constructor() {
|
constructor() {
|
||||||
this._filter = null;
|
this._filter = null;
|
||||||
this._palette = new Uint8Array(1024); // 256 * 4 (max palette size * max bytes-per-pixel)
|
this._palette = new Uint8Array(1024); // 256 * 4 (max palette size * max bytes-per-pixel)
|
||||||
|
this._directDraw = false; //Draw directly to the canvas without ordering
|
||||||
|
|
||||||
this._zlibs = [];
|
this._zlibs = [];
|
||||||
for (let i = 0; i < 4; i++) {
|
for (let i = 0; i < 4; i++) {
|
||||||
|
@ -29,20 +29,15 @@ export default class UDPDecoder {
|
||||||
let ret;
|
let ret;
|
||||||
|
|
||||||
if (ctl === 0x08) {
|
if (ctl === 0x08) {
|
||||||
ret = this._fillRect(x, y, width, height,
|
ret = this._fillRect(x, y, width, height, data, display, depth, frame_id);
|
||||||
data, display, depth);
|
|
||||||
} else if (ctl === 0x09) {
|
} else if (ctl === 0x09) {
|
||||||
ret = this._jpegRect(x, y, width, height,
|
ret = this._jpegRect(x, y, width, height, data, display, depth, frame_id);
|
||||||
data, display, depth);
|
|
||||||
} else if (ctl === 0x0A) {
|
} else if (ctl === 0x0A) {
|
||||||
ret = this._pngRect(x, y, width, height,
|
ret = this._pngRect(x, y, width, height, data, display, depth, frame_id);
|
||||||
data, display, depth);
|
|
||||||
} else if ((ctl & 0x08) == 0) {
|
} else if ((ctl & 0x08) == 0) {
|
||||||
ret = this._basicRect(ctl, x, y, width, height,
|
ret = this._basicRect(ctl, x, y, width, height, data, display, depth, frame_id);
|
||||||
data, display, depth);
|
|
||||||
} else if (ctl === 0x0B) {
|
} else if (ctl === 0x0B) {
|
||||||
ret = this._webpRect(x, y, width, height,
|
ret = this._webpRect(x, y, width, height, data, display, depth, frame_id);
|
||||||
data, display, depth);
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error("Illegal udp compression received (ctl: " +
|
throw new Error("Illegal udp compression received (ctl: " +
|
||||||
ctl + ")");
|
ctl + ")");
|
||||||
|
@ -51,48 +46,48 @@ export default class UDPDecoder {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
_fillRect(x, y, width, height, data, display, depth) {
|
_fillRect(x, y, width, height, data, display, depth, frame_id) {
|
||||||
|
|
||||||
display.fillRect(x, y, width, height,
|
display.fillRect(x, y, width, height,
|
||||||
[data[13], data[14], data[15]], false);
|
[data[13], data[14], data[15]], frame_id, this._directDraw);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
_jpegRect(x, y, width, height, data, display, depth) {
|
_jpegRect(x, y, width, height, data, display, depth, frame_id) {
|
||||||
let img = this._readData(data);
|
let img = this._readData(data);
|
||||||
if (img === null) {
|
if (img === null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
display.imageRect(x, y, width, height, "image/jpeg", img);
|
display.imageRect(x, y, width, height, "image/jpeg", img, frame_id, this._directDraw);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
_webpRect(x, y, width, height, data, display, depth) {
|
_webpRect(x, y, width, height, data, display, depth, frame_id) {
|
||||||
let img = this._readData(data);
|
let img = this._readData(data);
|
||||||
if (img === null) {
|
if (img === null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
display.imageRect(x, y, width, height, "image/webp", img);
|
display.imageRect(x, y, width, height, "image/webp", img, frame_id, this._directDraw);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
_pngRect(x, y, width, height, data, display, depth) {
|
_pngRect(x, y, width, height, data, display, depth, frame_id) {
|
||||||
//throw new Error("PNG received in UDP rect");
|
//throw new Error("PNG received in UDP rect");
|
||||||
Log.Error("PNG received in UDP rect");
|
Log.Error("PNG received in UDP rect");
|
||||||
}
|
}
|
||||||
|
|
||||||
_basicRect(ctl, x, y, width, height, data, display, depth) {
|
_basicRect(ctl, x, y, width, height, data, display, depth, frame_id) {
|
||||||
let zlibs_flags = data[12];
|
let zlibs_flags = data[12];
|
||||||
// Reset streams if the server requests it
|
// Reset streams if the server requests it
|
||||||
for (let i = 0; i < 4; i++) {
|
for (let i = 0; i < 4; i++) {
|
||||||
if ((zlibs_flags >> i) & 1) {
|
if ((zlibs_flags >> i) & 1) {
|
||||||
this._zlibs[i].reset();
|
this._zlibs[i].reset();
|
||||||
Log.Debug("Reset zlib stream " + i);
|
//Log.Debug("Reset zlib stream " + i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,15 +105,15 @@ export default class UDPDecoder {
|
||||||
switch (filter) {
|
switch (filter) {
|
||||||
case 0: // CopyFilter
|
case 0: // CopyFilter
|
||||||
ret = this._copyFilter(streamId, x, y, width, height,
|
ret = this._copyFilter(streamId, x, y, width, height,
|
||||||
data, display, depth, data_index);
|
data, display, depth, frame_id, data_index);
|
||||||
break;
|
break;
|
||||||
case 1: // PaletteFilter
|
case 1: // PaletteFilter
|
||||||
ret = this._paletteFilter(streamId, x, y, width, height,
|
ret = this._paletteFilter(streamId, x, y, width, height,
|
||||||
data, display, depth);
|
data, display, depth, frame_id);
|
||||||
break;
|
break;
|
||||||
case 2: // GradientFilter
|
case 2: // GradientFilter
|
||||||
ret = this._gradientFilter(streamId, x, y, width, height,
|
ret = this._gradientFilter(streamId, x, y, width, height,
|
||||||
data, display, depth);
|
data, display, depth, frame_id);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error("Illegal tight filter received (ctl: " +
|
throw new Error("Illegal tight filter received (ctl: " +
|
||||||
|
@ -128,7 +123,7 @@ export default class UDPDecoder {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
_copyFilter(streamId, x, y, width, height, data, display, depth, data_index=14) {
|
_copyFilter(streamId, x, y, width, height, data, display, depth, frame_id, data_index=14) {
|
||||||
const uncompressedSize = width * height * 3;
|
const uncompressedSize = width * height * 3;
|
||||||
|
|
||||||
if (uncompressedSize === 0) {
|
if (uncompressedSize === 0) {
|
||||||
|
@ -156,12 +151,12 @@ export default class UDPDecoder {
|
||||||
rgbx[i + 3] = 255; // Alpha
|
rgbx[i + 3] = 255; // Alpha
|
||||||
}
|
}
|
||||||
|
|
||||||
display.blitImage(x, y, width, height, rgbx, 0, false);
|
display.blitImage(x, y, width, height, rgbx, 0, frame_id, this._directDraw);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
_paletteFilter(streamId, x, y, width, height, data, display, depth) {
|
_paletteFilter(streamId, x, y, width, height, data, display, depth, frame_id) {
|
||||||
const numColors = data[14] + 1;
|
const numColors = data[14] + 1;
|
||||||
const paletteSize = numColors * 3;
|
const paletteSize = numColors * 3;
|
||||||
let palette = data.slice(15, 15 + paletteSize);
|
let palette = data.slice(15, 15 + paletteSize);
|
||||||
|
@ -190,15 +185,15 @@ export default class UDPDecoder {
|
||||||
|
|
||||||
// Convert indexed (palette based) image data to RGB
|
// Convert indexed (palette based) image data to RGB
|
||||||
if (numColors == 2) {
|
if (numColors == 2) {
|
||||||
this._monoRect(x, y, width, height, data, palette, display);
|
this._monoRect(x, y, width, height, data, palette, display, frame_id);
|
||||||
} else {
|
} else {
|
||||||
this._paletteRect(x, y, width, height, data, palette, display);
|
this._paletteRect(x, y, width, height, data, palette, display, frame_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
_monoRect(x, y, width, height, data, palette, display) {
|
_monoRect(x, y, width, height, data, palette, display, frame_id) {
|
||||||
// Convert indexed (palette based) image data to RGB
|
// Convert indexed (palette based) image data to RGB
|
||||||
// TODO: reduce number of calculations inside loop
|
// TODO: reduce number of calculations inside loop
|
||||||
const dest = this._getScratchBuffer(width * height * 4);
|
const dest = this._getScratchBuffer(width * height * 4);
|
||||||
|
@ -228,10 +223,10 @@ export default class UDPDecoder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
display.blitImage(x, y, width, height, dest, 0, false);
|
display.blitImage(x, y, width, height, dest, 0, frame_id, this._directDraw);
|
||||||
}
|
}
|
||||||
|
|
||||||
_paletteRect(x, y, width, height, data, palette, display) {
|
_paletteRect(x, y, width, height, data, palette, display, frame_id) {
|
||||||
// Convert indexed (palette based) image data to RGB
|
// Convert indexed (palette based) image data to RGB
|
||||||
const dest = this._getScratchBuffer(width * height * 4);
|
const dest = this._getScratchBuffer(width * height * 4);
|
||||||
const total = width * height * 4;
|
const total = width * height * 4;
|
||||||
|
@ -243,10 +238,10 @@ export default class UDPDecoder {
|
||||||
dest[i + 3] = 255;
|
dest[i + 3] = 255;
|
||||||
}
|
}
|
||||||
|
|
||||||
display.blitImage(x, y, width, height, dest, 0, false);
|
display.blitImage(x, y, width, height, dest, 0, frame_id, this._directDraw);
|
||||||
}
|
}
|
||||||
|
|
||||||
_gradientFilter(streamId, x, y, width, height, data, display, depth) {
|
_gradientFilter(streamId, x, y, width, height, data, display, depth, frame_id) {
|
||||||
throw new Error("Gradient filter not implemented");
|
throw new Error("Gradient filter not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* noVNC: HTML5 VNC client
|
* KasmVNC: HTML5 VNC client
|
||||||
|
* Copyright (C) 2020 Kasm Technologies
|
||||||
* Copyright (C) 2019 The noVNC Authors
|
* Copyright (C) 2019 The noVNC Authors
|
||||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||||
*
|
*
|
||||||
|
@ -18,7 +19,7 @@ export default class Display {
|
||||||
/*
|
/*
|
||||||
For performance reasons we use a multi dimensional array
|
For performance reasons we use a multi dimensional array
|
||||||
1st Dimension of Array Represents Frames, each element is a Frame
|
1st Dimension of Array Represents Frames, each element is a Frame
|
||||||
2nd Dimension contains 4 elements
|
2nd Dimension is the contents of a frame and meta data, contains 4 elements
|
||||||
0 - int, FrameID
|
0 - int, FrameID
|
||||||
1 - int, Rect Count
|
1 - int, Rect Count
|
||||||
2 - Array of Rect objects
|
2 - Array of Rect objects
|
||||||
|
@ -26,11 +27,6 @@ export default class Display {
|
||||||
4 - int, index of current rect (post-processing)
|
4 - int, index of current rect (post-processing)
|
||||||
*/
|
*/
|
||||||
this._asyncFrameQueue = [];
|
this._asyncFrameQueue = [];
|
||||||
/*
|
|
||||||
Buffer for incoming frames. The larger the buffer the more time there is to collect, process, and order rects
|
|
||||||
but the more delay there is. May need to adjust this higher for lower power devices when UDP is complete.
|
|
||||||
Decoders that use WASM in parallel can also cause out of order rects
|
|
||||||
*/
|
|
||||||
this._maxAsyncFrameQueue = 3;
|
this._maxAsyncFrameQueue = 3;
|
||||||
this._clearAsyncQueue();
|
this._clearAsyncQueue();
|
||||||
|
|
||||||
|
@ -68,13 +64,15 @@ export default class Display {
|
||||||
this._lastFlip = Date.now();
|
this._lastFlip = Date.now();
|
||||||
this._droppedFrames = 0;
|
this._droppedFrames = 0;
|
||||||
this._droppedRects = 0;
|
this._droppedRects = 0;
|
||||||
this._missingRectCnt = 0;
|
this._forcedFrameCnt = 0;
|
||||||
setInterval(function() {
|
this._missingFlipRect = 0;
|
||||||
|
this._lateFlipRect = 0;
|
||||||
|
this._frameStatsInterval = setInterval(function() {
|
||||||
let delta = Date.now() - this._lastFlip;
|
let delta = Date.now() - this._lastFlip;
|
||||||
if (delta > 0) {
|
if (delta > 0) {
|
||||||
this._fps = (this._flipCnt / (delta / 1000)).toFixed(2);
|
this._fps = (this._flipCnt / (delta / 1000)).toFixed(2);
|
||||||
}
|
}
|
||||||
Log.Info('Dropped Frames: ' + this._droppedFrames + ' Dropped Rects: ' + this._droppedRects + ' Missing Rect Cnt: ' + this._missingRectCnt);
|
Log.Info('Dropped Frames: ' + this._droppedFrames + ' Dropped Rects: ' + this._droppedRects + ' Forced Frames: ' + this._forcedFrameCnt + ' Missing Flips: ' + this._missingFlipRect + ' Late Flips: ' + this._lateFlipRect);
|
||||||
this._flipCnt = 0;
|
this._flipCnt = 0;
|
||||||
this._lastFlip = Date.now();
|
this._lastFlip = Date.now();
|
||||||
}.bind(this), 5000);
|
}.bind(this), 5000);
|
||||||
|
@ -91,7 +89,7 @@ export default class Display {
|
||||||
this.onflush = () => { }; // A flush request has finished
|
this.onflush = () => { }; // A flush request has finished
|
||||||
|
|
||||||
// Use requestAnimationFrame to write to canvas, to match display refresh rate
|
// Use requestAnimationFrame to write to canvas, to match display refresh rate
|
||||||
window.requestAnimationFrame( () => { this._pushAsyncFrame(); });
|
this._animationFrameID = window.requestAnimationFrame( () => { this._pushAsyncFrame(); });
|
||||||
|
|
||||||
Log.Debug("<< Display.constructor");
|
Log.Debug("<< Display.constructor");
|
||||||
}
|
}
|
||||||
|
@ -259,7 +257,11 @@ export default class Display {
|
||||||
this.viewportChangePos(0, 0);
|
this.viewportChangePos(0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// rendering canvas
|
/*
|
||||||
|
* Mark the specified frame with a rect count
|
||||||
|
* @param {number} frame_id - The frame ID of the target frame
|
||||||
|
* @param {number} rect_cnt - The number of rects in the target frame
|
||||||
|
*/
|
||||||
flip(frame_id, rect_cnt) {
|
flip(frame_id, rect_cnt) {
|
||||||
this._asyncRenderQPush({
|
this._asyncRenderQPush({
|
||||||
'type': 'flip',
|
'type': 'flip',
|
||||||
|
@ -268,17 +270,44 @@ export default class Display {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Is the frame queue full
|
||||||
|
* @returns {bool} is the queue full
|
||||||
|
*/
|
||||||
pending() {
|
pending() {
|
||||||
//is the slot in the queue for the newest frame in use
|
//is the slot in the queue for the newest frame in use
|
||||||
return this._asyncFrameQueue[this._maxAsyncFrameQueue - 1][0] > 0;
|
return this._asyncFrameQueue[this._maxAsyncFrameQueue - 1][0] > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
flush() {
|
/*
|
||||||
|
* Force the oldest frame in the queue to render, whether ready or not.
|
||||||
|
* @param {bool} onflush_message - The caller wants an onflush event triggered once complete. This is
|
||||||
|
* useful for TCP, allowing the websocket to block until we are ready to process the next frame.
|
||||||
|
* UDP cannot block and thus no need to notify the caller when complete.
|
||||||
|
*/
|
||||||
|
flush(onflush_message=true) {
|
||||||
//force oldest frame to render
|
//force oldest frame to render
|
||||||
this._asyncFrameComplete(0, true);
|
this._asyncFrameComplete(0, true);
|
||||||
|
|
||||||
//this in effect blocks more incoming frames until the oldest frame has been rendered to canvas (tcp only)
|
if (onflush_message)
|
||||||
this._flushing = true;
|
this._flushing = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Clears the buffer of anything that has not yet been displayed.
|
||||||
|
* This must be called when switching between transit modes tcp/udp
|
||||||
|
*/
|
||||||
|
clear() {
|
||||||
|
this._clearAsyncQueue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Cleans up resources, should be called on a disconnect
|
||||||
|
*/
|
||||||
|
dispose() {
|
||||||
|
clearInterval(this._frameStatsInterval);
|
||||||
|
cancelAnimationFrame(this._animationFrameID);
|
||||||
|
this.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
fillRect(x, y, width, height, color, frame_id, fromQueue) {
|
fillRect(x, y, width, height, color, frame_id, fromQueue) {
|
||||||
|
@ -432,7 +461,7 @@ export default class Display {
|
||||||
let frameIx = -1;
|
let frameIx = -1;
|
||||||
let oldestFrameID = Number.MAX_SAFE_INTEGER;
|
let oldestFrameID = Number.MAX_SAFE_INTEGER;
|
||||||
let newestFrameID = 0;
|
let newestFrameID = 0;
|
||||||
for (let i=0; i<this._maxAsyncFrameQueue; i++) {
|
for (let i=0; i<this._asyncFrameQueue.length; i++) {
|
||||||
if (rect.frame_id == this._asyncFrameQueue[i][0]) {
|
if (rect.frame_id == this._asyncFrameQueue[i][0]) {
|
||||||
this._asyncFrameQueue[i][2].push(rect);
|
this._asyncFrameQueue[i][2].push(rect);
|
||||||
frameIx = i;
|
frameIx = i;
|
||||||
|
@ -440,7 +469,6 @@ export default class Display {
|
||||||
} else if (this._asyncFrameQueue[i][0] == 0) {
|
} else if (this._asyncFrameQueue[i][0] == 0) {
|
||||||
let rect_cnt = ((rect.type == "flip") ? rect.rect_cnt : 0);
|
let rect_cnt = ((rect.type == "flip") ? rect.rect_cnt : 0);
|
||||||
this._asyncFrameQueue[i][0] = rect.frame_id;
|
this._asyncFrameQueue[i][0] = rect.frame_id;
|
||||||
this._asyncFrameQueue[i][1] = rect_cnt;
|
|
||||||
this._asyncFrameQueue[i][2].push(rect);
|
this._asyncFrameQueue[i][2].push(rect);
|
||||||
this._asyncFrameQueue[i][3] = (rect_cnt == 1);
|
this._asyncFrameQueue[i][3] = (rect_cnt == 1);
|
||||||
frameIx = i;
|
frameIx = i;
|
||||||
|
@ -453,7 +481,13 @@ export default class Display {
|
||||||
if (frameIx >= 0) {
|
if (frameIx >= 0) {
|
||||||
if (rect.type == "flip") {
|
if (rect.type == "flip") {
|
||||||
//flip rect contains the rect count for the frame
|
//flip rect contains the rect count for the frame
|
||||||
|
if (this._asyncFrameQueue[frameIx][1] !== 0) {
|
||||||
|
Log.Warn("Redundant flip rect, current rect_cnt: " + this._asyncFrameQueue[frameIx][1] + ", new rect_cnt: " + rect.rect_cnt );
|
||||||
|
}
|
||||||
this._asyncFrameQueue[frameIx][1] = rect.rect_cnt;
|
this._asyncFrameQueue[frameIx][1] = rect.rect_cnt;
|
||||||
|
if (rect.rect_cnt == 0) {
|
||||||
|
Log.Warn("Invalid rect count");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._asyncFrameQueue[frameIx][1] == this._asyncFrameQueue[frameIx][2].length) {
|
if (this._asyncFrameQueue[frameIx][1] == this._asyncFrameQueue[frameIx][2].length) {
|
||||||
|
@ -464,6 +498,7 @@ export default class Display {
|
||||||
if (rect.frame_id < oldestFrameID) {
|
if (rect.frame_id < oldestFrameID) {
|
||||||
//rect is older than any frame in the queue, drop it
|
//rect is older than any frame in the queue, drop it
|
||||||
this._droppedRects++;
|
this._droppedRects++;
|
||||||
|
if (rect.type == "flip") { this._lateFlipRect++; }
|
||||||
return;
|
return;
|
||||||
} else if (rect.frame_id > newestFrameID) {
|
} else if (rect.frame_id > newestFrameID) {
|
||||||
//frame is newer than any frame in the queue, drop old frames
|
//frame is newer than any frame in the queue, drop old frames
|
||||||
|
@ -497,9 +532,12 @@ export default class Display {
|
||||||
|
|
||||||
if (force) {
|
if (force) {
|
||||||
if (this._asyncFrameQueue[frameIx][1] == 0) {
|
if (this._asyncFrameQueue[frameIx][1] == 0) {
|
||||||
this._missingRectCnt++;
|
this._missingFlipRect++; //at minimum the flip rect is missing
|
||||||
} else if (this._asyncFrameQueue[frameIx][1] !== this._asyncFrameQueue[frameIx][2].length) {
|
} else if (this._asyncFrameQueue[frameIx][1] !== this._asyncFrameQueue[frameIx][2].length) {
|
||||||
this._droppedRects += (this._asyncFrameQueue[frameIx][1] - this._asyncFrameQueue[frameIx][2].length);
|
this._droppedRects += (this._asyncFrameQueue[frameIx][1] - this._asyncFrameQueue[frameIx][2].length);
|
||||||
|
if (this._asyncFrameQueue[frameIx][2].length > this._asyncFrameQueue[frameIx][1]) {
|
||||||
|
Log.Warn("Frame has more rects than the reported rect_cnt.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
while (currentFrameRectIx < this._asyncFrameQueue[frameIx][2].length) {
|
while (currentFrameRectIx < this._asyncFrameQueue[frameIx][2].length) {
|
||||||
if (this._asyncFrameQueue[frameIx][2][currentFrameRectIx].type == 'img' && !this._asyncFrameQueue[frameIx][2][currentFrameRectIx].img.complete) {
|
if (this._asyncFrameQueue[frameIx][2][currentFrameRectIx].type == 'img' && !this._asyncFrameQueue[frameIx][2][currentFrameRectIx].img.complete) {
|
||||||
|
@ -525,10 +563,12 @@ export default class Display {
|
||||||
/*
|
/*
|
||||||
Push the oldest frame in the buffer to the canvas if it is marked ready
|
Push the oldest frame in the buffer to the canvas if it is marked ready
|
||||||
*/
|
*/
|
||||||
_pushAsyncFrame() {
|
_pushAsyncFrame(force=false) {
|
||||||
if (this._asyncFrameQueue[0][3]) {
|
if (this._asyncFrameQueue[0][3]) {
|
||||||
let frame = this._asyncFrameQueue.shift()[2];
|
let frame = this._asyncFrameQueue.shift()[2];
|
||||||
this._asyncFrameQueue.push([ 0, 0, [], false, 0 ]);
|
if (this._asyncFrameQueue.length < this._maxAsyncFrameQueue) {
|
||||||
|
this._asyncFrameQueue.push([ 0, 0, [], false, 0 ]);
|
||||||
|
}
|
||||||
|
|
||||||
//render the selected frame
|
//render the selected frame
|
||||||
for (let i = 0; i < frame.length; i++) {
|
for (let i = 0; i < frame.length; i++) {
|
||||||
|
@ -560,7 +600,9 @@ export default class Display {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.requestAnimationFrame( () => { this._pushAsyncFrame(); });
|
if (!force) {
|
||||||
|
window.requestAnimationFrame( () => { this._pushAsyncFrame(); });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_rescale(factor) {
|
_rescale(factor) {
|
||||||
|
|
34
core/rfb.js
34
core/rfb.js
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* noVNC: HTML5 VNC client
|
* KasmVNC: HTML5 VNC client
|
||||||
|
* Copyright (C) 2020 Kasm Technologies
|
||||||
* Copyright (C) 2020 The noVNC Authors
|
* Copyright (C) 2020 The noVNC Authors
|
||||||
* Licensed under MPL 2.0 (see LICENSE.txt)
|
* Licensed under MPL 2.0 (see LICENSE.txt)
|
||||||
*
|
*
|
||||||
|
@ -1107,20 +1108,25 @@ export default class RFB extends EventTargetMixin {
|
||||||
(u8[14] << 16) +
|
(u8[14] << 16) +
|
||||||
(u8[15] << 24), 10);
|
(u8[15] << 24), 10);
|
||||||
// TODO: check the hash. It's the low 32 bits of XXH64, seed 0
|
// TODO: check the hash. It's the low 32 bits of XXH64, seed 0
|
||||||
|
const frame_id = parseInt(u8[16] +
|
||||||
|
(u8[17] << 8) +
|
||||||
|
(u8[18] << 16) +
|
||||||
|
(u8[19] << 24), 10);
|
||||||
|
|
||||||
if (me._transitConnectionState !== me.TransitConnectionStates.Udp) {
|
if (me._transitConnectionState !== me.TransitConnectionStates.Udp) {
|
||||||
|
me._display.clear();
|
||||||
me._changeTransitConnectionState(me.TransitConnectionStates.Udp);
|
me._changeTransitConnectionState(me.TransitConnectionStates.Udp);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pieces == 1) { // Handle it immediately
|
if (pieces == 1) { // Handle it immediately
|
||||||
me._handleUdpRect(u8.slice(16));
|
me._handleUdpRect(u8.slice(20), frame_id);
|
||||||
} else { // Insert into wait array
|
} else { // Use buffer
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
|
||||||
if (udpBuffer.has(id)) {
|
if (udpBuffer.has(id)) {
|
||||||
let item = udpBuffer.get(id);
|
let item = udpBuffer.get(id);
|
||||||
item.recieved_pieces += 1;
|
item.recieved_pieces += 1;
|
||||||
item.data[i] = u8.slice(16);
|
item.data[i] = u8.slice(20);
|
||||||
item.total_bytes += item.data[i].length;
|
item.total_bytes += item.data[i].length;
|
||||||
|
|
||||||
if (item.total_pieces == item.recieved_pieces) {
|
if (item.total_pieces == item.recieved_pieces) {
|
||||||
|
@ -1132,7 +1138,7 @@ export default class RFB extends EventTargetMixin {
|
||||||
z += item.data[x].length;
|
z += item.data[x].length;
|
||||||
}
|
}
|
||||||
udpBuffer.delete(id);
|
udpBuffer.delete(id);
|
||||||
me._handleUdpRect(finaldata);
|
me._handleUdpRect(finaldata, frame_id);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let item = {
|
let item = {
|
||||||
|
@ -1142,7 +1148,7 @@ export default class RFB extends EventTargetMixin {
|
||||||
total_bytes: 0, // total size of all data pieces combined
|
total_bytes: 0, // total size of all data pieces combined
|
||||||
data: new Array(pieces)
|
data: new Array(pieces)
|
||||||
}
|
}
|
||||||
item.data[i] = u8.slice(16);
|
item.data[i] = u8.slice(20);
|
||||||
item.total_bytes = item.data[i].length;
|
item.total_bytes = item.data[i].length;
|
||||||
udpBuffer.set(id, item);
|
udpBuffer.set(id, item);
|
||||||
}
|
}
|
||||||
|
@ -1192,6 +1198,7 @@ export default class RFB extends EventTargetMixin {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this._display.dispose();
|
||||||
clearTimeout(this._resizeTimeout);
|
clearTimeout(this._resizeTimeout);
|
||||||
clearTimeout(this._mouseMoveTimer);
|
clearTimeout(this._mouseMoveTimer);
|
||||||
Log.Debug("<< RFB.disconnect");
|
Log.Debug("<< RFB.disconnect");
|
||||||
|
@ -3054,7 +3061,7 @@ export default class RFB extends EventTargetMixin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleUdpRect(data) {
|
_handleUdpRect(data, frame_id) {
|
||||||
let frame = {
|
let frame = {
|
||||||
x: (data[0] << 8) + data[1],
|
x: (data[0] << 8) + data[1],
|
||||||
y: (data[2] << 8) + data[3],
|
y: (data[2] << 8) + data[3],
|
||||||
|
@ -3066,10 +3073,9 @@ export default class RFB extends EventTargetMixin {
|
||||||
|
|
||||||
switch (frame.encoding) {
|
switch (frame.encoding) {
|
||||||
case encodings.pseudoEncodingLastRect:
|
case encodings.pseudoEncodingLastRect:
|
||||||
if (document.visibilityState !== "hidden") {
|
this._display.flip(frame_id, frame.x + 1); //Last Rect message, first 16 bytes contain rect count
|
||||||
this._display.flip(false); //TODO: UDP is now broken, flip needs rect count and frame number
|
if (this._display.pending())
|
||||||
this._udpBuffer.clear();
|
this._display.flush(false);
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case encodings.encodingTight:
|
case encodings.encodingTight:
|
||||||
let decoder = this._decoders[encodings.encodingUDP];
|
let decoder = this._decoders[encodings.encodingUDP];
|
||||||
|
@ -3077,7 +3083,7 @@ export default class RFB extends EventTargetMixin {
|
||||||
decoder.decodeRect(frame.x, frame.y,
|
decoder.decodeRect(frame.x, frame.y,
|
||||||
frame.width, frame.height,
|
frame.width, frame.height,
|
||||||
data, this._display,
|
data, this._display,
|
||||||
this._fbDepth);
|
this._fbDepth, frame_id);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this._fail("Error decoding rect: " + err);
|
this._fail("Error decoding rect: " + err);
|
||||||
return false;
|
return false;
|
||||||
|
@ -3203,7 +3209,7 @@ export default class RFB extends EventTargetMixin {
|
||||||
this._FBU.encoding = null;
|
this._FBU.encoding = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._FBU.rect_total > 0) {
|
if (this._FBU.rect_total > 1) {
|
||||||
this._display.flip(this._FBU.frame_id, this._FBU.rect_total);
|
this._display.flip(this._FBU.frame_id, this._FBU.rect_total);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3528,6 +3534,7 @@ export default class RFB extends EventTargetMixin {
|
||||||
this._udpTransitFailures++;
|
this._udpTransitFailures++;
|
||||||
}
|
}
|
||||||
this._changeTransitConnectionState(this.TransitConnectionStates.Tcp);
|
this._changeTransitConnectionState(this.TransitConnectionStates.Tcp);
|
||||||
|
this._display.clear();
|
||||||
if (this._useUdp) {
|
if (this._useUdp) {
|
||||||
if (this._udpConnectFailures < 3 && this._udpTransitFailures < 3) {
|
if (this._udpConnectFailures < 3 && this._udpTransitFailures < 3) {
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
|
@ -3539,6 +3546,7 @@ export default class RFB extends EventTargetMixin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (this._transitConnectionState == this.TransitConnectionStates.Downgrading) {
|
} else if (this._transitConnectionState == this.TransitConnectionStates.Downgrading) {
|
||||||
|
this._display.clear();
|
||||||
this._changeTransitConnectionState(this.TransitConnectionStates.Tcp);
|
this._changeTransitConnectionState(this.TransitConnectionStates.Tcp);
|
||||||
}
|
}
|
||||||
return decoder.decodeRect(this._FBU.x, this._FBU.y,
|
return decoder.decodeRect(this._FBU.x, this._FBU.y,
|
||||||
|
|
Loading…
Reference in New Issue