Skip to content

Commit

Permalink
feat: initial implementation for WebM player for recordings (#832)
Browse files Browse the repository at this point in the history
Adds a video and xterm player at the `GET /jet/jrec/play` endpoint which
supports multiple videos and builds the page dynamically based on the
type of recording.

Issue: DGW-110
  • Loading branch information
nicolesabourin1 authored May 6, 2024
1 parent 1c8a7d2 commit 58362b9
Show file tree
Hide file tree
Showing 7 changed files with 224 additions and 0 deletions.
2 changes: 2 additions & 0 deletions webapp/player/css/xterm-player.min.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions webapp/player/css/xterm-player.min.css.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

215 changes: 215 additions & 0 deletions webapp/player/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
<html>
<head>
<meta name="viewport" content="width=device-width">
<link rel="stylesheet" href="css/xterm-player.min.css" />
<script src="js/xterm.min.js"></script>
<script src="js/xterm-player.min.js"></script>
</head>
<style>

video {
position: absolute;
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
max-height: 100%;
max-width: 100%;
margin: auto;
}

html, body {
height: 100%;
margin: 0;
background-color: black;
display: flex;
justify-content: center;
align-items: center;
}

</style>
<body>
<script>
const windowURL = new URL(window.location.href);
var sessionId = windowURL.searchParams.get('sessionId');
var token = windowURL.searchParams.get('token');

var videoSrcInfo = `${windowURL.protocol}//${windowURL.host}/jet/jrec/pull/${sessionId}/recording.json?token=${token}`;
var request = new XMLHttpRequest();

request.onreadystatechange = function () {
if (request.readyState === XMLHttpRequest.DONE) {
if (request.status === 200) {

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 = `${windowURL.protocol}//${windowURL.host}/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 = `${windowURL.protocol}//${windowURL.host}/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 = `${windowURL.protocol}//${windowURL.host}/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 = `${windowURL.protocol}//${windowURL.host}/jet/jrec/pull/${sessionId}/${recordingInfo.files[currentIndex].fileName}?token=${token}`

loadFile(castSrc, function(castFileContent) {

// 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;
}

} else {
console.error('Request failed. Returned status of ' + request.status);
}
}
}

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);
}

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];
}

</script>
</body>
</html>
2 changes: 2 additions & 0 deletions webapp/player/js/xterm-player.min.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions webapp/player/js/xterm-player.min.js.map

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions webapp/player/js/xterm.js.map

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions webapp/player/js/xterm.min.js

Large diffs are not rendered by default.

0 comments on commit 58362b9

Please sign in to comment.