Online Interactive Paint Board
This guide demonstrates how to implement an interactive paint board (canvas) feature in an Arctos meeting room. The paint board allows moderators to draw, annotate, and share visual content in real-time during meetings.
Prerequisites
Before implementing the paint board, ensure you have:
- Arctos SDK properly loaded
- Meeting room initialized with moderator permissions
- HTML5 Canvas support
Step 1: Initialize Arctos Instance
First initialize an Arctos Instance and set up the meeting room.
/**
* Initialize Arctos SDK instance and connect to meeting room
* This should be called when the Arctos widget has fully loaded
*/
// wait for arctos-widget.js to load
window.addEventListener('ArctosLoad', async function (e) {
// create an instance of Arctos
arctosSdkInstance = new Arctos({
endpointUrl: ENDPOINTURL,
endpointKey: ENDPOINTKEY,
});
// Be careful !!!, the following code is for testing only. You should store the license key in your server side.
const authCode = await getAuthCode();
// Retrieve the user token.
const accessToken = await getAuthToken(authCode);
// create a session object
await arctosSdkInstance.initSession({
accessToken: accessToken,
sessionId: taskNum,
});
// Initialize meeting room
await arctosSdkInstance.initMeetingRoom({
isDebug: false,
recordingAutoStart: false,
showPrejoin: false,
showToolbar: true,
});
utilsCanvasInit();
});
Step 2: Canvas Initialization
Initialize the canvas with proper sizing, event handlers, and drawing tools.
/**
* Initialize the paint board canvas with drawing capabilities
* Sets up canvas dimensions, event listeners, and drawing tools
*/
utilsCanvasInit = async function() {
/**
* Create color palette buttons for brush color selection
* Dynamically generates buttons for each color in the colorAry array
*/
let cGroup = document.querySelector('.colorGroup');
for (let i = 0; i < colorAry.length; i++) {
let btn = document.createElement('button');
btn.addClass = "btn";
btn.style.backgroundColor = colorAry[i];
btn.addEventListener("click", function(){onChangeColor(colorAry[i])});
cGroup.appendChild(btn);
}
// Initially hide toolbar
canvasToolbar.style.display = 'none';
};
function updateCanvasStream() {
if (overlay && canvasStream) {
// Use requestAnimationFrame to ensure smooth stream updates
if (animationFrameId) {
cancelAnimationFrame(animationFrameId);
}
animationFrameId = requestAnimationFrame(() => {
try {
// Ensure canvas content has been updated
const imageData = overlay.toDataURL('image/png');
// Notify Arctos SDK to update filter
if (arctosSdkInstance && arctosSdkInstance.$meetingRoom) {
arctosSdkInstance.$meetingRoom.startFilter([{
type: 'image-mask',
value: imageData,
}], '300x150');
}
} catch (error) {
console.error('Error occurred while updating canvas stream:', error);
}
});
}
};
function initCaptureStream() {
if (overlay && !canvasStream) {
try {
// Set higher frame rate to ensure smoothness
canvasStream = overlay.captureStream(30);
// Monitor stream status
canvasStream.getTracks().forEach(track => {
track.addEventListener('ended', () => {
console.log('Canvas stream track has ended');
});
});
console.log('Canvas stream initialized successfully');
return true;
} catch (error) {
console.error('Failed to initialize canvas stream:', error);
return false;
}
}
return false;
}
function stopCaptureStream() {
if (canvasStream) {
canvasStream.getTracks().forEach(track => {
track.stop();
});
canvasStream = null;
}
if (animationFrameId) {
cancelAnimationFrame(animationFrameId);
animationFrameId = null;
}
}
Step 3: Canvas movement
/**
* Start drawing operation when mouse/touch is pressed down
* @param {MouseEvent|TouchEvent} e - Mouse or touch event
*/
function startDrawing(e) {
e.preventDefault();
isDrawing = true;
const { x, y } = getCoordinates(e);
[lastX, lastY] = [x, y];
updateCanvasStream();
};
/**
* Handle drawing movement and update dynamic cursor position
* @param {MouseEvent|TouchEvent} e - Mouse or touch move event
*/
function draw(e) {
if (!isDrawing) return;
e.preventDefault();
const { x, y } = getCoordinates(e);
ctx.beginPath();
ctx.moveTo(lastX, lastY);
ctx.lineTo(x, y);
ctx.lineCap = "round";
ctx.strokeStyle = strokeColor;
ctx.lineWidth = penBoo?strokeWidth:strokeWidth*.5;
ctx.stroke();
lastX = x;
lastY = y;
updateCanvasStream();
};
/**
* Stop drawing operation and save canvas state to history
* Triggers filter update to share drawing with other participants
*/
function stopDrawing() {
if (!isDrawing) return;
isDrawing = false;
e && e.preventDefault();
const base64 = overlay.toDataURL();
base64Data = base64;
historyImgs.push(base64);
updateCanvasStream();
};
/**
* Get normalized coordinates from mouse or touch event
* @param {MouseEvent|TouchEvent} event - Input event
* @returns {Object} Object containing x and y coordinates adjusted for pixel ratio
*/
function getCoordinates(event) {
let rect = overlay.getBoundingClientRect();
const scaleX = overlay.width / rect.width;
const scaleY = overlay.height / rect.height;
return {
x: event.type.includes('touch') ?
(event.touches[0].clientX - rect.left) :
(event.clientX - rect.left) * scaleX,
y: event.type.includes('touch') ?
(event.touches[0].clientY - rect.top) :
(event.clientY - rect.top) * scaleY
};
};
/**
* Handle mouse leave event - hide dynamic cursor
*/
function onMouseLeave() {
if (isDrawing) {
stopDrawing();
}
};
Step 4: Paint Board Toolbar Functions
The canvas toolbar provides 5 main functions for enhanced drawing experience.
/**
* Capture screenshot of local video and draw it as canvas background
* Useful for annotating over the current video feed
*/
// 1. Screenshot and set as background
function onCutPic() {
const video = document.querySelector('#local-element-camera .OV_video-element');
if (video) {
ctx.drawImage(video, 0, 0, overlay.width, overlay.height);
updateCanvasStream();
}
};
/**
* Clear the entire canvas and reset drawing history
* Updates the filter to reflect the cleared state
*/
// 2. Clear entire canvas
function onClearAll() {
ctx.clearRect(0, 0, overlay.width, overlay.height);
historyImgs = [];
updateCanvasStream();
}
/**
* Save the current canvas content as a PNG image file
* Downloads the image to the user's device
*/
// 3. Save canvas as image file
function onSave() {
const anchor = document.createElement('a');
anchor.href = overlay.toDataURL('image/png');
anchor.download = 'canvas-image-' + new Date().getTime() + '.png';
anchor.click();
}
/**
* Toggle brush thickness between normal (4px) and bold (16px)
* Updates UI to reflect current brush state
*/
// 4. Toggle brush thickness
function onPen() {
canvasToolbar.querySelector('.action.pen').classList.toggle('bold');
penBoo = !penBoo;
ctx.lineWidth = penBoo?strokeWidth:strokeWidth*.5;
}
/**
* Change the drawing brush color
* @param {string} str - Hex color code (e.g., '#ff0000' for red)
*/
// 5. Change drawing color
function onChangeColor(str) {
strokeColor = str;
ctx.strokeStyle = strokeColor;
}
Method Reference
Core Paint Board Methods
| Method | Description | Parameters |
|---|---|---|
utilsCanvasInit() | Initialize the paint board canvas | None |
onCutPic() | Take screenshot and set as background | None |
onClearAll() | Clear entire canvas | None |
onSave() | Save canvas as PNG image | None |
onPen() | Toggle brush thickness (4px ↔ 16px) | None |
onChangeColor(color) | Change drawing color | color: Hex color string |
Configuration Options
| Property | Type | Default | Description |
|---|---|---|---|
strokeColor | string | '#000000' | Current drawing color |
strokeWidth | number | 4 | Current brush width |
pixelRatio | number | window.devicePixelRatio | Canvas pixel density |
colorAry | array | Predefined colors | Available color palette |
The following url is a sample code for paint board implementation.
