arctos Web-SDK For developsarctos Web-SDK For develops
  • English
  • 繁體中文
  • English
  • 繁體中文
  • Guide

    • get-started
  • overview
  • Spec
  • installization
  • features

    • initial_setting
    • camera
    • microphone
    • speaker
    • share_screen
    • paint_board
      • Prerequisites
      • Step 1: Initialize Arctos Instance
      • Step 2: Canvas Initialization
      • Step 3: Canvas movement
      • Step 4: Paint Board Toolbar Functions
      • Method Reference
        • Core Paint Board Methods
        • Configuration Options
    • share_message
    • switch_template
    • video_filters

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

MethodDescriptionParameters
utilsCanvasInit()Initialize the paint board canvasNone
onCutPic()Take screenshot and set as backgroundNone
onClearAll()Clear entire canvasNone
onSave()Save canvas as PNG imageNone
onPen()Toggle brush thickness (4px ↔ 16px)None
onChangeColor(color)Change drawing colorcolor: Hex color string

Configuration Options

PropertyTypeDefaultDescription
strokeColorstring'#000000'Current drawing color
strokeWidthnumber4Current brush width
pixelRationumberwindow.devicePixelRatioCanvas pixel density
colorAryarrayPredefined colorsAvailable color palette

The following url is a sample code for paint board implementation.

Sample Code

Prev
share_screen
Next
share_message