Chapter 6: JavaScript Playback#

HTML5 & JavaScript for Media Playback#

BrightSign players use the Chromium rendering engine to display HTML5 content and execute JavaScript applications. This chapter covers how to leverage HTML5 and JavaScript for rich interactive media playback on BrightSign devices.

HTML5 Support#

Chromium Rendering Engine#

BrightSign players use Chromium as their HTML rendering engine. The version varies by firmware:

Rendering EngineVersionBrightSign FW Versions
Chromium878.5.x, 9.0.x
Chromium698.4.x, 8.3.x, 8.2.x, 8.1.x
Chromium658.0.x
Chromium457.1.x, 7.0.x, 6.2.x
Chromium376.1.x, 6.0.x

Use caniuse.com to determine which HTML5 features and APIs are supported in specific Chromium versions.

Supported HTML5 Features#

BrightSign HTML5 support includes:

  • Standard HTML5 elements (div, canvas, video, audio)
  • CSS3 styling and animations
  • WebGL for 3D graphics
  • SVG for vector graphics
  • Web Storage (localStorage, sessionStorage, IndexedDB)
  • WebSockets for real-time communication
  • Geolocation and device APIs
  • Media Source Extensions (MSE) for adaptive streaming

Limitations#

No Flash Support: BrightSign players do not support Flash content. Export Flash content as HTML using Adobe Creative Suite tools.

4K Graphics Restrictions:

  • XTx44, XTx43: Support native 4K HTML graphics with performance limitations (max 20 FPS for animations, recommend 1-2 4K images maximum)
  • XDx34, XDx33, HDx24, HDx23, LS424, LS423, 4Kx42: Graphics limited to 1920x1200, upscaled to 4K output

Image Size Limits: Default maximum is 2048x1280x32bpp (or 3840x2160x32bpp for XT/4K models). Use roVideoMode.SetImageSizeThreshold() to increase.

Memory Constraints by Model (Series 4 and older):

  • XTx43/44: 512MB graphics, 512MB JavaScript
  • XDx33/34: 256MB graphics, 512MB JavaScript
  • HDx24: 460MB graphics
  • LS4x5: 280MB graphics
  • HDx23/LS423/HO523: 256MB graphics, 128MB JavaScript

Series 5 players have dynamic memory allocation without pre-allocated graphics/system memory.

Best Practices#

Page Structure:

  1. Match HTML page aspect ratio to display resolution
  2. Use a master div aligned to 0,0 for correct alignment
  3. Keep all assets (images, videos, fonts) in the same site folder
  4. Test locally using Google Chrome for similar rendering

Performance:

  • Use images at their native size (avoid downscaling)
  • Use Class 10 SD cards for resource-intensive presentations
  • Limit directory depth to prevent complex folder structure issues
  • Scale images to output resolution before rendering in HTML

GPU Optimization:

  • GPU rasterization is enabled by default in firmware 6.2.x and later
  • Use image-rendering: optimizeSpeedBS for fast bilinear filtering when scaling images to 50% or less

JavaScript Engine#

V8 Engine Capabilities#

BrightSign uses the V8 JavaScript engine embedded in Chromium. Capabilities include:

  • ECMAScript standards support (version depends on Chromium version)
  • ES6 features (can be disabled via registry if needed)
  • Asynchronous operations (Promises, async/await)
  • Web Workers for multi-threading
  • Full DOM API access

Performance Considerations#

Memory Management:

  • Each HTML widget has its own JavaScript heap
  • Hard memory limit - Chromium terminates if no memory after garbage collection
  • Multiple HTML widgets can overcommit JavaScript memory
  • Use Chromium Web Inspector to monitor resource usage

Execution Context:

  • BrightSign players are HTML players with interactive capabilities, not general-purpose web browsers
  • Thoroughly test each page before deployment
  • Avoid complex, resource-intensive web applications designed for desktop browsers

Optimization Tips:

  • Minimize DOM manipulation operations
  • Avoid memory leaks with console.log of complex objects
  • Use TraceEvent system for debugging memory and performance issues
  • Disable Web Inspector in production (it consumes memory continuously in OS 8.5.31+)

DOM Manipulation#

Working with HTML Elements#

Standard DOM methods work as expected:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// Creating and appending elements
var div = document.createElement('div');
div.id = 'content';
div.className = 'container';
document.body.appendChild(div);

// Modifying elements
var element = document.getElementById('myElement');
element.textContent = 'Updated content';
element.style.backgroundColor = '#FF0000';

// Removing elements
var parent = document.getElementById('parent');
var child = document.getElementById('child');
parent.removeChild(child);

Event Handling#

BrightSign supports standard DOM event handling:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// addEventListener pattern
element.addEventListener('click', function(event) {
    console.log('Element clicked');
});

// Mouse and touch events (must enable mouse_enabled)
element.addEventListener('touchstart', handleTouch);
element.addEventListener('touchmove', handleMove);
element.addEventListener('touchend', handleEnd);

// Keyboard events (USB keyboard support)
document.addEventListener('keydown', function(event) {
    console.log('Key pressed:', event.key);
});

Animations#

Use CSS Animations Over JavaScript:

JavaScript timer-based animations (including jQuery .animate()) don’t efficiently use GPU resources. Use CSS animations instead:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<style>
.slide-in {
    -webkit-animation-name: slideAnimation;
    -webkit-animation-duration: 2s;
    -webkit-animation-fill-mode: forwards;
}

@-webkit-keyframes slideAnimation {
    0% { -webkit-transform: translateX(-100%); }
    100% { -webkit-transform: translateX(0); }
}
</style>

For jQuery users, use the Transit library which provides CSS animation-based API similar to .animate().

Canvas Animations:

  • Canvas 2D acceleration is enabled by default (OS8+)
  • Bitmap animations display smoothly when 1/3 or less of 1080p canvas
  • Set canvas to 720p for larger high-quality animations

CSS & Styling#

Advanced Styling#

BrightSign supports modern CSS features based on Chromium version:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
/* Flexbox layouts */
.container {
    display: flex;
    justify-content: center;
    align-items: center;
}

/* Grid layouts */
.grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 20px;
}

/* Custom properties */
:root {
    --primary-color: #0066cc;
    --spacing: 16px;
}

Responsive Design#

Design for the player’s output resolution:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
/* Example for 1920x1080 display */
body {
    width: 1920px;
    height: 1080px;
    margin: 0;
    padding: 0;
    overflow: hidden;
}

/* Media queries work but consider fixed resolution */
@media (min-width: 1920px) {
    .container { font-size: 24px; }
}

Hardware Acceleration#

CSS Transforms:

Always specify transforms as WebKit transforms. Don’t use inline transforms:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
/* CORRECT: Use CSS classes */
.rotate-element {
    -webkit-animation-name: rotation;
    -webkit-animation-duration: 2s;
    -webkit-animation-iteration-count: 1;
    -webkit-animation-fill-mode: forwards;
}

@-webkit-keyframes rotation {
    0% { -webkit-transform: rotateY(0deg); }
    50% { -webkit-transform: rotateY(180deg); }
    100% { -webkit-transform: rotateY(360deg); }
}

GPU Rasterization:

  • Force GPU rasterization is deprecated (enabled by default in OS8)
  • Canvas 2D acceleration enabled by default (can disable with canvas_2d_acceleration_enabled: false)

Web Fonts:

Include font files for better aesthetics. Supported formats:

  • TrueType Font (.ttf)
  • OpenType Font (.otf)
  • Web Open Font (.woff, .woff2)
1
2
3
4
5
6
7
8
9
@font-face {
    font-family: 'CustomFont';
    src: url('fonts/customfont.woff2') format('woff2'),
         url('fonts/customfont.woff') format('woff');
}

body {
    font-family: 'CustomFont', sans-serif;
}

Media Integration#

HTML5 Video/Audio#

BrightSign extends standard HTML5 video with custom attributes:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<!-- Basic video playback -->
<video src="video.mp4" width="1920" height="1080" autoplay loop>
    Your browser does not support video.
</video>

<!-- Streaming video -->
<video src="http://example.com/stream.m3u8" autoplay></video>
<video src="udp://239.192.1.1:5004" autoplay></video>
<video src="rtsp://example.com/stream" autoplay></video>

<!-- HDMI input -->
<video width="1920" height="1080" autoplay>
    <source src="tv:brightsign.biz/hdmi">
</video>

BrightSign Streaming Parameters:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<!-- Set stream timeout -->
<video src="udp://239.192.1.1:5004" x-bs-stream-timeout="5000"></video>

<!-- Low latency mode for RTSP -->
<video src="rtsp://camera.local/stream" x-bs-stream-low-latency="1"></video>

<!-- Reduce latency by 500ms -->
<video src="udp://239.192.1.1:5004" x-bs-stream-latency="-500"></video>

<!-- Set intrinsic size for proper aspect ratio -->
<video src="stream.m3u8"
       x-bs-intrinsic-width="1920"
       x-bs-intrinsic-height="1080"></video>

JavaScript Video Control:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
var video = document.getElementById('myVideo');

// Set attributes programmatically
video.setAttribute('x-bs-stream-low-latency', '1');

// Standard video controls
video.play();
video.pause();
video.currentTime = 30; // Seek to 30 seconds

// Event listeners
video.addEventListener('loadedmetadata', function() {
    console.log('Duration:', video.duration);
});

video.addEventListener('ended', function() {
    console.log('Video finished');
});

HWZ Video (Hardware Video Plane):

Use HWZ to route video directly to hardware compositor for better performance:

1
2
3
4
5
6
7
8
<!-- Video on hardware plane, behind graphics -->
<video src="video.mp4" hwz="z-index:-1" autoplay></video>

<!-- Video with rotation -->
<video src="video.mp4" hwz="z-index:-1; transform:rot90;" autoplay></video>

<!-- Video with luma keying -->
<video src="keyed.mp4" hwz="z-index:1; luma-key:#ff0020;" autoplay></video>

Enable HWZ globally in BrightScript:

htmlWidget = CreateObject("roHtmlWidget", rect)
htmlWidget.SetHWZDefault("on")

Audio Routing:

1
2
3
4
5
6
7
8
<!-- Route PCM audio to HDMI -->
<video src="video.mp4" pcmaudio="hdmi" autoplay></video>

<!-- Route compressed audio to HDMI and USB -->
<video src="video.mp4" compaudio="hdmi;usb" autoplay></video>

<!-- Multiple outputs for Series 5 -->
<video src="video.mp4" pcmaudio="hdmi-1;hdmi-2" autoplay></video>

Video Decryption:

1
2
3
4
<video src="udp://239.192.1.59:5000"
       EncryptionAlgorithm="TsAesEcb"
       EncryptionKey="01030507090b0d0f00020406080a0c0e">
</video>
1
2
3
var player = document.getElementById('secureVideo');
player.setAttribute('EncryptionAlgorithm', 'TsAesEcb');
player.setAttribute('EncryptionKey', '01030507090b0d0f00020406080a0c0e');

Synchronization with BrightScript#

Message Passing from JavaScript to BrightScript:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// In HTML/JavaScript
var MESSAGE_PORT = require("@brightsign/messageport");
var bsMessage = new MESSAGE_PORT();

// Send message to BrightScript
bsMessage.PostBSMessage({
    event: 'videoComplete',
    result: 'success',
    timestamp: Date.now()
});
' In BrightScript
htmlWidget = CreateObject("roHtmlWidget", rect, {url: "file:///index.html"})
port = CreateObject("roMessagePort")
htmlWidget.SetPort(port)

while true
    msg = wait(0, port)
    if type(msg) = "roHtmlWidgetEvent" then
        data = msg.GetData()
        if data.reason = "message" then
            print "Message from JS: "; data.message.event
            print "Result: "; data.message.result
        end if
    end if
end while

Message Passing from BrightScript to JavaScript:

' In BrightScript with roHtmlWidget
htmlWidget.PostJSMessage({command: "play", videoId: "video1"})
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// In JavaScript
var MESSAGE_PORT = require("@brightsign/messageport");
var bsMessage = new MESSAGE_PORT();

bsMessage.addEventListener('bsmessage', function(msg) {
    console.log('Received from BrightScript:', msg);
    if (msg.command === 'play') {
        playVideo(msg.videoId);
    }
});

Local Storage#

Browser Storage APIs#

BrightSign supports standard web storage APIs:

localStorage and sessionStorage:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// Store data
localStorage.setItem('username', 'John');
localStorage.setItem('settings', JSON.stringify({volume: 80, brightness: 100}));

// Retrieve data
var username = localStorage.getItem('username');
var settings = JSON.parse(localStorage.getItem('settings'));

// Remove data
localStorage.removeItem('username');
localStorage.clear(); // Remove all

// sessionStorage (cleared on widget close)
sessionStorage.setItem('tempData', 'value');

IndexedDB:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Open database
var request = indexedDB.open('MyDatabase', 1);

request.onupgradeneeded = function(event) {
    var db = event.target.result;
    var objectStore = db.createObjectStore('videos', {keyPath: 'id'});
    objectStore.createIndex('title', 'title', {unique: false});
};

request.onsuccess = function(event) {
    var db = event.target.result;

    // Add data
    var transaction = db.transaction(['videos'], 'readwrite');
    var objectStore = transaction.objectStore('videos');
    objectStore.add({id: 1, title: 'Video 1', duration: 120});

    // Read data
    var getRequest = objectStore.get(1);
    getRequest.onsuccess = function() {
        console.log('Video:', getRequest.result);
    };
};

Data Persistence#

Configuring Storage in BrightScript:

' Set storage path and quota
rect = CreateObject("roRectangle", 0, 0, 1920, 1080)
config = {
    url: "file:///index.html",
    storage_path: "SD:/html-storage",
    storage_quota: "1073741824" ' 1GB in bytes (use string for >2GB)
}
htmlWidget = CreateObject("roHtmlWidget", rect, config)

Configuring Storage in JavaScript:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
var HtmlWidgetClass = require("@brightsign/htmlwidget");
var htmlwidget = new HtmlWidgetClass({
    rect: {x: 0, y: 0, width: 1920, height: 1080},
    url: "https://example.com",
    storage: {
        path: "/storage/sd/html-data",
        quota: 1073741824, // 1GB
        forceSharedStorage: false,
        forceUnsharedStorage: false
    }
});

Offline Functionality#

Application Cache (deprecated but supported):

1
2
3
4
5
6
7
8
9
<!DOCTYPE html>
<html manifest="cache.appcache">
<head>
    <title>Offline App</title>
</head>
<body>
    <h1>Content works offline</h1>
</body>
</html>

Service Workers (preferred for modern applications):

1
2
3
4
5
6
7
// Register service worker
if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('/sw.js')
        .then(function(registration) {
            console.log('ServiceWorker registered:', registration);
        });
}

Web APIs#

Device APIs Available in BrightSign#

BrightSign provides JavaScript APIs for hardware access:

Device Information:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
var DeviceInfoClass = require("@brightsign/deviceinfo");
var deviceInfo = new DeviceInfoClass();

console.log('Model:', deviceInfo.model);
console.log('OS Version:', deviceInfo.osVersion);
console.log('Serial Number:', deviceInfo.serialNumber);

// Check capabilities
if (deviceInfo.hasFeature('hdmi')) {
    console.log('HDMI output available');
}

// Get temperature
deviceInfo.getTemperature().then(function(temp) {
    console.log('Temperature:', temp.celsius + '°C');
});

Storage Management:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
var StorageClass = require("@brightsign/storage");
var storage = new StorageClass();

// Format SD card
storage.format("/storage/sd", "vfat").then(function() {
    console.log("SD card formatted");
});

// Safely eject
storage.eject("/storage/sd").then(function() {
    console.log("SD card can be removed");
});

GPIO Control:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var ControlPortClass = require("@brightsign/controlport");
var controlPort = new ControlPortClass("BrightSign");

// Set GPIO output
controlPort.SetPinValue(0, true); // Set pin 0 high

// Read GPIO input
controlPort.addEventListener("controldown", function(event) {
    console.log("Button pressed on pin:", event.detail);
});

Serial Communication:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
var SerialPortClass = require("@brightsign/serialport");
var serial = new SerialPortClass("/dev/ttyUSB0");

serial.open({
    baudRate: 115200,
    dataBits: 8,
    stopBits: 1,
    parity: "none"
});

serial.on('data', function(data) {
    console.log('Received:', data);
});

serial.write('Hello\r\n');

Video Output Control:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
var VideoOutputClass = require("@brightsign/videooutput");
var videoOutput = new VideoOutputClass();

// Set video mode
videoOutput.setMode({
    width: 1920,
    height: 1080,
    refreshRate: 60
}).then(function() {
    console.log('Video mode set');
});

Sensors#

Temperature monitoring:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var DeviceInfoClass = require("@brightsign/deviceinfo");
var deviceInfo = new DeviceInfoClass();

setInterval(function() {
    deviceInfo.getTemperature().then(function(temp) {
        if (temp.celsius > 70) {
            console.warn('High temperature:', temp.celsius);
        }
    });
}, 60000); // Check every minute

Geolocation#

Standard Geolocation API (requires network connection):

1
2
3
4
5
6
7
8
if (navigator.geolocation) {
    navigator.geolocation.getCurrentPosition(function(position) {
        console.log('Latitude:', position.coords.latitude);
        console.log('Longitude:', position.coords.longitude);
    }, function(error) {
        console.error('Geolocation error:', error);
    });
}

Communication#

JavaScript to BrightScript Interaction#

Using messageport:

The messageport is the preferred communication method.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// JavaScript side
var MESSAGE_PORT = require("@brightsign/messageport");
var bsMessage = new MESSAGE_PORT();

// Listen for messages from BrightScript
bsMessage.addEventListener('bsmessage', function(msg) {
    console.log('Received:', JSON.stringify(msg));

    // Process commands
    switch(msg.command) {
        case 'updateContent':
            updateDisplay(msg.data);
            break;
        case 'restart':
            location.reload();
            break;
    }
});

// Send message to BrightScript
function notifyBrightScript(eventType, data) {
    bsMessage.PostBSMessage({
        type: eventType,
        timestamp: Date.now(),
        data: data
    });
}

// Example usage
document.getElementById('button').addEventListener('click', function() {
    notifyBrightScript('buttonClicked', {buttonId: 'button1'});
});
' BrightScript side
rect = CreateObject("roRectangle", 0, 0, 1920, 1080)
port = CreateObject("roMessagePort")
config = {
    url: "file:///index.html",
    port: port
}
htmlWidget = CreateObject("roHtmlWidget", rect, config)

' Send message to JavaScript
htmlWidget.PostJSMessage({
    command: "updateContent",
    data: {title: "New Title", content: "New content"}
})

' Event loop
while true
    msg = wait(0, port)
    if type(msg) = "roHtmlWidgetEvent" then
        data = msg.GetData()

        if data.reason = "message" then
            print "Message from JS: "; data.message.type
            if data.message.type = "buttonClicked" then
                print "Button ID: "; data.message.data.buttonId
                ' Handle button click
            end if
        else if data.reason = "load-finished" then
            print "Page loaded successfully"
        else if data.reason = "load-error" then
            print "Page load error: "; data.url
        end if
    end if
end while

Message Passing Patterns#

Request-Response Pattern:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// JavaScript - Request data
var requestId = 0;
var pendingRequests = {};

function requestData(type) {
    var id = ++requestId;

    return new Promise(function(resolve, reject) {
        pendingRequests[id] = {resolve: resolve, reject: reject};
        bsMessage.PostBSMessage({
            requestId: id,
            type: 'request',
            dataType: type
        });

        // Timeout after 5 seconds
        setTimeout(function() {
            if (pendingRequests[id]) {
                reject(new Error('Request timeout'));
                delete pendingRequests[id];
            }
        }, 5000);
    });
}

// Handle responses
bsMessage.addEventListener('bsmessage', function(msg) {
    if (msg.type === 'response' && pendingRequests[msg.requestId]) {
        pendingRequests[msg.requestId].resolve(msg.data);
        delete pendingRequests[msg.requestId];
    }
});

// Usage
requestData('currentPlaylist').then(function(playlist) {
    console.log('Playlist:', playlist);
});

Event Broadcasting:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// JavaScript - Broadcast events
var EventEmitter = {
    emit: function(eventName, data) {
        bsMessage.PostBSMessage({
            type: 'event',
            event: eventName,
            data: data,
            timestamp: Date.now()
        });
    }
};

// Usage
EventEmitter.emit('userInteraction', {action: 'touch', x: 100, y: 200});
EventEmitter.emit('videoEnded', {videoId: 'video1', duration: 120});

Multiple HTML Widget Communication#

Using JavaScript htmlwidget for parent-child communication:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// Parent widget
var HtmlWidgetClass = require("@brightsign/htmlwidget");
var childWidget = new HtmlWidgetClass({
    rect: {x: 100, y: 100, width: 800, height: 600},
    url: "child.html"
});

// Send message to child
childWidget.postMessage({command: 'play', videoId: 'video1'});

// Listen for events from child
childWidget.addEventListener('message', function(event) {
    console.log('Child message:', event);
});

Performance Optimization#

Rendering Performance#

Minimize Repaints and Reflows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// BAD - Multiple reflows
element.style.width = '100px';
element.style.height = '100px';
element.style.backgroundColor = 'red';

// GOOD - Single reflow
element.style.cssText = 'width: 100px; height: 100px; background-color: red;';

// BETTER - Use CSS classes
element.className = 'optimized-style';

Batch DOM Operations:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// BAD - Multiple DOM insertions
for (var i = 0; i < 100; i++) {
    var div = document.createElement('div');
    div.textContent = 'Item ' + i;
    container.appendChild(div);
}

// GOOD - Build fragment first
var fragment = document.createDocumentFragment();
for (var i = 0; i < 100; i++) {
    var div = document.createElement('div');
    div.textContent = 'Item ' + i;
    fragment.appendChild(div);
}
container.appendChild(fragment);

Use requestAnimationFrame:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// Smooth animation loop
function animate() {
    // Update animation state
    updatePosition();

    // Request next frame
    requestAnimationFrame(animate);
}

requestAnimationFrame(animate);

Hardware-Accelerated Properties:

Prefer CSS properties that use GPU acceleration:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
/* GPU-accelerated */
.animated {
    transform: translateX(100px);
    opacity: 0.5;
}

/* Avoid - causes repaints */
.slow {
    left: 100px;
    background-color: rgba(0, 0, 0, 0.5);
}

Memory Management#

Avoid Memory Leaks:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// Remove event listeners when done
var handler = function() { /* ... */ };
element.addEventListener('click', handler);

// Later, when element is removed
element.removeEventListener('click', handler);

// Clear intervals and timeouts
var interval = setInterval(update, 1000);
clearInterval(interval);

// Null references to large objects
var largeArray = new Array(1000000);
// ... use array ...
largeArray = null; // Allow garbage collection

Manage Video Elements:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// Release video resources when switching
function switchVideo(newSource) {
    var video = document.getElementById('player');

    // Important: set src to empty to release resources
    video.src = '';

    // Create new video element
    video = document.createElement('video');
    video.id = 'player';
    video.src = newSource;
    video.play();

    document.getElementById('container').appendChild(video);
}

Monitor Memory Usage:

1
2
3
4
5
6
// Check available memory (if performance.memory is available)
if (performance.memory) {
    console.log('Used JS Heap:', performance.memory.usedJSHeapSize);
    console.log('Total JS Heap:', performance.memory.totalJSHeapSize);
    console.log('Heap Limit:', performance.memory.jsHeapSizeLimit);
}

Profiling#

Web Inspector for Debugging:

Enable in BrightScript:

' Enable Web Inspector
reg = CreateObject("roRegistrySection", "html")
reg.Write("enable_web_inspector", "1")
reg.Flush()

' Create widget with inspector
rect = CreateObject("roRectangle", 0, 0, 1920, 1080)
config = {
    url: "file:///index.html",
    inspector_server: {port: 2999}
}
htmlWidget = CreateObject("roHtmlWidget", rect, config)

Access from Chrome on same network:

  1. Navigate to chrome://inspect/devices
  2. Configure with player IP and port (e.g., 192.168.1.100:2999)
  3. Click “Inspect” to open DevTools

TraceEvent System:

Enable trace events for advanced profiling:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var registryClass = require("@brightsign/registry");
var registry = new registryClass();

registry.write("html", {
    "tracecategories": "toplevel,blink_gc,disabled-by-default-memory-infra",
    "tracemaxsnapshots": "25",
    "tracemonitorinterval": "60"
}).then(function() {
    console.log("TraceEvent enabled");
});

Create brightsign-webinspector directory on SD card. JSON trace files will be written there for import into Chrome’s chrome://tracing.

Performance Monitoring:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
var DeviceInfoClass = require("@brightsign/deviceinfo");
var deviceInfo = new DeviceInfoClass();

// Get system load statistics
deviceInfo.getLoadStatistics('loadavg').then(function(stats) {
    console.log('System load:', stats);
});

deviceInfo.getLoadStatistics('meminfo').then(function(stats) {
    console.log('Memory info:', stats);
});

Console Logging Best Practices:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// Disable console in production
if (PRODUCTION) {
    console.log = function() {};
    console.error = function() {};
    console.warn = function() {};
}

// Use performance markers
performance.mark('video-load-start');
// ... load video ...
performance.mark('video-load-end');
performance.measure('video-load', 'video-load-start', 'video-load-end');

var measures = performance.getEntriesByType('measure');
console.log('Video load time:', measures[0].duration, 'ms');

Complete Example: Interactive Video Player#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>BrightSign Video Player</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            width: 1920px;
            height: 1080px;
            background-color: #000;
            overflow: hidden;
        }

        #videoContainer {
            position: absolute;
            width: 100%;
            height: 100%;
        }

        #player {
            width: 100%;
            height: 100%;
            object-fit: cover;
        }
// ... (see full example below)
        }

        // Start application
        init();

        // Notify BrightScript that page is ready
        notifyBrightScript('ready', {});
    </script>
</body>
</html>
View complete code
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>BrightSign Video Player</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            width: 1920px;
            height: 1080px;
            background-color: #000;
            overflow: hidden;
        }

        #videoContainer {
            position: absolute;
            width: 100%;
            height: 100%;
        }

        #player {
            width: 100%;
            height: 100%;
            object-fit: cover;
        }

        #controls {
            position: absolute;
            bottom: 50px;
            left: 50%;
            transform: translateX(-50%);
            background: rgba(0, 0, 0, 0.7);
            padding: 20px;
            border-radius: 10px;
        }

        button {
            padding: 15px 30px;
            margin: 0 10px;
            font-size: 18px;
            border: none;
            border-radius: 5px;
            background-color: #0066cc;
            color: white;
            cursor: pointer;
        }

        button:hover {
            background-color: #0052a3;
        }
    </style>
</head>
<body>
    <div id="videoContainer">
        <video id="player" hwz="z-index:-1"></video>
    </div>

    <div id="controls">
        <button id="playBtn">Play</button>
        <button id="pauseBtn">Pause</button>
        <button id="nextBtn">Next</button>
    </div>

    <script>
        // BrightSign messageport for communication
        var MESSAGE_PORT = require("@brightsign/messageport");
        var bsMessage = new MESSAGE_PORT();

        // Device info
        var DeviceInfoClass = require("@brightsign/deviceinfo");
        var deviceInfo = new DeviceInfoClass();

        console.log('Player model:', deviceInfo.model);
        console.log('OS version:', deviceInfo.osVersion);

        // Video player state
        var currentVideoIndex = 0;
        var playlist = [];
        var player = document.getElementById('player');

        // Initialize
        function init() {
            // Request playlist from BrightScript
            bsMessage.PostBSMessage({
                type: 'request',
                action: 'getPlaylist'
            });

            // Setup event listeners
            setupVideoEvents();
            setupControls();
            setupMessageListener();
        }

        function setupVideoEvents() {
            player.addEventListener('loadedmetadata', function() {
                console.log('Video loaded, duration:', player.duration);
                notifyBrightScript('videoLoaded', {
                    duration: player.duration,
                    index: currentVideoIndex
                });
            });

            player.addEventListener('ended', function() {
                console.log('Video ended');
                notifyBrightScript('videoEnded', {index: currentVideoIndex});
                playNext();
            });

            player.addEventListener('error', function(e) {
                console.error('Video error:', e);
                notifyBrightScript('videoError', {
                    index: currentVideoIndex,
                    error: player.error.code
                });
            });
        }

        function setupControls() {
            document.getElementById('playBtn').addEventListener('click', function() {
                player.play();
                notifyBrightScript('userAction', {action: 'play'});
            });

            document.getElementById('pauseBtn').addEventListener('click', function() {
                player.pause();
                notifyBrightScript('userAction', {action: 'pause'});
            });

            document.getElementById('nextBtn').addEventListener('click', function() {
                playNext();
                notifyBrightScript('userAction', {action: 'next'});
            });
        }

        function setupMessageListener() {
            bsMessage.addEventListener('bsmessage', function(msg) {
                console.log('Message from BrightScript:', msg);

                switch(msg.type) {
                    case 'playlist':
                        playlist = msg.videos;
                        if (playlist.length > 0) {
                            loadVideo(0);
                        }
                        break;

                    case 'command':
                        handleCommand(msg);
                        break;

                    case 'updateSettings':
                        updateSettings(msg.settings);
                        break;
                }
            });
        }

        function handleCommand(msg) {
            switch(msg.command) {
                case 'play':
                    player.play();
                    break;
                case 'pause':
                    player.pause();
                    break;
                case 'seek':
                    player.currentTime = msg.position;
                    break;
                case 'setVolume':
                    player.volume = msg.volume;
                    break;
            }
        }

        function loadVideo(index) {
            if (index >= 0 && index < playlist.length) {
                currentVideoIndex = index;

                // Clear current video to release resources
                player.src = '';

                // Load new video
                player.src = playlist[index].url;
                player.load();

                console.log('Loading video:', playlist[index].title);
            }
        }

        function playNext() {
            var nextIndex = (currentVideoIndex + 1) % playlist.length;
            loadVideo(nextIndex);
            player.play();
        }

        function notifyBrightScript(eventType, data) {
            bsMessage.PostBSMessage({
                type: 'event',
                event: eventType,
                timestamp: Date.now(),
                data: data
            });
        }

        function updateSettings(settings) {
            if (settings.volume !== undefined) {
                player.volume = settings.volume;
            }

            // Save to localStorage
            localStorage.setItem('settings', JSON.stringify(settings));
        }

        // Load saved settings
        var savedSettings = localStorage.getItem('settings');
        if (savedSettings) {
            updateSettings(JSON.parse(savedSettings));
        }

        // Start application
        init();

        // Notify BrightScript that page is ready
        notifyBrightScript('ready', {});
    </script>
</body>
</html>

Corresponding BrightScript code:

Sub Main()
    ' Create HTML widget
    rect = CreateObject("roRectangle", 0, 0, 1920, 1080)
    port = CreateObject("roMessagePort")

    config = {
        url: "file:///sd:/index.html",
        port: port,
        mouse_enabled: true,
        storage_path: "SD:/html-storage",
        storage_quota: "536870912"  ' 512MB
    }

    htmlWidget = CreateObject("roHtmlWidget", rect, config)
    htmlWidget.Show()

    ' Playlist data
    playlist = [
        {title: "Video 1", url: "file:///sd:/videos/video1.mp4"},
        {title: "Video 2", url: "file:///sd:/videos/video2.mp4"},
        {title: "Video 3", url: "file:///sd:/videos/video3.mp4"}
    ]

    ' Event loop
    while true
        msg = wait(0, port)

        if type(msg) = "roHtmlWidgetEvent" then
            data = msg.GetData()

// ... (see full example below)
        {title: "Video 1", url: "file:///sd:/videos/video1.mp4"},
        {title: "Video 2", url: "file:///sd:/videos/video2.mp4"},
        {title: "Video 3", url: "file:///sd:/videos/video3.mp4"}
    ]
End Function

Sub LogUserAction(action as String)
    ' Log to registry or file
    print "User performed: "; action
End Sub
View complete code
Sub Main()
    ' Create HTML widget
    rect = CreateObject("roRectangle", 0, 0, 1920, 1080)
    port = CreateObject("roMessagePort")

    config = {
        url: "file:///sd:/index.html",
        port: port,
        mouse_enabled: true,
        storage_path: "SD:/html-storage",
        storage_quota: "536870912"  ' 512MB
    }

    htmlWidget = CreateObject("roHtmlWidget", rect, config)
    htmlWidget.Show()

    ' Playlist data
    playlist = [
        {title: "Video 1", url: "file:///sd:/videos/video1.mp4"},
        {title: "Video 2", url: "file:///sd:/videos/video2.mp4"},
        {title: "Video 3", url: "file:///sd:/videos/video3.mp4"}
    ]

    ' Event loop
    while true
        msg = wait(0, port)

        if type(msg) = "roHtmlWidgetEvent" then
            data = msg.GetData()

            if data.reason = "message" then
                ' Handle messages from JavaScript
                HandleJavaScriptMessage(htmlWidget, data.message)

            else if data.reason = "load-finished" then
                print "Page loaded successfully"

            else if data.reason = "load-error" then
                print "Page load error: "; data.url
            end if
        end if
    end while
End Sub

Sub HandleJavaScriptMessage(htmlWidget as Object, message as Object)
    print "Message from JS: "; message.type

    if message.type = "request" and message.action = "getPlaylist" then
        ' Send playlist to JavaScript
        htmlWidget.PostJSMessage({
            type: "playlist",
            videos: GetPlaylist()
        })

    else if message.type = "event" then
        print "Event: "; message.event

        if message.event = "videoEnded" then
            print "Video ended at index: "; message.data.index

        else if message.event = "userAction" then
            print "User action: "; message.data.action
            LogUserAction(message.data.action)
        end if
    end if
End Sub

Function GetPlaylist() as Object
    return [
        {title: "Video 1", url: "file:///sd:/videos/video1.mp4"},
        {title: "Video 2", url: "file:///sd:/videos/video2.mp4"},
        {title: "Video 3", url: "file:///sd:/videos/video3.mp4"}
    ]
End Function

Sub LogUserAction(action as String)
    ' Log to registry or file
    print "User performed: "; action
End Sub

Summary#

JavaScript and HTML5 playback on BrightSign provides powerful capabilities for creating interactive media experiences:

  • HTML5 Support: Chromium-based rendering with modern web standards
  • JavaScript Engine: V8 engine with full DOM API access
  • DOM Manipulation: Standard web APIs with hardware-optimized rendering
  • CSS & Styling: Modern CSS3 features with GPU acceleration
  • Media Integration: Extended HTML5 video with BrightSign-specific features
  • Local Storage: Full support for localStorage, IndexedDB, and persistent data
  • Web APIs: BrightSign-specific device APIs for hardware access
  • Communication: Robust messageport system for JavaScript-BrightScript interaction
  • Performance: Hardware acceleration, profiling tools, and optimization techniques

Always test thoroughly on target hardware, monitor resource usage, and disable debugging features in production deployments.