feat: read serial port and play video
This commit is contained in:
commit
ad839bbdd4
9 changed files with 243 additions and 0 deletions
155
index.js
Normal file
155
index.js
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
/** Video container element */
|
||||
const videoRoot = /** @type {HTMLElement} */
|
||||
(document.getElementById('video-root'));
|
||||
|
||||
/**
|
||||
* A map from tag id to video element
|
||||
* {[$tagId]: <video><source src="$src"/></video>}
|
||||
*/
|
||||
const videos = Object.fromEntries(Object.entries(tags).map(([tagId, src]) => {
|
||||
const video = document.createElement('video');
|
||||
const source = document.createElement('source');
|
||||
source.setAttribute('src', src);
|
||||
video.appendChild(source);
|
||||
videoRoot.appendChild(video);
|
||||
return [tagId, video];
|
||||
}));
|
||||
|
||||
/**
|
||||
* Current playing video element
|
||||
* @type {HTMLVideoElement|null}
|
||||
*/
|
||||
let currentVideo = null;
|
||||
|
||||
/**
|
||||
* Callback function when a tag is read
|
||||
* @param {string} tagId
|
||||
*/
|
||||
function onTag(tagId) {
|
||||
const nextVideo = videos[tagId];
|
||||
// ignore unknown tag
|
||||
if (nextVideo == null) {
|
||||
console.log('unknown tag', tagId);
|
||||
return;
|
||||
}
|
||||
// ignore if the same tag is used
|
||||
if (nextVideo === currentVideo) return;
|
||||
// play the next video from the beginning
|
||||
nextVideo.currentTime = 0;
|
||||
nextVideo.play();
|
||||
nextVideo.classList.add('playing');
|
||||
// stop the current video
|
||||
currentVideo?.pause();
|
||||
currentVideo?.classList.remove('playing');
|
||||
// update video reference
|
||||
currentVideo = nextVideo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open and read the serial port
|
||||
* @param {SerialPort} port
|
||||
*/
|
||||
async function openPort(port, baudRate=9600) {
|
||||
// open the serial port
|
||||
await port.open({baudRate});
|
||||
// reader of byte stream from the serial port
|
||||
const reader = port.readable.getReader({mode: 'byob'});
|
||||
|
||||
// buffer for reading the serial port
|
||||
const bufferSize = 16;
|
||||
let buffer = new ArrayBuffer(bufferSize);
|
||||
let offset = 0;
|
||||
try {
|
||||
while (true) {
|
||||
// read bytes from the serial port to buffer
|
||||
const {value, done} = await reader.read(
|
||||
new Uint8Array(buffer, offset, bufferSize - offset),
|
||||
);
|
||||
|
||||
// no more message
|
||||
if (done) {
|
||||
showMessage('The serial port is closed');
|
||||
break;
|
||||
}
|
||||
|
||||
// update buffer and offset
|
||||
buffer = value.buffer;
|
||||
offset += value.byteLength;
|
||||
|
||||
// message format:
|
||||
// - (u8) N = tag id length
|
||||
// - (u8[N]) tag id
|
||||
/** pointer (array index) to the message in buffer */
|
||||
let ptr = 0;
|
||||
// if tag id length of the next message presents
|
||||
while (offset > ptr) {
|
||||
const tagIdLength = new DataView(buffer).getUint8(0);
|
||||
// break if the message is not completely read
|
||||
const ptrNext = ptr + 1 + tagIdLength;
|
||||
if (offset < ptrNext) break;
|
||||
// convert tag id bytes to HEX string
|
||||
const tagId = Array.from(
|
||||
new Uint8Array(buffer, ptr + 1, tagIdLength),
|
||||
// %02X
|
||||
x => x.toString(16).toUpperCase().padStart(2, '0'),
|
||||
).join('');
|
||||
// handle the tag
|
||||
onTag(tagId);
|
||||
// try to read the next message
|
||||
ptr = ptrNext;
|
||||
}
|
||||
// move the remaining bytes in the buffer to the beginning
|
||||
// if any message is read
|
||||
if (ptr > 0) {
|
||||
// remaining byte count
|
||||
const remainingLength = offset - ptr;
|
||||
// copy bytes
|
||||
if (remainingLength > 0) {
|
||||
new Uint8Array(buffer)
|
||||
.set(new Uint8Array(buffer, ptr, remainingLength));
|
||||
}
|
||||
// update offset
|
||||
offset = remainingLength;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
showMessage(
|
||||
'Error occurred while reading serial port:\n'
|
||||
+ (err?.toString() ?? ''),
|
||||
);
|
||||
console.error(err);
|
||||
} finally {
|
||||
reader.releaseLock();
|
||||
}
|
||||
}
|
||||
|
||||
/** HTML element for showing message */
|
||||
const msgElm = /** @type {HTMLElement} */ (document.getElementById('message'));
|
||||
/** Show message on page */
|
||||
const showMessage = (/** @type {string} */ msg) => msgElm.textContent = msg;
|
||||
|
||||
// check if this browser supports Web Serial API
|
||||
if (navigator.serial == null) {
|
||||
showMessage('Your browser does not support Web Serial API!');
|
||||
} else {
|
||||
// click body to select serial port to listen
|
||||
// this is required since videos cannot be played without user's interaction
|
||||
document.body.addEventListener('click', async function onBodyClicked() {
|
||||
// get serial port
|
||||
try {
|
||||
// try to get serial ports
|
||||
const ports = await navigator.serial.getPorts();
|
||||
// choose the first port (FIXME) if availble port presents
|
||||
// or request user to select a port
|
||||
const port = ports[0] ?? await navigator.serial.requestPort();
|
||||
// unregister body onClick listener
|
||||
document.body.removeEventListener('click', onBodyClicked);
|
||||
// remove message from page
|
||||
showMessage('');
|
||||
// start handling the messages from the serial port
|
||||
openPort(port);
|
||||
} catch(err) {
|
||||
showMessage('Failed to access serial port:\n' + (err?.toString() ?? ''));
|
||||
}
|
||||
});
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue