/** 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() ?? '')); } }); }