-
Notifications
You must be signed in to change notification settings - Fork 60
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
459 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
/* | ||
* This file is part of Xpra. | ||
* Copyright (C) 2021 Tijs van der Zwaan <tijzwa@vpo.nl> | ||
* Licensed under MPL 2.0, see: | ||
* http://www.mozilla.org/MPL/2.0/ | ||
* | ||
*/ | ||
|
||
/* | ||
* Receives native image packages and decode them via ImageDecoder. | ||
* https://developer.mozilla.org/en-US/docs/Web/API/ImageDecoder | ||
* ImageDecoder is only working in Chrome 94+ and Android | ||
* | ||
*/ | ||
|
||
const XpraImageDecoderLoader = { | ||
hasNativeDecoder: function () { | ||
return typeof ImageDecoder !== "undefined"; | ||
} | ||
} | ||
|
||
function XpraImageDecoder() { | ||
this.on_frame_decoded = null; | ||
} | ||
|
||
XpraImageDecoder.prototype.queue_frame = function (packet, start) { | ||
const width = packet[4]; | ||
const height = packet[5]; | ||
const coding = packet[6]; | ||
if (coding.startsWith("rgb")) { | ||
// TODO: Figure out how to decode rgb with ImageDecoder API; | ||
const data = decode_rgb(packet); | ||
createImageBitmap(new ImageData(new Uint8ClampedArray(data.buffer), width, height), 0, 0, width, height).then((bitmap) => { | ||
packet[6] = "bitmap"; | ||
packet[7] = bitmap; | ||
this.on_frame_decoded(packet, start); | ||
}); | ||
} else { | ||
const decoder = new ImageDecoder({ | ||
type: "image/" + coding, | ||
data: packet[7] | ||
}); | ||
decoder.decode({ frameIndex: 0 }).then((result) => { | ||
packet[6] = "frame"; | ||
packet[7] = result; | ||
decoder.close(); | ||
this.on_frame_decoded(packet, start); | ||
}); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
/* | ||
* This file is part of Xpra. | ||
* Copyright (C) 2021 Tijs van der Zwaan <tijzwa@vpo.nl> | ||
* Licensed under MPL 2.0, see: | ||
* http://www.mozilla.org/MPL/2.0/ | ||
* | ||
*/ | ||
|
||
/* | ||
* Worker for offscreen decoding and painting. | ||
* Requires Chrome 94+ or Android and a secure (SSL or localhost) context. | ||
*/ | ||
|
||
importScripts("./lib/zlib.js"); | ||
importScripts("./lib/lz4.js"); | ||
importScripts("./lib/broadway/Decoder.js"); | ||
importScripts("./VideoDecoder.js"); | ||
importScripts("./ImageDecoder.js"); | ||
importScripts("./RgbHelpers.js"); | ||
|
||
// Array of offscreen canvases and video decoders we have control over | ||
const offscreen_canvas = []; | ||
const image_decoders = []; | ||
const video_decoders = []; | ||
|
||
function add_decoder_for_window(wid, canvas) { | ||
// Canvas | ||
offscreen_canvas[wid] = []; | ||
offscreen_canvas[wid]["c"] = canvas; | ||
offscreen_canvas[wid]["ctx"] = canvas.getContext("2d"); | ||
offscreen_canvas[wid]["ctx"].imageSmoothingEnabled = false; | ||
|
||
// Decoders | ||
image_decoders[wid] = new XpraImageDecoder(); | ||
image_decoders[wid].on_frame_decoded = ((packet, start) => { | ||
const wid = packet[1], | ||
x = packet[2], | ||
y = packet[3], | ||
width = packet[4], | ||
height = packet[5], | ||
coding = packet[6], | ||
data = packet[7]; | ||
|
||
let ctx = offscreen_canvas[wid]["ctx"]; | ||
if (coding == "bitmap") { | ||
// RGB is transformed to bitmap | ||
ctx.clearRect(x, y, width, height); | ||
ctx.drawImage(data, x, y, width, height); | ||
} else { | ||
// All others are transformed to VideoFrame | ||
ctx.clearRect(x, y, width, height); | ||
ctx.drawImage(data.image, x, y, width, height); | ||
data.image.close(); | ||
} | ||
// Replace the coding & drop data | ||
packet[6] = "offscreen-painted"; | ||
packet[7] = null; | ||
self.postMessage({ 'draw': packet, 'start': start }, []); | ||
}); | ||
|
||
video_decoders[wid] = new XpraVideoDecoder(); | ||
video_decoders[wid].on_frame_decoded = ((packet, start) => { | ||
const wid = packet[1], | ||
x = packet[2], | ||
y = packet[3], | ||
w = packet[4], | ||
h = packet[5], | ||
coding = packet[6], | ||
data = packet[7]; | ||
let options = packet[10].length > 10 ? packet[10] : {}; | ||
|
||
let enc_width = w; | ||
let enc_height = h; | ||
const scaled_size = options["scaled_size"]; | ||
if (scaled_size) { | ||
enc_width = scaled_size[0]; | ||
enc_height = scaled_size[1]; | ||
} | ||
|
||
let ctx = offscreen_canvas[wid]["ctx"]; | ||
if (coding == "frame") { | ||
ctx.drawImage(data, x, y, enc_width, enc_height); | ||
data.close(); | ||
packet[6] = "offscreen-painted"; | ||
packet[7] = null; | ||
self.postMessage({ 'draw': packet, 'start': start }, []); | ||
} else { | ||
// Encoding throttle is used to slow down frame input | ||
// TODO: Relal error handling | ||
const timeout = coding == "throttle" ? 500 : 0; | ||
setTimeout(() => { | ||
packet[6] = "offscreen-painted"; | ||
packet[7] = null; | ||
self.postMessage({ 'draw': packet, 'start': start }, []); | ||
}, timeout); | ||
} | ||
}); | ||
|
||
} | ||
|
||
function decode_draw_packet(packet, start) { | ||
const image_coding = ["rgb", "rgb32", "rgb24", "jpeg", "png", "webp"]; | ||
const video_coding = ["h264"]; | ||
const wid = packet[1]; | ||
const coding = packet[6]; | ||
|
||
if (image_coding.includes(coding)) { | ||
// Add to image queue | ||
let decoder = image_decoders[wid]; | ||
decoder.queue_frame(packet, start); | ||
|
||
} else if (video_coding.includes(coding)) { | ||
// Add to video queue | ||
let decoder = video_decoders[wid]; | ||
if (!decoder.initialized) { | ||
// Init with width and heigth of this packet. | ||
// TODO: Use video max-size? It does not seem to matter. | ||
decoder.init(packet[4], packet[5]); | ||
} | ||
decoder.queue_frame(packet, start); | ||
} | ||
else if (coding == "scroll") { | ||
const data = packet[7]; | ||
const canvas = offscreen_canvas[wid]["c"];; | ||
const ctx = offscreen_canvas[wid]["ctx"];; | ||
for (let i = 0, j = data.length; i < j; ++i) { | ||
const scroll_data = data[i]; | ||
const sx = scroll_data[0], | ||
sy = scroll_data[1], | ||
sw = scroll_data[2], | ||
sh = scroll_data[3], | ||
xdelta = scroll_data[4], | ||
ydelta = scroll_data[5]; | ||
|
||
ctx.drawImage(canvas, sx, sy, sw, sh, sx + xdelta, sy + ydelta, sw, sh); | ||
} | ||
packet[6] = "offscreen-painted"; | ||
packet[7] = null; | ||
self.postMessage({ 'draw': packet, 'start': start }, []); | ||
} | ||
else { | ||
// We dont know, pass trough | ||
self.postMessage({ 'draw': packet, 'start': start }, []); | ||
} | ||
} | ||
|
||
function close_video(wid) { | ||
try { | ||
video_decoders[wid]._close(); | ||
} catch { | ||
// TODO: Handle error. | ||
} | ||
} | ||
|
||
onmessage = function (e) { | ||
const data = e.data; | ||
switch (data.cmd) { | ||
case 'check': | ||
// We do not check. We are here because we support native decoding. | ||
// TODO: Reconsider this. It might be a good thing to do some testing, just for sanity?? | ||
const encodings = data.encodings; | ||
self.postMessage({ 'result': true, 'formats': encodings }); | ||
break; | ||
case 'eos': | ||
close_video(data.wid); | ||
break; | ||
case 'decode': | ||
decode_draw_packet(data.packet, data.start); | ||
break | ||
case 'canvas': | ||
add_decoder_for_window(data.wid, data.canvas) | ||
break; | ||
case 'canvas-geo': | ||
if (offscreen_canvas[data.wid]) { | ||
const canvas = offscreen_canvas[data.wid]["c"]; | ||
if (canvas.width != data.w || canvas.height != data.h) { | ||
canvas.width = data.w; | ||
canvas.height = data.h; | ||
} | ||
} | ||
break; | ||
default: | ||
console.error("Offscreen decode worker got unknown message: " + data.cmd); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
/* | ||
* This file is part of Xpra. | ||
* Copyright (C) 2021 Tijs van der Zwaan <tijzwa@vpo.nl> | ||
* Licensed under MPL 2.0, see: | ||
* http://www.mozilla.org/MPL/2.0/ | ||
* | ||
*/ | ||
|
||
/* | ||
* Helper for offscreen decoding and painting. | ||
* Requires Chrome 94+ or Android and a secure (SSL or localhost) context. | ||
*/ | ||
|
||
const XpraOffscreenWorker = { | ||
isAvailable: function () { | ||
if (XpraImageDecoderLoader.hasNativeDecoder() && XpraVideoDecoderLoader.hasNativeDecoder && typeof OffscreenCanvas !== "undefined") { | ||
return true; | ||
} else { | ||
console.warn("Offscreen decoding is not available. Please consider using Google Chrome 94+ in a secure (SSL or localhost) context for better performance."); | ||
return false; | ||
} | ||
} | ||
} |
Oops, something went wrong.