Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ Note that some starter examples include the creation of a `brightsign-dumps` fol
| [send-plugin-message](browser/send-plugin-message) | Plugin message communication between BrightScript and HTML/JS | 8.x, 9.x |
| [syncmanager-js](browser/syncmanager-js) | Multi-player content synchronization using SyncManager JS API | 8.x, 9.x |
| [large-file-download](browser/large-file-download) | Memory-bounded multi-GB download using Node streams in roHtmlWidget | 8.x, 9.x |
| [seamless-video-switching](browser/seamless-video-switching) | Gap-free HTML5 video playback using dual video elements with background preloading; reads asset list via Node `fs` in roHtmlWidget | 8.x, 9.x |


### Node.js 14 Examples (`node-14/`)
Expand Down
94 changes: 94 additions & 0 deletions examples/browser/seamless-video-switching/README.md
Comment thread
jdmedlin1 marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# BrightSign Dual Video Player HTML5 Application - Seamless Playback

> A seamless HTML5 video player application for BrightSign that provides gap-free video transitions using dual video elements with background preloading.

## 🎯 Why Use This Approach?

**This dual video player provides truly seamless, gap-free video playback** - essential for advertising and professional digital signage where any visible gap between videos is unacceptable.

### The Dual Player Advantage:
- ✅ **Zero visible gaps** - No freeze frames or black screens between videos
- ✅ **Instant transitions** - Next video is preloaded and ready to play
- ✅ **No fade effects** - Clean cuts between videos without mixing content

### How It Works:
1. Two video elements are layered on top of each other
2. While video 1 plays, video 2 loads the next file in the background
3. When video 1 ends, video 2 instantly becomes visible and starts playing
4. Video 1 (now hidden) loads the next file in the background
5. The cycle repeats for seamless continuous playback

## Configuration

You can customize the following settings in `index.js` before deploying:

- **`rootStoragePath`** - The root storage path on the BrightSign player (default: `/storage/sd`)
- **`assetsFolder`** - The folder name containing your video files (default: `assets`)
- **`MULTI_DECODER`** - Set to `true` on players with multiple hardware decode pipelines to enable near-zero-gap transitions (default: `false`). See [Multi-decoder mode](#multi-decoder-mode) below.
- **`EARLY_START_LEAD_SECONDS`** - Multi-decoder only. How many seconds before the visible video ends to start the hidden player (default: `0.5`). Raise if the swap reveals unrendered frames; lower for a tighter cut.

Example:
```javascript
const rootStoragePath = '/storage/sd';
const assetsFolder = 'assets'; // Change this to use a different folder name
const MULTI_DECODER = false; // Set to true on XT/4K series players
const EARLY_START_LEAD_SECONDS = 0.5; // Multi-decoder only
```

If you change `assetsFolder` to a different name (e.g., `videos`), make sure to create that folder on your SD card and place your video files there instead.

## Multi-decoder mode

On BrightSign players with multiple hardware decode pipelines (mid-to-high-end XT and 4K series), set `MULTI_DECODER = true` in `index.js` to enable two optimizations that further reduce the transition gap:

1. **Decoder pre-warming** — after the hidden player buffers (`canplay`), it plays briefly then pauses at frame 0. This initializes the hardware decode pipeline so `play()` at switch time starts near-instantly.
2. **Early start** — the hidden player begins playing (muted, hidden) `EARLY_START_LEAD_SECONDS` before the visible video ends (default 0.5s). By the time `ended` fires, the hidden player is already producing frames and the swap is immediate.

If the early start does not complete in time, the swap falls back to awaiting the `playing` event, preventing a swap onto an unrendered frame.

**Do not enable `MULTI_DECODER = true` on single-decoder hardware** — pre-warming and early start will compete with the active player's decoder and may cause stuttering. Leave `MULTI_DECODER = false` for the default single-decoder behavior. Refer to the [BrightSign model and series reference](https://docs.brightsign.biz/hardware/model-and-series-reference) to check your player's capabilities.

| | `MULTI_DECODER = false` (default) | `MULTI_DECODER = true` |
|---|---|---|
| Hardware | Any BrightSign player | Multi-decoder required (XT, 4K) |
| Gap at transition | Small gap (decoder handoff) | Near-zero |
| Simultaneous decoding | No | Yes |

## Deployment to BrightSign Player

### SD Card Structure

Your BrightSign player's SD card should have the following structure:

```
SD/
├── autorun.brs (launches index.html)
├── index.html (loads index.js)
├── index.js (application logic)
└── assets/ (your video files)
├── video1.mp4
├── video2.mp4
└── video3.ts
```

### Deployment Steps

1. Copy the following files to the root of your SD card:
- `autorun.brs`
- `index.html`
- `index.js`
2. Create an `assets/` folder on the SD card
3. Copy your video files into the `assets/` folder
4. Insert the SD card into your BrightSign player and power it on

The application will automatically:
- Load video files from `/storage/sd/assets/`
- Sort them alphabetically
- Play them in sequence with seamless transitions
- Loop back to the first video after the last one finishes

## Notes

- **Supported video formats**: See [BrightSign video formats and codecs](https://docs.brightsign.biz/advanced/video-formats-and-codecs) for the list of containers, codecs, and profiles supported by each player series.
- **Performance**: Playback smoothness and switching behavior may vary depending on player model, OS version, video mode (resolution/framerate), and the encoding of your source files. Adjust the code (e.g., preload behavior, number of preloaded players) to fit your specific use-case.
- **Early start threshold (multi-decoder mode)**: Adjust `EARLY_START_LEAD_SECONDS` (default `0.5`) to trade content budget against transition reliability on your specific hardware and content type.
61 changes: 61 additions & 0 deletions examples/browser/seamless-video-switching/architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Architecture Diagram

```mermaid
graph TD
Player["BrightSign Player"]
Autorun["autorun.brs<br/>(BrightScript)"]
HTML["index.html<br/>(Dual Video Elements)"]
Bundle["index.js<br/>(Switching Logic)"]
Display["HDMI Display<br/>(Video Output)"]
Assets[("assets/<br/>(Video Files<br/>.mp4, .ts)")]
Player1["Video Player 1<br/>(Visible/Hidden)"]
Player2["Video Player 2<br/>(Hidden/Visible)"]

Player -->|"Boots & Launches"| Autorun
Autorun -->|"Creates roHtmlWidget<br/>Loads HTML"| HTML
HTML -->|"Loads & Executes"| Bundle
Bundle -->|"Reads Video Files"| Assets
Bundle -->|"Controls Playback<br/>& Visibility"| Player1
Bundle -->|"Controls Playback<br/>& Visibility"| Player2
Player1 -->|"Renders Video"| Display
Player2 -->|"Renders Video"| Display

style Player fill:#4a90e2,stroke:#333,stroke-width:2px,color:#fff
style Autorun fill:#e67e22,stroke:#333,stroke-width:2px,color:#fff
style HTML fill:#9b59b6,stroke:#333,stroke-width:2px,color:#fff
style Bundle fill:#7b68ee,stroke:#333,stroke-width:2px,color:#fff
style Display fill:#34495e,stroke:#333,stroke-width:2px,color:#fff
style Assets fill:#50c878,stroke:#333,stroke-width:2px,color:#fff
style Player1 fill:#e74c3c,stroke:#333,stroke-width:2px,color:#fff
style Player2 fill:#e74c3c,stroke:#333,stroke-width:2px,color:#fff
```

## Seamless Video Switching Flow

1. **Initial Load**: Player 1 loads and plays the first video
2. **Preload**: While Player 1 plays, Player 2 preloads the next video (hidden)
3. **Switch Trigger**: When Player 1 ends, the switching sequence begins
4. **Start Hidden Player**: Player 2 starts playing (while still hidden)
5. **Wait for Playback**: Wait until Player 2 is actually playing
6. **Instant Transition**: Player 2 becomes visible, Player 1 becomes hidden
7. **Background Preload**: Player 1 (now hidden) preloads the next video
8. **Loop**: Repeat steps 3-7 indefinitely for continuous playback

## Key Features

- **Zero-Gap Transitions**: No black screens or freeze frames between videos
- **Dual Player Technique**: Two HTML5 video elements layered using absolute positioning
- **Background Preloading**: Next video is fully loaded before current video ends
- **Instant Visibility Toggle**: CSS class switching provides immediate visual transition
- **Alphabetical Playback**: Videos are sorted and played in alphabetical order
- **Infinite Loop**: Playlist automatically loops back to the first video

## Legend

- **Blue**: BrightSign Player
- **Orange**: BrightScript
- **Purple**: HTML/JS Application
- **Purple (Dark)**: JavaScript Logic
- **Dark Gray**: External Hardware
- **Green**: Video Files
- **Red**: Video Player Elements
40 changes: 40 additions & 0 deletions examples/browser/seamless-video-switching/autorun.brs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
function main()
mp = CreateObject("roMessagePort")

' Create HTML Widget
widget = CreateHTMLWidget(mp)
widget.Show()

'Event Loop
while true
msg = wait(0, mp)
print "msg received - type=";type(msg)
if type(msg) = "roHtmlWidgetEvent" then
print "msg: ";msg
end if
end while

end function

function CreateHTMLWidget(mp as object) as object
' Get Screen Resolution
vidmode = CreateObject("roVideoMode")
width = vidmode.GetResX()
height = vidmode.GetResY()

r = CreateObject("roRectangle", 0, 0, width, height)

' Create HTML Widget config
config = {
javascript_enabled: true,
brightsign_js_objects_enabled: true,
nodejs_enabled: true,
url: "file:///sd:/index.html",
port: mp
}

' Create HTML Widget
h = CreateObject("roHtmlWidget", r, config)
return h

end function
51 changes: 51 additions & 0 deletions examples/browser/seamless-video-switching/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Seamless Video App</title>
<style>
body,
html {
margin: 0;
padding: 0;
height: 100%;
overflow: hidden;
}

body {
font-family: Arial, sans-serif;
color: white;
}

#video-container {
width: 100%;
height: 100%;
}

.hidden {
display: none;
}

#video-player-1,
#video-player-2 {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}

video {
background: black;
}
</style>
</head>
<body>
<div id="video-container">
<video id="video-player-1" preload="auto" autoplay></video>
<video id="video-player-2" class="hidden" preload="auto"></video>
</div>
<script src="index.js"></script>
</body>
</html>
Loading
Loading