Advanced
Client-side watermarks (Labs)

Client-side text watermarks

Use the same video on different brands: show hola.com, adios.com, or buenosdias.com/username inside the player without processing another copy. Your website supplies the text for each player session. Nothing is saved to a project, content record, or watermark profile.

Labs preview, October 2026. Use the Labs injector and a Labs playback token below. This feature has not been released to the production player yet.

1. Embed with your brand

Serve your website over HTTPS (localhost is also supported for development). Set the configuration before loading the injector:

<div id="brand-player" style="width:100%;aspect-ratio:16/9"></div>
<script>
  const container = document.getElementById("brand-player");
  container.setAttribute("data-token", "YOUR_LABS_PLAYBACK_TOKEN");
  container.setAttribute("data-config", JSON.stringify({
    autoPlay: false,
    startMuted: false,
    clientWatermark: {
      version: 1,
      text: "hola.com/usuario123",
      position: "bottomRight",
      margin: { horizontal: 2, vertical: 4, unit: "percent" },
      font: { type: "catalog", name: "inter", weight: 600 },
      color: "#ffffff",
      opacity: 0.6,
      shadow: true
    }
  }));
</script>
<script src="https://player.fastevo-labs.com/player-injector.js"></script>

You provide the exact text; the player does not infer your domain from its iframe URL. For example, your integration can build it from your site's brand and a display username. Use setAttribute with JSON.stringify rather than inserting unescaped user text into HTML. Avoid unnecessary personal data.

The injector delivers the configuration to its own iframe through an origin-checked message channel. When casting with the updated Labs receiver, the player also sends it to that receiver. It is not added to the playback URL or token. Do not add a clientWatermark query parameter to a direct player URL.

2. Choose position and appearance

FieldValuesDefault
version1 (required)—
textNon-empty single-line text, up to 200 Unicode code points (required)—
positiontopLeft, topRight, bottomLeft, bottomRightbottomRight
marginOptional { horizontal, vertical, unit }; see belowAutomatic
size"auto""auto"
colorSix-digit hexadecimal, e.g. "#ffffff""#ffffff"
opacityNumber between 0.1 and 10.6
shadowBooleantrue
fontCatalogue or URL configuration belowSystem font, weight 400

The text scales with the picture, not the black bars. It remains within the selected corner when the player resizes or rotates. Very long text shrinks; if it still does not fit, it is truncated with an ellipsis. Keep usernames short for small screens. Text is literal: no HTML, CSS, markup or templates. Unknown fields and invalid configurations are rejected without replacing the current watermark.

Move the mark inward with margins

Keep a corner as the anchor and configure its horizontal and vertical spacing:

clientWatermark: {
  version: 1,
  text: "hola.com/username",
  position: "bottomRight",
  margin: { horizontal: 2, vertical: 4, unit: "percent" }
}

This moves the mark 2% of the rendered video's width inward from the right and 4% of its height upward from the bottom. The percentages use each axis separately, not the iframe or the screen. For a vertical video inside a landscape player, the anchor and margins stay inside the vertical picture, excluding the player's black bars. Black bars encoded into the video itself are part of the picture and cannot be excluded by this layout calculation.

  • unit: "percent": each axis accepts a number from 0 to 50, including decimals.
  • unit: "px": each axis accepts 0 to 4096 CSS pixels on the rendering device, e.g. margin: { horizontal: 24, vertical: 12, unit: "px" }.
  • All three fields are required when margin is supplied. Numeric strings, negative values, unknown fields and units are rejected; 0 is a real zero inset.
  • Omit margin to preserve the existing automatic spacing: 3% of the shorter rendered side, minimum 4 CSS pixels. Do not send margin: null or "auto".
  • If the requested spacing cannot fit the measured text, the effective inset is reduced to keep it inside the picture. Explicit margins move the anchor; they do not shrink the font just to force a large inset. Very long text can therefore leave less room for the requested margin.

Placement is recalculated after resize, rotation and font loading. The same configuration works on the web and the updated Labs Chromecast receiver; percentages are recommended for proportional spacing across mobile and TV. Apply a complete configuration with setClientWatermark() to change margins without restarting playback. Omit margin in the next call to restore automatic spacing. Stop and reconnect an existing Cast session after updating the player so the receiver also loads the new version.

3. Choose a font

Built-in catalogue

font: { type: "catalog", name: "inter", weight: 600 }

Choose system, inter, or roboto. Weight is an integer from 100 to 900 (default 400). Catalogue fonts support style: "normal" only. Inter and Roboto are served from Fastevo's player assets; system fonts need no download. The browser chooses the available system face/weight for system.

Your own font

font: {
  type: "url",
  url: "https://assets.your-domain.com/fonts/brand.woff2",
  weight: 600,
  style: "normal"
}

Use a browser-supported WOFF2 (recommended), WOFF, TTF or OTF file. The configured weight and style (normal or italic) must match your font. You host it and are responsible for its web-embedding licence.

Your font server must:

  • Serve the font directly over HTTPS, without redirects, cookies or login.
  • Allow CORS from https://player.fastevo-labs.com, the iframe's origin, not just your website's origin. Access-Control-Allow-Origin: * is suitable for a publicly accessible font.
  • Keep the file at or below 2 MiB. Localhost, local-domain names and literal IP addresses are not accepted. Use a public hostname.

The player (or the Chromecast receiver while casting) fetches the font without credentials or a referrer. If you manage the player document's CSP, its connect-src must allow the font origin. The embedding page's policy must separately allow the Labs injector and iframe.

A font never blocks playback. Text appears immediately in a system fallback while the selected font loads. If it loads within five seconds, the player swaps fonts and recalculates placement. A network/CORS error, invalid font, oversized file or timeout leaves the system fallback visible. Up to eight successful font configurations are cached per player instance; additional variants fall back rather than growing memory indefinitely.

4. Change or remove it while playing

Once the container's playerInitialized event fires, use:

const player = window.players["brand-player"];
 
try {
  const state = await player.setClientWatermark({
    version: 1,
    text: "adios.com/another-user",
    position: "topLeft",
    font: { type: "catalog", name: "roboto", weight: 500 }
  });
  // Configuration is applied; a custom font may still be loading.
} catch (error) {
  // Keep your UI consistent if validation or iframe communication fails.
}
 
// Later, without changing the token, media or playback position:
await player.clearClientWatermark();
// Equivalent: await player.setClientWatermark(null);

Each call replaces the complete configuration; omitted optional fields revert to their defaults. Calls queue while the watermark channel connects. The promise resolves when the iframe validates and applies the configuration, not when a font finishes loading. It rejects after 15 seconds without an acknowledgement. If you reload the same iframe, the injector reapplies the last acknowledged configuration. Pending requests reject on reload or removal.

Listen before initialization to capture initial state and configuration errors:

const container = document.getElementById("brand-player");
container.addEventListener("clientWatermarkStateChange", ({ detail }) => {
  // detail: { enabled, visible, reason, fontStatus, warningCode, remote }
  // Display a fallback warning if needed; do not stop the video.
});
container.addEventListener("clientWatermarkError", ({ detail }) => {
  // Initial configuration failed: detail.code.
});

After initialization you can also use player.on("clientWatermarkStateChange", handler). clientWatermarkReady means the watermark channel is connected; it does not mean media is playing. fontStatus is system, loading, loaded or fallback. Events contain state and bounded codes, not your text or font URL.

Chromecast: the same brand on the TV

The updated Labs custom receiver displays the same configuration on the TV automatically. No extra flag, API field, saved profile, transcoding job or Cast application is required. The browser's local mark is hidden during casting. Calling setClientWatermark() or clearClientWatermark() updates the receiver without reloading or seeking the video.

The existing promise still acknowledges local configuration acceptance. For the TV's confirmation, inspect detail.remote in clientWatermarkStateChange:

container.addEventListener("clientWatermarkStateChange", ({ detail }) => {
  const remote = detail.remote;
  if (!remote) return; // No active watermark synchronization with a receiver.
  if (remote.synchronization === "synced") {
    // Receiver confirmed this revision. remote.visible says whether it is
    // currently drawn; fontStatus can still be "loading" or "fallback".
  }
});
remote.synchronizationMeaning
pendingConnecting to the owned receiver or waiting for acknowledgement
syncedReceiver confirmed the revision; inspect enabled, visible, reason, fontStatus, warningCode
unsupportedThe connected receiver does not advertise this feature; stop casting and reconnect to load the updated Labs receiver
errorDelivery could not be confirmed or configuration was rejected; do not assume the TV changed its mark

remote.revision identifies the desired/confirmed configuration revision. Missing acknowledgements are retried at most twice; the video is never stopped or reloaded to recover a watermark. After an error, explicitly reapply the same configuration to retry. Font failure on the TV leaves a visible system fallback and reports its state back to the owning player.

Only the sender that loaded the content may change its mark. Messages from a different sender, source or previous receiver session cannot replace it. Text and font URLs are excluded from the receiver's broadcast media status; acknowledgements are sent only to the owning sender.

Closing the browser does not remove the receiver's mark while that authorized playback session continues. A new video, STOP, playback end, error or receiver shutdown clears it. This is independent of the forensic badge/steganography build setting: displaying brand text does not turn those overlays on.

Physical TV rendering and device-specific font behavior should be checked on your target Cast devices. The Labs demo reports the receiver's acknowledgement separately from local playback, making that check easier.

State reasonMeaning
localVisible on the local video
disabledNo client-side watermark configured
waiting-for-videoWaiting for usable video frames/dimensions
airplay, google-castLocal overlay hidden during remote playback/handoff
native-fullscreen, picture-in-pictureBrowser's video-only surface cannot include the overlay
unsupported-contentThis v1 supports video, not standalone audio or images
media-errorMedia failed; the overlay is hidden

Common rejection codes include WATERMARK_INVALID_CONFIG, WATERMARK_ACK_TIMEOUT, WATERMARK_PLAYER_RELOADED, WATERMARK_DESTROYED and WATERMARK_REQUEST_LIMIT (32 pending calls maximum). Font warnings include WATERMARK_FONT_LOAD_FAILED, WATERMARK_FONT_TIMEOUT, WATERMARK_FONT_TOO_LARGE, WATERMARK_FONT_UNSUPPORTED and WATERMARK_FONT_LIMIT; these do not reject an otherwise accepted configuration.

Scope and limitations

  • Works over local video playback through the Fastevo injector and a playback token. It does not change DRM, forensic marking, the tracking badge, or your existing content access rules. Clear delivery URLs used in your own player are a separate integration: this API does not automatically add an overlay to an external player or a bare media URL.
  • Container fullscreen and the updated Labs Chromecast receiver include the overlay. Native video-only fullscreen (including iPhone), PiP and AirPlay do not include it. The local overlay is hidden during casting and returns with local playback. Casting and DRM policies remain unchanged.
  • It is a visual brand overlay, not tamper-proof protection. Viewers can manipulate browser content. It is not included in downloads or media files.
  • A logo already burned into a source or processed video remains visible. Use a clean original and disable the server-side watermark profile when processing if you need one reusable unbranded video. See server-side profiles and clear delivery without a visible watermark.

Try it in Labs

Open the interactive Labs example (opens in a new tab) and enter a Labs playback token. Try brand text, a username, an external font, an intentionally broken font, and no watermark. Use the Márgenes 2 % / 4 % preset or choose automatic, percentage or pixel spacing and press Aplicar sin reiniciar vídeo. Cast to a Labs-registered device and repeat those cases: the status below the preview switches to TV confirmation. The example can change the configuration without reloading the video. The API, transcoder and existing stored content need no migration for this feature.