-
Notifications
You must be signed in to change notification settings - Fork 6
feat(PE-1271): add seamless video switching example #128
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
de9c5dd
feat(PE-1271): add seamless video switching example
jdmedlin1 33e91d7
fix(PE-1271): defer visibility swap until nextPlayer is playing
jdmedlin1 f4d2039
docs(PE-1271): added notes to supported content types and varied perf…
jdmedlin1 9de1524
fix(PE-1271): improve seamless video switching reliability and add mu…
jdmedlin1 64ea2d9
docs(PE-1271): improve single-decoder README cross-reference to multi…
jdmedlin1 d21cea9
refactor(PE-1271): consolidate multi-decoder variant into MULTI_DECOD…
jdmedlin1 5bb4ce3
refactor(PE-1271): extract early-start lead time into EARLY_START_LEA…
jdmedlin1 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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> |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.