Bugfix/kasm 2053 video quality (#19)

* KASM-2053 Fixes previous issues where there was no difference between medium and high
* Expands quality settings to adjust more rendering settings
* Adds an extreme quality setting and custom
* Adds the quality setting to the noVNC control panel, was previously only exposed in the backend for integration with Kasm Workspaces
* Switching quality settings no longer requires reconnecting.
This commit is contained in:
mmcclaskey 2021-11-08 12:57:24 -05:00 committed by GitHub
parent e11e76e2c7
commit 2faf2049c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 214 additions and 137 deletions

148
app/ui.js
View File

@ -206,6 +206,7 @@ const UI = {
UI.initSetting('treat_lossless', 7);
UI.initSetting('jpeg_video_quality', 5);
UI.initSetting('webp_video_quality', 5);
UI.initSetting('video_quality', 2);
UI.initSetting('anti_aliasing', 0);
UI.initSetting('video_area', 65);
UI.initSetting('video_time', 5);
@ -228,14 +229,12 @@ const UI = {
UI.initSetting('enable_perf_stats', false);
if (WebUtil.isInsideKasmVDI()) {
UI.initSetting('video_quality', 1);
UI.initSetting('clipboard_up', false);
UI.initSetting('clipboard_down', false);
UI.initSetting('clipboard_seamless', false);
UI.initSetting('enable_webp', false);
UI.initSetting('resize', 'off');
} else {
UI.initSetting('video_quality', 3);
UI.initSetting('clipboard_up', true);
UI.initSetting('clipboard_down', true);
UI.initSetting('clipboard_seamless', true);
@ -244,6 +243,7 @@ const UI = {
}
UI.setupSettingLabels();
UI.updateQuality();
},
// Adds a link to the label elements on the corresponding input elements
setupSettingLabels() {
@ -430,6 +430,8 @@ const UI = {
UI.addSettingChangeHandler('treat_lossless', UI.updateQuality);
UI.addSettingChangeHandler('anti_aliasing');
UI.addSettingChangeHandler('anti_aliasing', UI.updateQuality);
UI.addSettingChangeHandler('video_quality');
UI.addSettingChangeHandler('video_quality', UI.updateQuality);
UI.addSettingChangeHandler('jpeg_video_quality');
UI.addSettingChangeHandler('jpeg_video_quality', UI.updateQuality);
UI.addSettingChangeHandler('webp_video_quality');
@ -862,10 +864,14 @@ const UI = {
},
// Set the new value, update and disable form control setting
forceSetting(name, val) {
forceSetting(name, val, disable=true) {
WebUtil.setSetting(name, val);
UI.updateSetting(name);
UI.disableSetting(name);
if (disable) {
UI.disableSetting(name);
} else {
UI.enableSetting(name);
}
},
// Update cookie and form control setting. If value is not set, then
@ -880,6 +886,7 @@ const UI = {
ctrl.checked = value;
} else if (typeof ctrl.options !== 'undefined') {
value = String(value);
for (let i = 0; i < ctrl.options.length; i += 1) {
if (ctrl.options[i].value === value) {
ctrl.selectedIndex = i;
@ -974,6 +981,7 @@ const UI = {
UI.updateSetting('anti_aliasing', 0);
UI.updateSetting('jpeg_video_quality', 5);
UI.updateSetting('webp_video_quality', 5);
UI.updateSetting('video_quality', 2);
UI.updateSetting('video_area', 65);
UI.updateSetting('video_time', 5);
UI.updateSetting('video_out_time', 3);
@ -1289,7 +1297,7 @@ const UI = {
UI.rfb.compressionLevel = parseInt(UI.getSetting('compression'));
UI.rfb.showDotCursor = UI.getSetting('show_dot');
UI.rfb.idleDisconnect = UI.getSetting('idle_disconnect');
UI.rfb.videoQuality = UI.getSetting('video_quality');
UI.rfb.videoQuality = parseInt(UI.getSetting('video_quality'));
UI.rfb.antiAliasing = UI.getSetting('anti_aliasing');
UI.rfb.clipboardUp = UI.getSetting('clipboard_up');
UI.rfb.clipboardDown = UI.getSetting('clipboard_down');
@ -1395,6 +1403,7 @@ const UI = {
return;
}
UI.connect(null, UI.reconnectPassword);
},
@ -1462,6 +1471,11 @@ const UI = {
UI.openControlbar();
UI.openConnectPanel();
if (UI.forceReconnect) {
UI.forceReconnect = false;
UI.connect(null, UI.reconnectPassword);
}
},
securityFailed(e) {
@ -1494,7 +1508,8 @@ const UI = {
}
break;
case 'setvideoquality':
UI.rfb.videoQuality = event.data.value;
UI.forceSetting('video_quality', parseInt(event.data.value), false);
UI.updateQuality();
break;
}
}
@ -1704,29 +1719,108 @@ const UI = {
* ------v------*/
updateQuality() {
if (!UI.rfb) return;
let present_mode = parseInt(UI.getSetting('video_quality'));
if (!UI.updatingSettings) {
// avoid sending too many, will only apply when there are changes
setTimeout(function() {
UI.rfb.qualityLevel = parseInt(UI.getSetting('quality'));
UI.rfb.antiAliasing = parseInt(UI.getSetting('anti_aliasing'));
UI.rfb.dynamicQualityMin = parseInt(UI.getSetting('dynamic_quality_min'));
UI.rfb.dynamicQualityMax = parseInt(UI.getSetting('dynamic_quality_max'));
UI.rfb.jpegVideoQuality = parseInt(UI.getSetting('jpeg_video_quality'));
UI.rfb.webpVideoQuality = parseInt(UI.getSetting('webp_video_quality'));
UI.rfb.videoArea = parseInt(UI.getSetting('video_area'));
UI.rfb.videoTime = parseInt(UI.getSetting('video_time'));
UI.rfb.videoOutTime = parseInt(UI.getSetting('video_out_time'));
UI.rfb.videoScaling = parseInt(UI.getSetting('video_scaling'));
UI.rfb.treatLossless = parseInt(UI.getSetting('treat_lossless'));
UI.rfb.maxVideoResolutionX = parseInt(UI.getSetting('max_video_resolution_x'));
UI.rfb.maxVideoResolutionY = parseInt(UI.getSetting('max_video_resolution_y'));
UI.rfb.frameRate = parseInt(UI.getSetting('framerate'));
UI.rfb.enableWebP = UI.getSetting('enable_webp');
UI.showStatus("Refresh page to apply encoding changes.");
}, 2000);
// video_quality preset values
switch (present_mode) {
case 10: //custom
UI.enableSetting('dynamic_quality_min');
UI.enableSetting('dynamic_quality_max');
UI.enableSetting('treat_lossless');
UI.enableSetting('video_time');
UI.enableSetting('video_area');
UI.enableSetting('max_video_resolution_x');
UI.enableSetting('max_video_resolution_y');
UI.enableSetting('jpeg_video_quality');
UI.enableSetting('webp_video_quality');
UI.enableSetting('framerate');
UI.enableSetting('video_scaling');
UI.enableSetting('video_out_time');
UI.showStatus("Refresh or reconnect to apply changes.");
return;
case 4: //extreme
UI.forceSetting('dynamic_quality_min', 8);
UI.forceSetting('dynamic_quality_max', 9);
UI.forceSetting('framerate', 30);
UI.forceSetting('treat_lossless', 8);
// effectively disables video mode
UI.forceSetting('video_time', 100);
UI.forceSetting('video_area', 100);
// go ahead and set video mode settings, won't be used
UI.forceSetting('max_video_resolution_x', 1920);
UI.forceSetting('max_video_resolution_y', 1080);
UI.forceSetting('jpeg_video_quality', 8);
UI.forceSetting('webp_video_quality', 8);
UI.forceSetting('video_scaling', 0);
UI.forceSetting('video_out_time', 3);
break;
case 3: // high
UI.forceSetting('jpeg_video_quality', 8);
UI.forceSetting('webp_video_quality', 8);
UI.forceSetting('dynamic_quality_min', 7);
UI.forceSetting('dynamic_quality_max', 9);
UI.forceSetting('max_video_resolution_x', 1920);
UI.forceSetting('max_video_resolution_y', 1080);
UI.forceSetting('framerate', 30);
UI.forceSetting('treat_lossless', 8);
UI.forceSetting('video_time', 5);
UI.forceSetting('video_area', 65);
UI.forceSetting('video_scaling', 0);
UI.forceSetting('video_out_time', 3);
break;
case 1: // low, resolution capped at 720p keeping aspect ratio
UI.forceSetting('jpeg_video_quality', 5);
UI.forceSetting('webp_video_quality', 4);
UI.forceSetting('dynamic_quality_min', 3);
UI.forceSetting('dynamic_quality_max', 7);
UI.forceSetting('max_video_resolution_x', 960);
UI.forceSetting('max_video_resolution_y', 540);
UI.forceSetting('framerate', 22);
UI.forceSetting('treat_lossless', 7);
UI.forceSetting('video_time', 5);
UI.forceSetting('video_area', 65);
UI.forceSetting('video_scaling', 0);
UI.forceSetting('video_out_time', 3);
break;
case 2: // medium
case 0: // static resolution, but same settings as medium
default:
UI.forceSetting('jpeg_video_quality', 7);
UI.forceSetting('webp_video_quality', 7);
UI.forceSetting('dynamic_quality_min', 4);
UI.forceSetting('dynamic_quality_max', 9);
UI.forceSetting('max_video_resolution_x', 960);
UI.forceSetting('max_video_resolution_y', 540);
UI.forceSetting('framerate', 24);
UI.forceSetting('treat_lossless', 7);
UI.forceSetting('video_time', 5);
UI.forceSetting('video_area', 65);
UI.forceSetting('video_scaling', 0);
UI.forceSetting('video_out_time', 3);
break;
}
if (UI.rfb) {
UI.rfb.qualityLevel = parseInt(UI.getSetting('quality'));
UI.rfb.antiAliasing = parseInt(UI.getSetting('anti_aliasing'));
UI.rfb.dynamicQualityMin = parseInt(UI.getSetting('dynamic_quality_min'));
UI.rfb.dynamicQualityMax = parseInt(UI.getSetting('dynamic_quality_max'));
UI.rfb.jpegVideoQuality = parseInt(UI.getSetting('jpeg_video_quality'));
UI.rfb.webpVideoQuality = parseInt(UI.getSetting('webp_video_quality'));
UI.rfb.videoArea = parseInt(UI.getSetting('video_area'));
UI.rfb.videoTime = parseInt(UI.getSetting('video_time'));
UI.rfb.videoOutTime = parseInt(UI.getSetting('video_out_time'));
UI.rfb.videoScaling = parseInt(UI.getSetting('video_scaling'));
UI.rfb.treatLossless = parseInt(UI.getSetting('treat_lossless'));
UI.rfb.maxVideoResolutionX = parseInt(UI.getSetting('max_video_resolution_x'));
UI.rfb.maxVideoResolutionY = parseInt(UI.getSetting('max_video_resolution_y'));
UI.rfb.frameRate = parseInt(UI.getSetting('framerate'));
UI.rfb.enableWebP = UI.getSetting('enable_webp');
UI.rfb.videoQuality = parseInt(UI.getSetting('video_quality'));
// Gracefully update settings server side
UI.rfb.updateConnectionSettings();
}
},

View File

@ -343,10 +343,21 @@ export default class RFB extends EventTargetMixin {
set clipboardBinary(val) { this._clipboardMode = val; }
get videoQuality() { return this._videoQuality; }
set videoQuality(quality) { this._videoQuality = quality; }
set videoQuality(quality)
{
//if changing to or from a video quality mode that uses a fixed resolution server side
if (this._videoQuality <= 1 || quality <= 1) {
this._pendingApplyResolutionChange = true;
}
this._videoQuality = quality;
this._pendingApplyEncodingChanges = true;
}
get preferBandwidth() { return this._preferBandwidth; }
set preferBandwidth(val) { this._preferBandwidth = val; }
set preferBandwidth(val) {
this._preferBandwidth = val;
this._pendingApplyEncodingChanges = true;
}
get viewOnly() { return this._viewOnly; }
set viewOnly(viewOnly) {
@ -411,9 +422,7 @@ export default class RFB extends EventTargetMixin {
return;
}
this._enableWebP = enabled;
if (this._rfbConnectionState === 'connected') {
this._sendEncodings();
}
this._pendingApplyEncodingChanges = true;
}
get antiAliasing() { return this._display.antiAliasing; }
@ -433,10 +442,7 @@ export default class RFB extends EventTargetMixin {
}
this._jpegVideoQuality = qualityLevel;
if (this._rfbConnectionState === 'connected') {
this._sendEncodings();
}
this._pendingApplyEncodingChanges = true;
}
get webpVideoQuality() { return this._webpVideoQuality; }
@ -451,10 +457,7 @@ export default class RFB extends EventTargetMixin {
}
this._webpVideoQuality = qualityLevel;
if (this._rfbConnectionState === 'connected') {
this._sendEncodings();
}
this._pendingApplyEncodingChanges = true;
}
get treatLossless() { return this._treatLossless; }
@ -469,10 +472,6 @@ export default class RFB extends EventTargetMixin {
}
this._treatLossless = qualityLevel;
if (this._rfbConnectionState === 'connected') {
this._sendEncodings();
}
}
get dynamicQualityMin() { return this._dynamicQualityMin; }
@ -487,10 +486,7 @@ export default class RFB extends EventTargetMixin {
}
this._dynamicQualityMin = qualityLevel;
if (this._rfbConnectionState === 'connected') {
this._sendEncodings();
}
this._pendingApplyEncodingChanges = true;
}
get dynamicQualityMax() { return this._dynamicQualityMax; }
@ -505,10 +501,7 @@ export default class RFB extends EventTargetMixin {
}
this._dynamicQualityMax = qualityLevel;
if (this._rfbConnectionState === 'connected') {
this._sendEncodings();
}
this._pendingApplyEncodingChanges = true;
}
get videoArea() {
@ -525,10 +518,7 @@ export default class RFB extends EventTargetMixin {
}
this._videoArea = area;
if (this._rfbConnectionState === 'connected') {
this._sendEncodings();
}
this._pendingApplyEncodingChanges = true;
}
get videoTime() {
@ -545,10 +535,7 @@ export default class RFB extends EventTargetMixin {
}
this._videoTime = value;
if (this._rfbConnectionState === 'connected') {
this._sendEncodings();
}
this._pendingApplyEncodingChanges = true;
}
get videoOutTime() {
@ -565,10 +552,7 @@ export default class RFB extends EventTargetMixin {
}
this._videoOutTime = value;
if (this._rfbConnectionState === 'connected') {
this._sendEncodings();
}
this._pendingApplyEncodingChanges = true;
}
get videoScaling() {
@ -585,10 +569,7 @@ export default class RFB extends EventTargetMixin {
}
this._videoScaling = value;
if (this._rfbConnectionState === 'connected') {
this._sendEncodings();
}
this._pendingApplyEncodingChanges = true;
}
get frameRate() { return this._frameRate; }
@ -603,10 +584,7 @@ export default class RFB extends EventTargetMixin {
}
this._frameRate = value;
if (this._rfbConnectionState === 'connected') {
this._sendEncodings();
}
this._pendingApplyEncodingChanges = true;
}
get maxVideoResolutionX() { return this._maxVideoResolutionX; }
@ -621,13 +599,7 @@ export default class RFB extends EventTargetMixin {
}
this._maxVideoResolutionX = value;
if (this._rfbConnectionState === 'connected') {
RFB.messages.setMaxVideoResolution(this._sock,
this._maxVideoResolutionX,
this._maxVideoResolutionY);
}
this._pendingApplyVideoRes = true;
}
get maxVideoResolutionY() { return this._maxVideoResolutionY; }
@ -642,12 +614,7 @@ export default class RFB extends EventTargetMixin {
}
this._maxVideoResolutionY = value;
if (this._rfbConnectionState === 'connected') {
RFB.messages.setMaxVideoResolution(this._sock,
this._maxVideoResolutionX,
this._maxVideoResolutionY);
}
this._pendingApplyVideoRes = true;
}
get qualityLevel() {
@ -664,10 +631,7 @@ export default class RFB extends EventTargetMixin {
}
this._qualityLevel = qualityLevel;
if (this._rfbConnectionState === 'connected') {
this._sendEncodings();
}
this._pendingApplyEncodingChanges = true;
}
get compressionLevel() {
@ -692,6 +656,31 @@ export default class RFB extends EventTargetMixin {
// ===== PUBLIC METHODS =====
/*
This function must be called after changing any properties that effect rendering quality
*/
updateConnectionSettings() {
if (this._rfbConnectionState === 'connected') {
if (this._pendingApplyVideoRes) {
RFB.messages.setMaxVideoResolution(this._sock, this._maxVideoResolutionX, this._maxVideoResolutionY);
}
if (this._pendingApplyResolutionChange) {
this._requestRemoteResize();
}
if (this._pendingApplyEncodingChanges) {
this._sendEncodings();
}
this._pendingApplyVideoRes = false;
this._pendingApplyEncodingChanges = false;
this._pendingApplyResolutionChange = false;
}
}
disconnect() {
this._updateConnectionState('disconnecting');
this._sock.off('error');
@ -2220,16 +2209,6 @@ export default class RFB extends EventTargetMixin {
var quality = 6;
var compression = 2;
var screensize = this._screenSize(false);
if (this.videoQuality == 1) {
if (screensize.w > 1280) {
quality = 8; //higher quality needed because scaling enlarges artifacts
} else {
quality = 3; //twice the compression ratio as default, but not horrible quality
}
compression = 6;
} else if (this.videoQuality == 3) {
quality = 8
}
encs.push(encodings.pseudoEncodingQualityLevel0 + this._qualityLevel);
encs.push(encodings.pseudoEncodingCompressLevel0 + this._compressionLevel);

View File

@ -224,14 +224,25 @@
</li>
<li><hr></li>
<li>
<div class="noVNC_expander">Video Mode Settings</div>
<div class="noVNC_expander">Stream Quality</div>
<div><ul>
<li>
<label for="noVNC_setting_video_quality">Video Quality:</label>
<label for="noVNC_setting_video_quality">Preset Modes:</label>
<select id="noVNC_setting_video_quality" name="vncVideoQuality">
<option value=0>Low</option>
<option value=1>Medium</option>
<option value=2>High</option>
<option value=0>Static</option>
<option value=1>Low</option>
<option value=2>Medium</option>
<option value=3>High</option>
<option value=4>Extreme</option>
<option value=10>Custom</option>
</select>
</li>
<li>
<label for="noVNC_setting_anti_aliasing">Anti-Aliasing:</label>
<select id="noVNC_setting_anti_aliasing" name="vncAntiAliasing">
<option value=0>Auto Dynamic</option>
<option value=1>On</option>
<option value=2>Off</option>
</select>
</li>
<li>
@ -244,6 +255,30 @@
<input id="noVNC_setting_webp_video_quality" type="range" min="0" max="9" value="5" onchange="noVNC_setting_webp_video_quality_output.value=value">
<output id="noVNC_setting_webp_video_quality_output">5</output>
</li>
<li style="display: none;">
<label for="noVNC_setting_quality">Quality:</label>
<input id="noVNC_setting_quality" type="range" min="0" max="9" value="6">
</li>
<li>
<label for="noVNC_setting_dynamic_quality_min">Dynamic Quality Min:</label>
<input id="noVNC_setting_dynamic_quality_min" type="range" min="0" max="9" value="3" onchange="noVNC_setting_dynamic_quality_min_output.value=value">
<output id="noVNC_setting_dynamic_quality_min_output">3</output>
</li>
<li>
<label for="noVNC_setting_dynamic_quality_max">Dynamic Quality Max:</label>
<input id="noVNC_setting_dynamic_quality_max" type="range" min="0" max="9" value="9" onchange="noVNC_setting_dynamic_quality_max_output.value=value">
<output id="noVNC_setting_dynamic_quality_max_output">9</output>
</li>
<li>
<label for="noVNC_setting_treat_lossless">Treat Lossless:</label>
<input id="noVNC_setting_treat_lossless" type="range" min="0" max="9" value="7" onchange="noVNC_setting_treat_lossless_output.value=value">
<output id="noVNC_setting_treat_lossless_output">7</output>
</li>
<li>
<label for="noVNC_setting_framerate">Frame Rate:</label>
<input id="noVNC_setting_framerate" type="number" min="1" max="120" value="30">
</li>
<li>
<label for="noVNC_setting_video_area">Video Area:</label>
<input id="noVNC_setting_video_area" type="range" min="0" max="100" value="65" onchange="noVNC_setting_video_area_output.value=value">
@ -268,11 +303,11 @@
</select>
</li>
<li>
<label for="noVNC_setting_max_video_resolution_x">Max Width:</label>
<label for="noVNC_setting_max_video_resolution_x">Video Mode Width:</label>
<input id="noVNC_setting_max_video_resolution_x" type="number" min="100" max="3840" value="960">
</li>
<li>
<label for="noVNC_setting_max_video_resolution_y">Max Height:</label>
<label for="noVNC_setting_max_video_resolution_y">Video Mode Height:</label>
<input id="noVNC_setting_max_video_resolution_y" type="number" min="100" max="2160" value="540">
</li>
</ul></div>
@ -281,37 +316,6 @@
<li>
<div class="noVNC_expander">Advanced</div>
<div><ul>
<li style="display: none;">
<label for="noVNC_setting_quality">Quality:</label>
<input id="noVNC_setting_quality" type="range" min="0" max="9" value="6">
</li>
<li>
<label for="noVNC_setting_anti_aliasing">Smoothing:</label>
<select id="noVNC_setting_anti_aliasing" name="vncAntiAliasing">
<option value=0>Auto Dynamic</option>
<option value=1>On</option>
<option value=2>Off</option>
</select>
</li>
<li>
<label for="noVNC_setting_dynamic_quality_min">Dynamic Quality Min:</label>
<input id="noVNC_setting_dynamic_quality_min" type="range" min="0" max="9" value="3" onchange="noVNC_setting_dynamic_quality_min_output.value=value">
<output id="noVNC_setting_dynamic_quality_min_output">3</output>
</li>
<li>
<label for="noVNC_setting_dynamic_quality_max">Dynamic Quality Max:</label>
<input id="noVNC_setting_dynamic_quality_max" type="range" min="0" max="9" value="9" onchange="noVNC_setting_dynamic_quality_max_output.value=value">
<output id="noVNC_setting_dynamic_quality_max_output">9</output>
</li>
<li>
<label for="noVNC_setting_treat_lossless">Treat Lossless:</label>
<input id="noVNC_setting_treat_lossless" type="range" min="0" max="9" value="7" onchange="noVNC_setting_treat_lossless_output.value=value">
<output id="noVNC_setting_treat_lossless_output">7</output>
</li>
<li>
<label for="noVNC_setting_framerate">Frame Rate:</label>
<input id="noVNC_setting_framerate" type="number" min="1" max="120" value="30">
</li>
<li>
<label for="noVNC_setting_compression">Compression level:</label>
<input id="noVNC_setting_compression" type="range" min="0" max="9" value="2">