---
title: Adding HTML Overlays - ZEN Master
slug: zen-master/adding-html-overlays-zen-master
docTags: 
createdAt: 2026-05-22T19:54:07.392Z
---

## Overview

HTML overlays, which might be static images or dynamic widgets showing such things as news headlines, stock prices, sports scores, or local weather, can be added as overlays on streaming videos during transcoding.

Zixi uses the **Chromium Embedded Framework (CEF)&#x20;**&#x74;o embed the HTML overlay into each frame of the video.&#x20;

Note that this process is separate from the processing of the video frames, and depending on the CPU load, might run more slowly. That means that while a snapshot from the HTML overlay will be added to every frame, some images may repeat. For a static overlay this makes no difference, but for a dynamic overlay, there might be small discrepancies between what is seen in the video stream and what is seen in overlay viewed separately.

## Requirements

- The Broadcaster must have a license key that enables transcoding as well as a [transcoder (software, GPU, or VPU) installed](docId\:lgQ7l9Suyqop2EtaBv1uL).
- The [HTML Overlay Plugin](docId:1svyta7P777nBTqPN8yt8) must also be installed.

## Adding the Overlay to a Transcoded Source

In ZEN Master, HTML Overlays can only be added to [Transcoded Source](docId\:lNauA-wKTttUl4xktBido). The process is very simple:

1. Put the HTML Overlay on public URL.
2. Add the URL to the **HTML Overlay URL** field, which appears in the **Advanced** section of the screen for creating a Transcoded Source:

![HTML Overlay URL Field](https://api.archbee.com/api/optimize/mzxtTQEvCNIdUNgF2kuwJ/2oX5nTJ6NeWXnX2_ohihD-20260525-160008.png)

Note that there is an option to use the audio track from the HTML Overlay for the video audio track, which requires entering the audio PID number.

## Creating HTML Overlays

HTML overlays can be created as static content that sits on top of other content, such as a logo or an ad banner. They can also be dynamic, pulling content such as current weather conditions or news headlines in from another source via JavaScript.

:::hint{type="info"}
Before looking at the details of creating overlays, it's worth pointing out that there are several platforms there are several platforms available that will help you do so easily. The one we find best for video overlays is [Singular.live](https://singular.live).
:::

### Creating Static HTML Overlays

Creating a static overlay is straightforward.

::::WorkflowBlock
:::WorkflowBlockItem
Add a basic overlay element to your HTML:

```html
<div id="overlay">Place whatever content you want here.</div>
```
:::

:::WorkflowBlockItem
Style it with CSS so it covers the whole viewport and sits on top of the page:
:::
::::

You can drop that into a bare HTML file and see it work, then adapt the CSS (color, opacity, z‑index) and what you place inside `#overlay` for your use case (logo, tutorial, modal, etc.).

### Creating Dynamic HTML Overlays

You can treat the overlay as a floating “info panel” that your JavaScript keeps updating with live data (weather, headlines, or both).

**Basic Idea**

You create a fixed-position overlay element, style it so it sits on top of the page (but not full‑screen, unless you want that), and use`fetch` on an interval to pull in JSON from whatever API you like. Then you render that data into the overlay.

**Example: dynamic overlay with weather + headlines&#x20;**

```html
<!DOCTYPE html>
<html lang="en">
<head></head>
  <meta charset="UTF-8" />
  <title>Dynamic Overlay: Weather &amp; News</title>
  <style>
    /* Overlay container */
    #info-overlay {
      position: fixed;
      top: 1rem;
      right: 1rem;
      width: 300px;
      max-height: 80vh;
      background: rgba(0, 0, 0, 0.8);
      color: #f0f0f0;
      padding: 1rem;
      border-radius: 8px;
      font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
      font-size: 14px;
      z-index: 9999;
      box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
      overflow-y: auto;
    }

    #info-overlay h2 {
      margin: 0 0 0.5rem;
      font-size: 16px;
      border-bottom: 1px solid rgba(255, 255, 255, 0.2);
      padding-bottom: 0.25rem;
    }

    .section {
      margin-bottom: 0.75rem;
    }

    .headline {
      margin: 0 0 0.25rem;
      line-height: 1.3;
    }

    .timestamp {
      margin-top: 0.5rem;
      font-size: 11px;
      opacity: 0.7;
    }

    /* Simple close button (optional) */
    #overlay-close {
      position: absolute;
      top: 4px;
      right: 6px;
      background: transparent;
      border: none;
      color: #ccc;
      cursor: pointer;
      font-size: 16px;
    }

    #overlay-close:hover {
      color: #fff;
    }
  </style>
</head>
<body>
  <!-- Your normal page content would be here -->

  <!-- Dynamic overlay -->
  <div id="info-overlay">
    <button id="overlay-close" aria-label="Close overlay">×</button>
    <div class="section" id="weather-section">
      <h2>Weather</h2>
      <div id="weather-content">Loading weather…</div>
    </div>

    <div class="section" id="news-section">
      <h2>Headlines</h2>
      <div id="news-content">Loading news…</div>
    </div>

    <div class="timestamp" id="last-updated"></div>
  </div>

  <script>
    const weatherEl = document.getElementById("weather-content");
    const newsEl = document.getElementById("news-content");
    const tsEl = document.getElementById("last-updated");
    const overlay = document.getElementById("info-overlay");
    const closeBtn = document.getElementById("overlay-close");

    closeBtn.addEventListener("click", () => {
      overlay.style.display = "none";
    });

    async function fetchWeather() {
      try {
        // Replace with your real weather endpoint
        const res = await fetch("/api/weather");
        if (!res.ok) throw new Error("Weather request failed");
        const data = await res.json();

        // Adjust this to match your actual JSON structure
        const location = data.location || "Your location";
        const temp = data.tempC != null ? data.tempC + " °C" : "";
        const desc = data.description || "";

        weatherEl.textContent = location + ": " + temp + (desc ? " — " + desc : "");
      } catch (err) {
        console.error(err);
        weatherEl.textContent = "Unable to load weather.";
      }
    }

    async function fetchNews() {
      try {
        // Replace with your real news endpoint
        const res = await fetch("/api/news");
        if (!res.ok) throw new Error("News request failed");
        const data = await res.json();

        // Expecting something like: { headlines: [ { title, url }, ... ] }
        const headlines = Array.isArray(data.headlines) ? data.headlines.slice(0, 5) : [];

        if (!headlines.length) {
          newsEl.textContent = "No headlines available.";
          return;
        }

        // Render simple list of headlines
        newsEl.innerHTML = "";
        headlines.forEach((h) => {
          const p = document.createElement("p");
          p.className = "headline";

          if (h.url) {
            const a = document.createElement("a");
            a.href = h.url;
            a.textContent = h.title || "Untitled";
            a.target = "_blank";
            a.rel = "noopener noreferrer";
            a.style.color = "#7fbfff";
            newsEl.appendChild(p);
            p.appendChild(a);
          } else {
            p.textContent = h.title || "Untitled";
            newsEl.appendChild(p);
          }
        });
      } catch (err) {
        console.error(err);
        newsEl.textContent = "Unable to load news.";
      }
    }

    async function refreshOverlay() {
      await Promise.all([fetchWeather(), fetchNews()]);
      const now = new Date();
      tsEl.textContent = "Last updated: " + now.toLocaleTimeString();
    }

    // Initial load
    refreshOverlay();

    // Refresh every 5 minutes
    setInterval(refreshOverlay, 5 * 60 * 1000);
  </script>
</body>
</html>
```

This gives you a small, dismissible overlay in the top‑right that keeps pulling in fresh weather and headlines. You can easily adapt it to a bottom bar or a ticker‑style strip by changing the CSS.

If you prefer not to deal with APIs at all, you can also embed existing widgets inside an overlay container like this—for example a news ticker widget for \[headlines] ￼ ([https://www.commoninja.com/widgets/news-ticker](https://www.commoninja.com/widgets/news-ticker)) or a configurable \[weather widget panel for current conditions and forecast] ⁠￼ ([https://weatherwidget.io/](https://weatherwidget.io/))—and let them handle the live data while your overlay just positions them.

