diff --git a/webapp/player/index.html b/webapp/player/index.html index 9cb876284..adf360dd2 100644 --- a/webapp/player/index.html +++ b/webapp/player/index.html @@ -1,215 +1,38 @@ - - - - - - - + + + + - html, body { - height: 100%; - margin: 0; - background-color: black; - display: flex; - justify-content: center; - align-items: center; - } - - - - - \ No newline at end of file diff --git a/webapp/player/js/index.js b/webapp/player/js/index.js new file mode 100644 index 000000000..55796f0c9 --- /dev/null +++ b/webapp/player/js/index.js @@ -0,0 +1,109 @@ +import { convertTRPtoCast } from "./trp-decoder.js"; + +const windowURL = new URL(window.location.href); +var sessionId = windowURL.searchParams.get("sessionId"); +var token = windowURL.searchParams.get("token"); +const gatewayAccessUrl = windowURL.toString().split("/jet/jrec")[0]; +var videoSrcInfo = `${gatewayAccessUrl}/jet/jrec/pull/${sessionId}/recording.json?token=${token}`; +var request = new XMLHttpRequest(); + +request.onreadystatechange = function () { + if (request.readyState !== XMLHttpRequest.DONE) { + return false; + } + + if (request.status !== 200) { + console.error("Request failed. Returned status of " + request.status); + return false; + } + + var recordingInfo = JSON.parse(request.responseText); + var fileType = recordingInfo.files[0].fileName.split(".")[1]; + + switch (fileType) { + case "webm": + // create the video object + var videoPlayer = document.createElement("video"); + videoPlayer.id = "videoPlayer"; + videoPlayer.controls = true; + videoPlayer.autoplay = true; + videoPlayer.name = "media"; + + var videoSrcElement = document.createElement("source"); + videoSrcElement.id = "videoSrcElement"; + + videoPlayer.appendChild(videoSrcElement); + document.body.appendChild(videoPlayer); + + // initialize the video player + let videoSrc = `${gatewayAccessUrl}/jet/jrec/pull/${sessionId}/${recordingInfo.files[0].fileName}?token=${token}`; + videoSrcElement.setAttribute("src", videoSrc); + + // set up video cycling + var currentIndex = 0; + var maxIndex = recordingInfo.files.length - 1; + + videoPlayer.onended = function () { + currentIndex++; + if (currentIndex > maxIndex) { + currentIndex = 0; + } + videoSrc = `${gatewayAccessUrl}/jet/jrec/pull/${sessionId}/${recordingInfo.files[currentIndex].fileName}?token=${token}`; + videoSrcElement.setAttribute("src", videoSrc); + videoPlayer.load(); + videoPlayer.play(); + }; + + break; + + case "trp": + // create the Div + var terminalDiv = document.createElement("div"); + document.body.appendChild(terminalDiv); + + let trpSrc = `${gatewayAccessUrl}/jet/jrec/pull/${sessionId}/${recordingInfo.files[0].fileName}?token=${token}`; + + loadFile(trpSrc, function (trpFileContent) { + var castFileContent = convertTRPtoCast(trpFileContent); + + // make the file a base64 embedded src url + var url = "data:text/plain;base64," + btoa(castFileContent); + var player = new XtermPlayer.XtermPlayer(url, terminalDiv); + + // need a slight delay to play waiting for it to load + setTimeout(function () { + player.play(); + }, 500); + }); + + break; + case "cast": + // create the Div + var terminalDiv = document.createElement("div"); + document.body.appendChild(terminalDiv); + let castSrc = `${gatewayAccessUrl}/jet/jrec/pull/${sessionId}/${recordingInfo.files[0].fileName}?token=${token}`; + const player = new XtermPlayer.XtermPlayer(castSrc, terminalDiv , { + fontSize: 12 + }); + player.play(); + + break; + } +}; + +request.open("GET", videoSrcInfo, true); +request.send(); + +function loadFile(fileName, onLoad) { + const req = new XMLHttpRequest(); + req.open("GET", fileName, true); + req.responseType = "arraybuffer"; + req.onload = (event) => { + const arrayBuffer = req.response; + if (arrayBuffer) { + const byteArray = new Uint8Array(arrayBuffer); + onLoad(byteArray); + } + }; + req.send(null); +} diff --git a/webapp/player/js/trp-decoder.js b/webapp/player/js/trp-decoder.js new file mode 100644 index 000000000..232b3ddf0 --- /dev/null +++ b/webapp/player/js/trp-decoder.js @@ -0,0 +1,65 @@ + +export function convertTRPtoCast(fileArray) { + var castHeader = { + version: 2, + width: 0, + height: 0, + }; + var castEvents = []; + + var time = 0.0; + var position = 0; + while (position < fileArray.length) { + var timer = readUInt32(fileArray, position); + var type = readUInt16(fileArray, position + 4); + var size = readUInt16(fileArray, position + 6); + var chunk = fileArray.subarray(position + 8, position + 8 + size); + position += 8 + size; + time += timer / 1000; + if (type == 0) { + // RECORD_CHUNK_TERMINAL_OUTPUT + var data = new TextDecoder().decode(chunk); + castEvents.push([time, "o", data]); + } else if (type == 1) { + // RECORD_CHUNK_USER_INPUT + var data = new TextDecoder().decode(chunk); + castEvents.push([time, "i", data]); + } else if (type == 2) { + // RECORD_CHUNK_SIZE_CHANGE + var width = readUInt16(chunk, 0); + var height = readUInt16(chunk, 2); + if (castHeader.width == 0) { + castHeader.width = width; + castHeader.height = height; + } else { + castEvents.push([time, "r", width + "x" + height]); + } + } else if (type == 4) { + // RECORD_CHUNK_TERMINAL_SETUP + var tagCount = size / 6; + for (var i = 0; i < tagCount; i++) { + var tag = readUInt16(chunk, i * 6); + var tagValue = readUInt32(chunk, i * 6 + 2); + } + } + } + castHeader.duration = time; + var castFile = JSON.stringify(castHeader) + "\n"; + castEvents.forEach((event) => { + castFile += JSON.stringify(event) + "\n"; + }); + return castFile; +} + +function readUInt32(array, position) { + return ( + ((array[position + 3] << 24) & 0xff000000) | + ((array[position + 2] << 16) & 0xff0000) | + ((array[position + 1] << 8) & 0xff00) | + array[position + 0] + ); +} + +function readUInt16(array, position) { + return ((array[position + 1] << 8) & 0xff00) | array[position]; +} \ No newline at end of file