# Introduction

3Q Video Player (js3q) is a powerful, modular video player. It features a flexible and comprehensive API that enables easy integration and customisation for a wide range of use cases.

The player supports modern streaming standards such as HLS and MPEG-DASH, offers adaptive bitrate streaming (ABR) for optimal playback quality, and provides comprehensive support for live and on-demand content. In addition, the js3q player can be seamlessly integrated into existing websites, CMS or OTT systems and can be visually adapted to your own corporate design using CSS or configuration parameters.

Other core features include advertising integration (VAST/VPAID/VMAP), analytics support, multilingualism, accessibility and GDPR-compliant use. Thanks to its modular architecture, js3q is suitable for both simple video integrations and complex enterprise streaming scenarios.

{% embed url="<https://playout.3qsdn.com/embed/95ea8442-c3aa-43a3-bfdd-c93f35c6242a>" %}

### Demo <a href="#page_demo" id="page_demo"></a>

<https://player.3qsdn.com/> (Latest)

[https://player-glass.3qsdn.com](https://player-glass.3qsdn.com/) (Glass, Upcoming versions)

### Versioning <a href="#page_versioning" id="page_versioning"></a>

To ensure that your player is always up to date, with the latest features and bug fixes, we recommend always using the latest player version: <https://player.3qsdn.com/js3q.latest.js>. If you wish to use a fixed player version, you may do so by including the specific player version in the path. For example, to access player version 5.4.1:

```
<script src="https://player.3qsdn.com/js3q.5.4.1.js"></script>
```

If you are using a fixed player version, please update the version regularly. By using a fixed version you will not benefit from regular bug fixes. In addition, previous player versions are only guaranteed to be available for 6 months after release. After that point the latest player version will be returned.

**Warning:** Very rarely breaking changes will be made in the player which necessitate an update of the player version. In these cases, the breaking change will be announced in advance on our [Status page.](https://www.3q-status.com/)

### Compatibility <a href="#page_compatibility" id="page_compatibility"></a>

<table><thead><tr><th width="247.46875">Browser</th><th>Supported versions</th></tr></thead><tbody><tr><td>Chrome</td><td>- Always the latest 3 Versions<br>- Chrome for android is supported<br>- Chrome for iOS is supported</td></tr><tr><td>Firefox</td><td>- Always the latest 3 Versions (and the latest ESR Version)<br>- Firefox for android is supported<br>- Firefox for iOS is supported<br>- Firefox Focus is supported</td></tr><tr><td>Safari</td><td>- Supported, starting with Safari 14<br>- Safari for iOS is supported</td></tr><tr><td>Edge</td><td>- Always the latest 3 versions (and the last EdgeHTML version)<br>- Edge for android is supported<br>- Edge for iOS is supported</td></tr><tr><td>Other browsers</td><td>- Opera (latest 3 versions)<br>- Samsung browser (latest 3 versions)<br>- Huawei browser (latest 3 versions)</td></tr><tr><td>Special Environments</td><td>- <a href="https://amp.dev/documentation/components/amp-3q-player">Google AMP</a> (via Plugin and iFrame)<br>- Facebook Instant Articles (iFrame)</td></tr></tbody></table>

## Disclaimer <a href="#page_disclaimer" id="page_disclaimer"></a>

Copyright (C) 2009 - 2026, 3Q GmbH, Munich, Europe, All Rights Reserved. This source code and its use and distribution, is subject to the terms and conditions of the applicable license agreement. More information on [www.3q.video](http://www.3q.video/)


# Basic Usage

This page describes how to integrate the 3Q video player js3q via our JavaScript API.

### Embed the player <a href="#page_generate_player_token" id="page_generate_player_token"></a>

Import the library anywhere on your page.

```html
<script src="https://player.3qsdn.com/js3q.latest.js"></script>
```

Now embed the player with a div at the position you’d like it to appear in your HTML page and use the js3q class to create a player instance.

```html
<div id="my-player"></div>
<script>
   const player = new js3q({
      playoutId: '5c3b0910-8850-11e7-9273-002590c750be',
      container: 'my-player',
      key: {key}, // Optional: Needed if player is protected
      timestamp: {timestamp} // Optional: expire date
   })
</script>
```

Please note that `key` and `timestamp` (expire date) are only required if token protection is active.

### Player Protection

If token protection is active in the player, you’ll need to generate a token and add it to the embed code to load the player.

{% hint style="info" %}
Do not generate tokens in the browser, as this would expose your private key. The `timestamp` parameter defines the key’s expiration time. It should be set to 5-10 seconds in the future and is only required when initializing the player.
{% endhint %}

#### **Example PHP Code**

```php
$_project_id = 'project id';
$_project_key = 'project_secret';

// Timestamp is the Expire Date.
// You'll find the private key in the project settings.
$timestamp = new \DateTime('now');
$timestamp->modify('+10 seconds');
$timestamp = $timestamp->getTimestamp();

$key = md5($_project_id.$_project_key.$timestamp);
```

#### Example Python Code

```python
from hashlib import md5

project_id = 'project id'
# You'll find the private key in the project settings.
project_key = 'project_secret'

// Expire date
timestamp = int(time.time()) + 10
key = md5('{}{}{}'.format(project_id, project_key, timestamp).encode('utf-8')).hexdigest()
```

After the variables `key` and `timestamp` have been generated, you have to insert them in the player configuration as shown in the following section.

## Working with the player instance <a href="#page_working_with_the_player_instance" id="page_working_with_the_player_instance"></a>

#### Methods <a href="#page_methods" id="page_methods"></a>

You can just use any of the available methods immediately after the player is initialized. For example, `player.seek(10);` seeks to 10 seconds. See [Methods ](/player-web-sdk/methods)for a full list of available methods.

#### Events <a href="#page_events" id="page_events"></a>

You can easily listen to events either by specifying an event type, using `*.*` for all events, or by grouping `media.*`. The following snippet receives all media related events.

```javascript
player.on('media.*', function (data) {
    console.log('3Q Player', this.event, data)
});
```

See [Events ](/player-web-sdk/events)for a full list of all supported events.


# Configuration

When initialising the 3Q video player via a constructor, it is possible to include multiple configuration attributes. While `playoutId` and `container` (and in case of token protection `key` and `timestamp`) are mandatory, all other options are optional.

```html
<script>
const player = new js3q({
    playoutId: '5c3b0910-8850-11e7-9273-002590c750be',
    container: 'player',
    autoplay: true
});
</script>
```

## Parameters <a href="#page_parameters" id="page_parameters"></a>

If the player is protected, you need to generate and configure `key` and `timestamp`, as described in [Basic Usage](/player-web-sdk/basic-usage).

### **General**

<table><thead><tr><th width="180.4765625">Parameter</th><th width="112.98828125">Type</th><th width="112.89453125">Default</th><th>Description</th></tr></thead><tbody><tr><td><strong>playoutId</strong></td><td>String</td><td>-</td><td>Video or Live-stream</td></tr><tr><td><strong>container</strong></td><td>String</td><td>-</td><td>The container where the player is placed.</td></tr><tr><td>key</td><td>String</td><td>-</td><td>Needed if player is protected</td></tr><tr><td>timestamp</td><td>Timestamp</td><td>-</td><td>Needed if player is protected. </td></tr><tr><td>autoplay</td><td>Boolean</td><td>false</td><td></td></tr><tr><td>allowmutedautoplay</td><td>Boolean</td><td>false</td><td>If autoplay is true and autoplay is not working, the player tries to autoplay the video muted.</td></tr><tr><td>chromecast</td><td>Boolean</td><td>false</td><td>Enables Remote playback</td></tr><tr><td>enabledTextTracks</td><td>Boolean</td><td>true</td><td>Enables subtitles if available</td></tr><tr><td>resumeAt</td><td>Integer</td><td>0</td><td>Resumes playback at given time.</td></tr><tr><td>initialQuality</td><td>Integer</td><td>360</td><td>The initial qualitiy level. Choose between 1080, 720, 480, 360, 240 and 144. The higher the value, the longer the video needs to cache before it can begin playing. If you choose a low value, the the video will start playing faster, but begin with a lower initial quality level. After the first few seconds playback quality will adapt automatically to the user's available bandwidth.</td></tr><tr><td>playbackRate</td><td>Number</td><td>1</td><td>Value of playback rate. Values 0-1 play slower than real time. Values greater than 1 play faster than real time</td></tr><tr><td>playbackRateMenu</td><td>Boolean</td><td>false</td><td>Show playbackRate menu</td></tr><tr><td>loop</td><td>Boolean</td><td>false</td><td></td></tr><tr><td>muted</td><td>Boolean</td><td>false</td><td></td></tr><tr><td>seo</td><td>Boolean</td><td>false</td><td>JSON-LD Data placement</td></tr><tr><td>preventNativeFullScreenOnIOS</td><td>Boolean</td><td>false</td><td>Prevent the player from switching to native mode on iOS</td></tr><tr><td>preview</td><td>Boolean</td><td>false</td><td>Limits the duration of the Video</td></tr><tr><td>previewtime</td><td>Number</td><td>30</td><td>Sets the duration of the preview</td></tr><tr><td>pictureInPicture</td><td>Boolean</td><td>false</td><td>Enables Picture in Picture</td></tr><tr><td>fullscreenOnOrientationChange</td><td>Boolean</td><td>false</td><td>Mobile only. Automatically switch to fullscreen, when the device gets rotated horizontally</td></tr><tr><td>sticky</td><td>Boolean</td><td>false</td><td>Sticks the player to the screen while Scrolling. Disables PictureInPicture Option.</td></tr><tr><td>stickyPosition</td><td>String</td><td>top-right</td><td>['top-left','top-right','bottom-left','bottom-right']</td></tr><tr><td>suppressFullscreen</td><td>Boolean</td><td>false</td><td>Hide the Fullscreen button and disable all ways to switch to fullscreen</td></tr><tr><td>polls</td><td>Boolean</td><td>true</td><td>Activate/Deaktivate live polls</td></tr></tbody></table>

### **Layout / Dimensions**

<table><thead><tr><th width="180.55859375">Parameter</th><th width="118.4296875">Type</th><th>Default</th><th>Description</th></tr></thead><tbody><tr><td>layout</td><td>String</td><td>responsive</td><td>[responsive|default|fixed]</td></tr><tr><td>width</td><td>String</td><td>-</td><td>[px|100%]</td></tr><tr><td>height</td><td>String</td><td>-</td><td>[px|100%]</td></tr><tr><td>tintColor</td><td>String</td><td>#009cd1</td><td></td></tr><tr><td>controlsPriority</td><td>array</td><td>['fullscreen', 'captions', 'settings', 'skip', 'volume', 'playpause', 'playlist', 'cast', 'time', 'chapters']</td><td>Priority of Controls in order to keep on smaller screens</td></tr><tr><td>cornerShape</td><td>String</td><td>squared</td><td>Border radius of elements within the player</td></tr><tr><td>playerShape</td><td>string</td><td>squared</td><td>Border radius of the player </td></tr></tbody></table>

### **Tracking / Analytics**

<table><thead><tr><th width="184.25">Parameter</th><th>Type</th><th>Default</th><th>Description</th></tr></thead><tbody><tr><td>tracking</td><td>Boolean</td><td>true</td><td>Video Engagemenent Tracking</td></tr><tr><td>trackingCookie</td><td>Boolean</td><td>true</td><td>Save the Tracking ID in the Cookies</td></tr><tr><td>trackingApi</td><td>String</td><td>v2</td><td>v1 (Reporting) | v2 (Analytics)</td></tr><tr><td>userToken</td><td>String</td><td>Custom String to identify the User</td><td></td></tr><tr><td>ga</td><td>Boolean</td><td>false</td><td>Google Analytics Tracking</td></tr><tr><td>gacallback</td><td>String</td><td>ga</td><td></td></tr></tbody></table>

### **Language and locales**

<table><thead><tr><th width="185.48046875">Parameter</th><th>Type</th><th>Default</th><th>Description</th></tr></thead><tbody><tr><td>locale</td><td>String</td><td>navigator.language</td><td>Player control navigation language. Available values: "de", "en", "es", "fr, "it", "nl"</td></tr><tr><td>defaultAudioLanguage</td><td>String</td><td>navigator.language</td><td></td></tr><tr><td>defaultCC</td><td>String</td><td>-</td><td>If available (e.g. "ger"), the set subtitle will be displayed without user interaction.</td></tr><tr><td>CCSize</td><td>Number</td><td>-</td><td>Default Size of Caoptions in px</td></tr></tbody></table>

### **Labels**

| Parameter | Type   | Description                                                      |
| --------- | ------ | ---------------------------------------------------------------- |
| labels    | Object | The language strings, that will be overwritten. See full example |

### **Controls**

| Parameter          | Type    | Default | Description |
| ------------------ | ------- | ------- | ----------- |
| controls           | Boolean | true    |             |
| controlBarAutoHide | Boolean | true    |             |

### **Advertising**

See chapter [Ad Integration](/player-web-sdk/ad-integration).

### **Sharing**

| Parameter     | Type    | Default | Description                                                                                                                                                                                  |
| ------------- | ------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| embedCodeMenu | Boolean | false   | Enable Embed-Code for Sharing                                                                                                                                                                |
| sharing       | Boolean | false   |                                                                                                                                                                                              |
| shareItems    | Array   | \['']   | <p>Available Items:  'mail',<br>'facebook',<br>'twitter',<br>'threads',<br>'bluesky',<br>'reddit',<br>'whatsapp',<br>'linkedin',<br>'xing',<br>'messenger',<br>'telegram',<br>'mastodon'</p> |


# Methods

To call a method or to receive information from the player, just use the player instance to call them.

```javascript
// Call the play method
player.play()
```

## Available Methods

### **Player controls**

| Method          | Type   | Description        |
| --------------- | ------ | ------------------ |
| fullscreen      | -      |                    |
| exitfullscreen  | -      |                    |
| enableControls  | -      |                    |
| disableControls | -      |                    |
| destroy         | -      |                    |
| getVersion      | -      |                    |
| load            | string | Loads a new dataid |

### **Media controls**

| Method                | Type              | Description                                                   |
| --------------------- | ----------------- | ------------------------------------------------------------- |
| play                  | -                 |                                                               |
| pause                 | -                 |                                                               |
| unmute                | -                 |                                                               |
| mute                  | -                 |                                                               |
| volume                | Integer \[0.1..1] | Sets the volume. E.g. for 50% volume use 0.5                  |
| seek                  | Integer           |                                                               |
| getVideoInfo          |                   |                                                               |
| getCurrentTime        |                   |                                                               |
| getDuration           |                   |                                                               |
| getStreamType         |                   |                                                               |
| getLiveDelayInSeconds |                   |                                                               |
| getProcessingStatus   | Boolan            | VoD only: method to check, if the video is already transcoded |

### **Subtitles / Captions**

| Method       | Type    | Description |
| ------------ | ------- | ----------- |
| getSubtitles | Object  |             |
| subtitle     | Integer |             |

### **AudioTracks**

| Method         | Type    | Description |
| -------------- | ------- | ----------- |
| getAudioTracks | Object  |             |
| audiotrack     | Integer |             |

### **Ads**

| Method    | Type   | Description                                                                                                                    |
| --------- | ------ | ------------------------------------------------------------------------------------------------------------------------------ |
| requestAd | String | Triggers a midroll. Pre- and PostRolls will still be called anyway. requestAd() or requestAd(url) if specifying an ad tag url. |

### **Sidebar**

| Method         | Type   | Description |
| -------------- | ------ | ----------- |
| setSideBarHTML | String |             |
| showSideBar    |        |             |
| showSideBar    |        |             |

### Playlists

| Method           | Type   | Description                                       |
| ---------------- | ------ | ------------------------------------------------- |
| next             |        | Plays the next item in the playlist               |
| previous         |        | Plays the previous item in the playlist           |
| getPlaylistItems |        | Retrieves a list of all playlist items            |
| PlaylistItem     | Number | Sets the current active Playlist Item to given ID |


# Events

You can listen to **all player events**, **groups of events**, or **individual events** to react to user interaction, playback state changes, ads, and UI behavior.

Events can be subscribed to using:

* **`.on(event, callback)`** – listens to an event continuously and is triggered every time the event occurs.
* **`.once(event, callback)`** – listens to an event only once and is automatically removed after the first trigger.

You can subscribe to:

* all events (e.g. `*.*`),
* a specific event namespace (e.g. `media.*`, `player.*`),
* or a single event (e.g. `media.ready`).

**Examples**

```javascript
// Listen to all events (not recommended)
player.on('*.*', function (data) {
    console.log('js3q::' + this.event + ' ' + JSON.stringify(data));
});

// Listen to all media events
player.on('media.*', function (data) {
    console.log('js3q::' + this.event + ' ' + JSON.stringify(data));
});

// Listen to a specific event only once
player.once('media.ready', function (data) {
    console.log('js3q::' + this.event + ' ' + JSON.stringify(data));
});
```

### Player Events

| Event                      | Description                       |
| -------------------------- | --------------------------------- |
| `player.init`              | Player initialization started     |
| `player.ready`             | Player is ready                   |
| `player.uiready`           | Player UI is ready                |
| `player.fullscreenChange`  | Player entered or left fullscreen |
| `player.controllbarChange` | Control bar state changed         |
| `player.playRequest`       | User requested playback           |
| `player.pauseRequest`      | User requested pause              |
| `player.seekRequest`       | User requested seek               |
| `player.bufferingChange`   | Buffering state changed           |
| `player.displayClickEvent` | Click on player surface           |
| `player.menusOpenRequest`  | Open menu requested               |
| `player.menusCloseRequest` | Close menu requested              |
| `player.playlistShow`      | Playlist opened                   |
| `player.playlistHide`      | Playlist closed                   |
| `player.destroy`           | Player destroyed                  |

***

### Configuration Events

| Event                     | Description                       |
| ------------------------- | --------------------------------- |
| `configuration.loaded`    | Configuration loaded successfully |
| `configuration.changed`   | Configuration changed             |
| `configuration.cssLoaded` | Configuration CSS loaded          |
| `configuration.loadError` | Configuration failed to load      |

### Media Events

| Event                       | Description              |
| --------------------------- | ------------------------ |
| `media.init`                | Media initialized        |
| `media.loading`             | Media loading            |
| `media.ready`               | Media ready              |
| `media.playing`             | Media playback started   |
| `media.paused`              | Media playback paused    |
| `media.complete`            | Media playback completed |
| `media.seeking`             | Seek started             |
| `media.seeked`              | Seek completed           |
| `media.timeChange`          | Playback time changed    |
| `media.durationChange`      | Media duration changed   |
| `media.volumeChange`        | Volume changed           |
| `media.muted`               | Media muted              |
| `media.unmuted`             | Media unmuted            |
| `media.subtitleChange`      | Subtitle changed         |
| `media.subtitleTrackChange` | Subtitle track changed   |
| `media.audioChange`         | Audio track changed      |
| `media.dvrChange`           | DVR window changed       |
| `media.switchSource`        | Media source switched    |
| `media.backToLiveRequest`   | Back to live requested   |
| `media.mediaNotFound`       | Media not found          |
| `media.autoplay`            | Autoplay succeeded       |
| `media.autoplayfailed`      | Autoplay failed          |
| `media.autoplaymuted`       | Autoplay started muted   |
| `media.destroy`             | Media destroyed          |

### Ad Events

| Event                      | Description                |
| -------------------------- | -------------------------- |
| `ads.initEvent`            | Ad system initialized      |
| `ads.loadError`            | Ad loading failed          |
| `ads.adRequest`            | Ad requested               |
| `ads.adLoaded`             | Ad loaded                  |
| `ads.adPlaying`            | Ad started                 |
| `ads.adPaused`             | Ad paused                  |
| `ads.skipped`              | Ad skipped                 |
| `ads.skippableChange`      | Ad skippable state changed |
| `ads.adClick`              | Ad clicked                 |
| `ads.adError`              | Ad error occurred          |
| `ads.adComplete`           | Ad finished                |
| `ads.contentPauseRequest`  | Content paused for ad      |
| `ads.contentResumeRequest` | Content resumed after ad   |
| `ads.completeEvent`        | All ads completed          |

### Casting Events

| Event          | Description     |
| -------------- | --------------- |
| `cast.started` | Casting started |
| `cast.resumed` | Casting resumed |
| `cast.stopped` | Casting stopped |

### Call-to-Action (CTA) Events

| Event            | Description   |
| ---------------- | ------------- |
| `cta.displaying` | CTA displayed |
| `cta.closed`     | CTA closed    |

### UI & Control Events

| Event                    | Description        |
| ------------------------ | ------------------ |
| `controls.hover`         | Controls hovered   |
| `controls.seekBarExpand` | Seek bar expanded  |
| `controls.showQoS`       | QoS panel shown    |
| `controls.hideQoS`       | QoS panel hidden   |
| `sidebar.toggle`         | Sidebar toggled    |
| `transcript.toggle`      | Transcript toggled |
| `thumbnails.toggle`      | Thumbnails toggled |
| `download.toggle`        | Download toggled   |

### Sticky Player Events

| Event                       | Description            |
| --------------------------- | ---------------------- |
| `sticky.change`             | Sticky mode changed    |
| `sticky.stickyCloseRequest` | Sticky close requested |


# Labels

You can overwrite any of the player labels to fully customize the video player text.

Label localization is based on **ISO language codes** such as `en`, `de`, `fr`.\
**Region-specific variants are not supported** — only the base language code is used.

This ensures consistent and predictable labeling across all platforms.

```javascript
const player = new js3q({
  dataid: '5c3b0910-8850-11e7-9273-002590c750be',
  container: 'player',
  labels: {
    en: {
    quality: 'Video Quality',
    playbackrate: 'Playbackrate',
    subtitles: 'Subtitles',
    auto: 'Auto',
    off: 'Off',
    chapters: 'Chapters',
    playbackerror:
      '<b>Error</b><br/><br/>An playback error occurred, please try again or try another browser.',
    drmerror:
      '<b>Error</b><br/><br/>This video requires DRM, please use a different Browser.',
    close: 'Dismiss',
    help: 'Problems? Give Feedback',
    stats: 'Show Debug info',
    debugCopy: 'Copy Debug info',
    licenseInformations: 'License informations',
    ad: 'Ad',
    adendsin: ' ends in ',
    skipin: 'Skip this ad in ',
    skip: ' Skip Ad',
    live: 'Live',
    backtolive: ' Back to Live',
    audiotracks: ' Audio Tracks',
    streamoffline: 'Livestream currently offline',
    streamnotavailable: 'Livestream currently not available',
    resume: 'Resume Video',
    size: 'Size',
    currentPosition: 'Now playing',
    bug: {
      description: 'Select all that applies',
      message: 'More details? (optional)',
      submit: 'Submit',
      video: 'Video problems',
      audio: 'Audio problems',
      network: 'Network problems oder buffering',
      submitmessage:
        'Thank you for your feedback. We will look at your data and make improvements to your operating system or browser if necessary.<br/><br/> At our <a href="https://3q.video/de/player-about" target="_blank">Support page</a>, you`ll find tips on how to solve play issues.',
    },
    tips: {
      play: 'Play',
      pause: 'Pause',
      volume: 'Unmute',
      mutevolume: 'Mute',
      fullscreen: 'Fullscreen',
      exitfullscreen: 'Exit Fullscreen',
      cast: 'Cast on',
      airplay: 'Cast with Airplay',
      backtolive: 'Switch back to Livestream',
      chapter: 'Chapters',
      close: 'Dismiss',
      settings: 'Settings',
      settingsClose: 'Close settings',
      subtitles: 'Subtitles',
      transcript: 'Transcript',
      transcriptLang : 'Choose language',
      transcriptSearchPlaceholder: 'Search transcript',
      pip: 'Picture in Picture',
      back: "Zur&uuml;ck"
    },
    config: {
      fetchError: 'Error loading configuration file',
      forbiddenError: 'No authorization',
      goneError: 'Key has expired',
      serverError: 'Server error',
      notFoundError: 'The video is not available',
      notPublished: 'The video is not published',
      geoRestriction: 'The content is not available in your country.',
    },
    http: {
      fetchError: 'Error loading content',
      forbiddenError: 'No authorization',
      goneError: 'Key has expired',
      serverError: 'Server error',
      notFoundError: 'The content is not available',
      notPublished: 'The content is not published',
      geoRestriction: 'The content is not available in your country.',
    },
    playlist: {
      label: 'Playlist',
      next: 'Next video in',
      playnext: 'Play next',
    playback: 'Back',
    of: 'of',
    now: 'Now playing:',
    history: 'Latest',
    listLabel: 'Show Playlist',
    open: 'Hide Playlist',
    multichannelButton: 'Alternate channels',
    searchPlaceholder: 'Search',
    allCategories: 'All Categories',
    },
    podcast: {
    listLabel: 'Show Podcast',
    open: 'Hide Podcast',
    },
    cta: {
    skip: 'Resume video',
    },
    quizzing: {
    answer: 'Answer',
    answeragain: 'Answer again',
    showanswer: 'Show answers',
    skip: 'Resume to video',
    },
    poll: {
    label: 'Poll',
    commit: 'Thanks for voting.',
    },
    casting: {
    playson: 'Casting on ',
    },
    wall: {
    prelive: 'Watch our livestream here',
    postlive: 'Our Livestream has ended.',
    postlivemessage: 'Thanks for watching',
    },
    comments: {
    headline: 'Comments',
    name: 'Name',
    comment: 'Enter your comment here',
    send: 'Send',
    thanks: 'Thank you for your comment',
    ariaName: 'Your name',
    ariaComment: 'Your comment',
    ariaButton: 'Send comment',
    },
    passwordProtection: {
    wrongpassword: 'Password is wrong, please try again.',
    enterpassword: 'Enter password',
    },
    countdown: {
    days: 'Days',
    hours: 'Hours',
    minutes: 'Minutes',
    seconds: 'Seconds',
    },
    livestatus: {
    addToCalendar: 'add to calendar',
    startingSoon: 'The stream is starting soon!',
    },
    processing: 'Video is being processed',
    embedMenu: 'Embed-Code',
    transcript: 'Transcript',
    reactions: 'React',
    pip: 'Picture in Picture',
    recommendations: 'Recommendations',
    recommendationsCountdownText: 'Next video starts in'
    },
    de: {
    // Same structure as above; different language strings
    }
  }
})
```


# Themes

With our templating framework you can create your own custom player designs. This feature is available with player version v5.1 and above.

```javascript
new js3q({
    dataid: '5c3b0910-8850-11e7-9273-002590c750be',
    container: 'player',
    template: {
        htmlFile: '*httpPathToFile*',
        // or inline:
        // html: `<div class="myCustomDiv"> ... </div>`,
        cssFile: '*httpPathToFile*',
        // or inline:
        // css: `.myCustomDiv { color: red; ... }`,
    }
});
```

A player template is comprised of a HTML and CSS definition of the player elements. To use a player template, you can provide links to these files hosted on your sever using the parameters `htmlFile` and `cssFile`. If you choose to host the files yourself, please check your `CORS` settings.

Alternatively you can post the markup inline using the config parameters `html` and `css`.

Hint: If you use inline Markup, we recommend using string literals (`` ` ``) instead of quotes ( `"`, `'`), to avoid issues with line breaks.

Finally, it is also possible to upload the files directly to a player using our GUI.

### **Template Options**

The following additional options are available when configuring a playlist

| Value            | type    | Description                                      | Default |
| ---------------- | ------- | ------------------------------------------------ | ------- |
| htmlFile         | string  | URl of the template. Be aware of CORS            |         |
| html             | string  | inline Markup                                    |         |
| cssFile          | string  | URL of the CSS                                   |         |
| css              | string  | inline CSS                                       |         |
| audioPoster      | boolean | For hiding the cover in the audio template       | true    |
| audioDescription | boolean | For hiding the description in the audio template | true    |

### **Syntax**

You can add a `class` attribute to each container to define the CSS. The `data-role` attribute is used by our engine to map the button role. You can find a complete list of all buttons below.

### **Base Template (Video)**

```html
<div data-role="sdn-player">
  <div data-role="sdn-display">
    <div data-role="sdn-stats"></div>
    <div data-role="motion-poster"></div>
    <div data-role="sdn-wall"></div>
    <div data-role="display-ad"></div>
    <div data-role="adskip-button"> {{skip}} </div>
    <div data-role="sdn-unmute-button"></div>

    [[context]]

    <div data-role="channel-label"></div>
    <div data-role="title"></div>

    <video
      data-role="source-container"
      x-webkit-airplay="allow"
      webkit-playsinline="true"
      playsinline="true"
      src=""
    ></video>

    <div data-role="play-button-overlay">
      <div data-role="play-button-overlay-span"></div>
    </div>

    <div data-role="play-buffer-overlay">
      <div data-role="play-buffer-overlay-spinner"></div>
    </div>

    <div data-role="player-controls">
      [[seekbar]] [[controls]]
      <div data-role="thumbnails"></div>
    </div>

    [[watermark]]

    <div data-role="gradiant"></div>
    <div data-role="gradiant-top"></div>

    [[qosmenu]]

    <div data-role="top-chrome">
      <div data-role="share-menu">
        <div data-role="sidebar-button"></div>
      </div>
    </div>

    <div data-role="sidebar"></div>

    [[infobar]]

    <div data-role="playlist"></div>
  </div>
</div>
```

### **Roles and Classes**

| data-role                    | default css class                                      | required                       |
| ---------------------------- | ------------------------------------------------------ | ------------------------------ |
| sdn-player                   | `.js3q-player`                                         | ✅                              |
| sdn-audio-player             | `.js3q-player.js3q-audio-player`                       | (as alternative to sdn-player) |
| sdn-display                  | `.sdn-display`                                         | ✅                              |
| sdn-stats                    | `.sdn-stats`                                           |                                |
| motion-poster                | `.sdn-motion-poster`                                   |                                |
| display-ad                   | `.sdn-display-ad`                                      | ✅                              |
| adskip-button                | `.sdn-display-adskipbutton`                            |                                |
| title                        | `.sdn-title`                                           | ✅                              |
| sdn-wall                     | `.sdn-wall`                                            |                                |
| sdn-unmute-button            | `.sdn-display-ad`                                      |                                |
| context                      | `.sdn-context-menu`                                    |                                |
| channel-label                | `.sdn-channel-label`                                   | ✅                              |
| source-container             | `.sdn-source-element`                                  |                                |
| play-button-overlay          | `.sdn-play-overlay`                                    |                                |
| play-button-overlay-span     | `.sdnicbsun-play.none-shadow`                          |                                |
| play-minus15-overlay         | `.sdn-minus15-overlay`                                 |                                |
| play-minus15-overlay-button  | `.ssdnicbsun-minus15.none-shadow`                      |                                |
| play-plus15-overlay          | `.sdn-plus15-overlay`                                  |                                |
| play-plus15-overlay-button   | `.ssdnicbsub-plus15.none-shadow`                       |                                |
| play-buffer-overlay          | `.sdn-buffering`                                       |                                |
| play-buffer-overlay-spinner  | `.sdn-spinner`                                         |                                |
| player-controls              | `.sdn-player-controls`                                 |                                |
| seek-bar                     | `.sdn-time-seekbar`                                    |                                |
| scrubberbar                  | `.sdn-time-scrubber`                                   |                                |
| scrubber-loaded              | `.sdn-time-loaded`                                     |                                |
| scrubber-loaded-pointer      | `.sdn-time-loaded-pointer`                             |                                |
| scrubberdragger              | `.sdn-time-playahead`                                  |                                |
| scrubber-playahead           | `.scrubbBarDragger`                                    |                                |
| chrome                       | `.sdn-player-chrome`                                   |                                |
| pause-button                 | `.sdn-button.sdn-play-button.sdnicbsun-play`           |                                |
| play-button                  | `.sdn-button.sdn-play-button.sdnicbsun-pause`          |                                |
| back-button                  | `.sdn-button.sdn-play-button.sdnicbsun-back`           |                                |
| play-minus10-button          | `.sdn-button.sdn-play-button.sdnicbsun-minus10`        |                                |
| play-plus10-button           | `.sdn-button.sdn-play-button.sdnicbsun-plus10`         |                                |
| play-plus15-button           | `.sdn-button.sdn-play-button.sdnicbsun-plus15`         |                                |
| play-minus15-button          | `.sdn-button.sdn-play-button.sdnicbsun-minus15`        |                                |
| volume-button                | `.sdn-button.sdnicbsun-volume3wave`                    |                                |
| volume-display-wrapper       | `.sdn-volume-wrapper`                                  |                                |
| volume-display               | `.sdn-volume-slider`                                   |                                |
| volume-controls-marker       | `.sdn-volume-marker`                                   |                                |
| volume-controls-thumb        | `.sdn-volume-thumb`                                    |                                |
| timeleft-display             | `.sdn-button.sdn-time-left`                            |                                |
| timeleft-span                |                                                        |                                |
| enter-button                 | `.sdn-button-right.sdnicbsun-fullscreen`               |                                |
| exit-button                  | `.sdn-button-right.sdnicbsun-exitfullscreen`           |                                |
| qos-button                   | `.sdn-button-right.sdn-audioonly-hidden.sdnicbsun-cog` |                                |
| cast-button                  | `.sdn-button-right.sdnicbsun-cast`                     |                                |
| airplay-button               | `.sdn-button-right.sdnicbsun-airplay`                  |                                |
| thumbnails                   | `.sdn-thumbnails`                                      |                                |
| gradient                     | `.sdn-player-gradiant`                                 |                                |
| gradient-top                 | `.sdn-player-gradiant-top`                             |                                |
| qos-menu                     | `.sdn-qos-menu`                                        |                                |
| qos-menu-header-close-button | `.sdn-button-right.sdnicbsun-close.sdn-hidden`         |                                |
| qos-menu-wrapper             | `.sdn-qos-menu-wrapper`                                |                                |
| qs-playbackrate-settings     | `.sdn-quality-settings`                                |                                |
| qs-playbackrate-menu         | `.sdn-ul-menu`                                         |                                |
| qs-quality-settings          | `.sdn-quality-settings`                                |                                |
| qs-audio-settings            | `.sdn-audio-settings`                                  |                                |
| qs-cc-settings               | `.sdn-audio-settings`                                  |                                |
| qs-audio-menu                | `.sdn-ul-menu`                                         |                                |
| top-chrome                   | `.sdn-top-chrome`                                      |                                |
| share-menu                   | `.sdn-share-menu`                                      |                                |
| sidebar-button               | `.sdn-button-right.sdnicbsun-info.sdn-hide`            |                                |
| playlist                     | `.sdn-playlist`                                        |                                |
| infobar                      | `.sdn-infobar.sdn-hide`                                |                                |
| sidebar                      | `.sdn-sidebar`                                         |                                |
| watermark                    | `.sdn-watermark`                                       |                                |
| watermark-picture            | `.sdn-watermark-picture`                               |                                |

### **Labels/Language Strings**

For language strings in the template, you can use the same fields as described [here](/player-web-sdk/labels). To embed them, place the field name between curly brackets. (e.g. `{{tips.play}}`)

Not only you can use the built-in language strings, you can also define your very own ones:

```javascript
new js3q({
  //...
  labels: {
    de: {
      'my-special-language-string': 'Lorem Ipsum',
    },
    en: {
      'my-special-language-string': 'dolor sit amet',
    },
  },
  template: {
    html: `<div>
      {{my-special-language-string}}

      <div data-role="title"></div>
      <!-- ... -->
    </div>`,
  },
})
```

### **Components**

Several components are separated for easier maintenance. You can override each component as described above, or you can create your own components.

To embed a component, place it between square brackets. (e.g. `[[context]]`)

```javascript
new js3q({
  //...
  template: {
    //...
    components: {
      audioPoster: {
        html: `<div class="outer" style="padding: 20px; background: lime;">
                <div class="inner">
                  <div data-role="audio-poster"></div>
                </div>
              </div>`,
        css: `
        .outer {
          /* Beware, that it is not possible to style the root element here */
          /* You need to style it inline, as shown above */
        }

        .inner {
          background: yellow;
          padding: 20px;
        }

        .sdn-ellipsis {
          display: none !important; // Does not affect the rest of the player, just works for the component
        }
      `,
      },
    },
  },
})
```

### **\[\[context]]**

The right-click menu

#### **Markup**

```html
<div data-role="context">
  <ul data-role="context-ul">
    <li data-role="context-li">
      <b>3Q</b> Videoplayer v${__PLAYER_VERSION__}
    </li>
    <li data-role="context-li-third">{{stats}}</li>
  </ul>
</div>
```

The cog menu for quality- and language-settings.

#### **Markup**

```html
<div data-role="qos-menu">
  <h2 data-role="qos-menu-header">
    {{tips.settings}}
    <button
      tooltip-position="left"
      tooltip="{{tips.settingsClose}}"
      data-role="qos-menu-header-close-button"
    ></button>
  </h2>

  <div data-role="qos-menu-wrapper">
    <div data-role="qs-playbackrate-settings">
      <h3>{{playbackrate}}</h3>
      <ul data-role="qs-playbackrate-menu"></ul>
    </div>

    <div data-role="qs-quality-settings">
      <h3>{{quality}}</h3>
      <ul class="sdn-ul-menu">
        <li>
          <span class="current">{{auto}}</span>
        </li>
      </ul>
    </div>

    <div data-role="qs-audio-settings">
      <h3>{{audiotracks}}</h3>
      <ul data-role="qs-audio-menu"></ul>
    </div>

    <div data-role="qs-cc-settings"></div>
  </div>
</div>
```

### **\[\[seekbar]]**

The seekbar which indicates the progress of the content

#### **Markup**

```html
<div data-role="seek-bar">
  <div data-role="scrubberbar"></div>
  <div data-role="scrubber-loaded">
    <div data-role="scrubber-loaded-pointer"></div>
  </div>
  <div data-role="scrubberdragger"></div>
  <div data-role="scrubber-playahead"></div>
</div>
```

### **\[\[videoControls]]**

The control bar for video content

#### **Markup**

```html
<div data-role="chrome">
  <button
    data-role="pause-button"
    tooltip="{{tips.play}}"
    tooltip-position="left"
  ></button>
  <button
    data-role="play-button"
    tooltip="{{tips.pause}}"
    tooltip-position="left"
  ></button>
  <button
    data-role="back-button"
    tooltip="{{playlist.playback}}"
    tooltip-position="left"
  ></button>
  <button
    data-role="next-button"
    tooltip="{{playlist.playnext}}"
    tooltip-position="left"
  ></button>
  <button data-role="play-minus10-button"></button>
  <button data-role="play-plus10-button"></button>
  <button data-role="play-minus15-button"></button>
  <button data-role="play-plus15-button"></button>
  <button
    data-role="volume-button"
    tooltip="{{tips.mutevolume}}"
    tooltip-position="left"
  ></button>

  <div data-role="volume-display-wrapper">
    <div data-role="volume-display">
      <div data-role="volume-controls-marker">
        <span data-role="volume-controls-thumb"></span>
      </div>
    </div>
  </div>

  <button data-role="timeleft-display">
    <span data-role="timeleft-span">
      ● Live
    </span>
  </div>

  <button
    data-role="enter-button"
    tooltip="{{tips.fullscreen}}"
  ></button>
  <button
    data-role="exit-button"
    tooltip="{{tips.exitfullscreen}}"
  ></button>
  <button data-role="qos-button" tooltip="{{tips.settings}}"></button>
  <button data-role="cast-button" tooltip="{{tips.cast}}"></button>
  <button
    data-role="airplay-button"
    tooltip="{{tips.airplay}}"
  ></button>
</div>
```

### **\[\[audioControls]]**

The control bar for audio content

#### **Markup**

```html
<div data-role="chrome">
  <button
    data-role="pause-button"
    tooltip="{{tips.play}}"
    tooltip-position="left"
  ></button>
  <button
    data-role="play-button"
    tooltip="{{tips.pause}}"
    tooltip-position="left"
  ></button>
  <button
    data-role="back-button"
    tooltip="{{playlist.playback}}"
    tooltip-position="left"
  ></button>
  <button
    data-role="next-button"
    tooltip="{{playlist.playnext}}"
    tooltip-position="left"
  ></button>
  <button data-role="play-minus10-button"></button>
  <button data-role="play-plus10-button"></button>
  <button data-role="play-minus15-button"></button>
  <button data-role="play-plus15-button"></button>
  <button
    data-role="volume-button"
    tooltip="{{tips.mutevolume}}"
    tooltip-position="left"
  ></button>

  <div data-role="volume-display-wrapper">
    <div data-role="volume-display">
      <div data-role="volume-controls-marker">
        <span data-role="volume-controls-thumb"></span>
      </div>
    </div>
  </div>

  <button data-role="qos-button" tooltip="{{tips.settings}}"></button>
  <button data-role="cast-button" tooltip="{{tips.cast}}"></button>
  <button
    data-role="airplay-button"
    tooltip="{{tips.airplay}}"
  ></button>

  <button data-role="timeleft-display">
    <span data-role="timeleft-span">
      ● Live
    </span>
  </button>
</div>
```

### Minimal Example Audio Player <a href="#page_minimal_example_audio_player" id="page_minimal_example_audio_player"></a>

```html
<div data-role="sdn-audio-player">
  <div data-role="sdn-display" class="sdn-display sdn-display-audio">
    <div data-role="channel-label"></div>
    <div data-role="title"></div>

    <video data-role="source-container" class="sdn-source-element sdn-hidden" x-webkit-airplay="allow" webkit-playsinline="true" playsinline="true" src="">

    <div data-role="player-controls">
      [[audioControls]]
    </div>

    <div data-role="playlist"></div>
  </div>
</div>
```


# Accessibility

The player can be fully controlled using the keyboard. **Keyboard** navigation is supported for common playback actions such as play/pause, seeking, volume control, fullscreen mode, and chapter navigation.

The player is fully **WCAG** compliant and **BITV** compliant, ensuring accessible interaction for keyboard and assistive-technology users.

Keyboard shortcuts are active when the player container is focused.

### Key Bindings (Desktop)

| Key                       | Action                        |
| ------------------------- | ----------------------------- |
| Space                     | Play / Pause                  |
| K                         | Play / Pause                  |
| F                         | Toggle Fullscreen             |
| Left Arrow                | Seek −10 seconds              |
| Right Arrow               | Seek +10 seconds              |
| Shift + Left Arrow        | Seek −60 seconds              |
| Shift + Right Arrow       | Seek +60 seconds              |
| Up Arrow                  | Increase Volume               |
| Down Arrow                | Decrease Volume               |
| M                         | Mute / Unmute                 |
| Ctrl (or ⌘) + Left Arrow  | Previous Chapter              |
| Ctrl (or ⌘) + Right Arrow | Next Chapter                  |
| Esc                       | Close open menus and overlays |

### Touch Gestures (Mobile)

| Gesture                            | Action           |
| ---------------------------------- | ---------------- |
| Two-finger double tap (left side)  | Previous Chapter |
| Two-finger double tap (right side) | Next Chapter     |

**Accessibility Notes**

The Tab key is intentionally not intercepted by the player. This allows users to navigate normally through focusable elements such as controls, buttons, and interactive UI components using standard keyboard navigation patterns.


# Consent (CMPs, TCF)

### TCF (Transparency & Consent Framework)

If TCF is enabled, the player automatically detects and uses the user’s **Consent String**. In most cases, **no additional player configuration is required**.

When using common Consent Management Platforms (CMPs) such as Cookiebot or other TCF-compatible solutions, it is usually sufficient to **add 3Q GmbH as a vendor** in the CMP configuration. Once configured, the player automatically reads the consent information provided by the CMP and applies it accordingly.

3Q GmbH is registered in the **IAB Europe TCF Vendor List** with **Vendor ID 876**:\
<https://iabeurope.eu/vendor-list-tcf/>

### Consent String

If required, the user’s consent string can be passed directly to the ad server by including the following placeholder in the Player Configuration. It will be passed automatically if you are integrating Ads. More information: [Ad Integration macros](/player-web-sdk/ad-integration#macros).

The consent string can be defined explicitly in the player configuration:

```js
<script>
const player = new js3q({
    playoutId: '5c3b0910-8850-11e7-9273-002590c750be',
    container: 'player',
    consentString: 'place consent string here',
    autoplay: true
});
</script>
```

This is typically only necessary for custom or non-standard integrations.


# Ad Integration

#### **Configuration**

<table><thead><tr><th width="135.39453125">Parameter</th><th width="128.39453125">Type</th><th width="118.6875">Default</th><th>Description</th></tr></thead><tbody><tr><td>ads</td><td>Boolean | object</td><td>false</td><td>This parameter is controled through the Settings on our AdManager Module. Alternatively you can also add Tags directly. Please see documentation blow.</td></tr></tbody></table>

**Using AdTags URLs**

Alternatively you can also add your AdTags directly to the Player configuration, see the following example. Here you can find some test tags from Google: <https://developers.google.com/interactive-media-ads/docs/sdks/html5/client-side/tags>.

```js
<script>
const player = new js3q({
    playoutId: '95ea8442-c3aa-43a3-bfdd-c93f35c6242a',
    ads: {
        preroll: "your ad tag url",
        midroll: "your ad tag url",
        postroll: "your ad tag url",
        adpoints: "360,720,1140,1500,1710"
    }
});
</script>
```

**Parameter adpoints (Midrolls)**

Ad-Cue points define fixed moments during content playback where midroll ads may be shown.

* **Seeking or fast-forwarding does not skip ads.**\
  If a viewer jumps past one or more ad cue points, the player will still trigger a midroll ad.
* **No ad stacking after seeking.**\
  When multiple cue points are skipped in a single seek action, only one midroll ad is played.
* **Normal playback behavior.**\
  During regular playback, midroll ads are triggered automatically when a cue point is reached.
* **Rewinding content.**\
  When a viewer seeks backward, upcoming cue points become active again and may trigger ads when reached.

This ensures fair ad delivery while keeping the viewing experience smooth and predictable.

#### **AdEvents**

You can create EventListeners for all AdEvents, please have a look here: [Events](/player-web-sdk/events#a-d-events).

#### **Macros**

We have integrated the Google IMA SDK and also a custom VAST Adapter. You can utilize the following macros to customize your AdTag URL. The macros are replaced automatically when calling the Ad.

* **{random}**: A random number to ensure URL uniqueness.
* **{referrer}**: URL-encoded referrer of the current page.
* **{consentString}**: User's consent string.
* **{loggedin}**: Boolean indicating if the user is logged in.

**Example**&#x20;

```
https://pubads.g.doubleclick.net/gampad/ads
?iu=/21775744923/external/single_ad_samples
&sz=640x480
&cust_params=sample_ct%3Dlinear%26loggedin%3D{loggedin}
&ciu_szs=300x250%2C728x90
&gdfp_req=1
&output=vast
&unviewed_position_start=1
&env=vp
&url={referrer}
&gdpr_consent={consentString}
&correlator={random}
```

#### Smartclip Integration

If you are using Smartclip, we are automatically replacing and inserting the parameters `consent` and `optout`.

#### Traffective Ad Integration

If you have the Traffective SDK integrated on your website, our player connects with the SDK and fetches the AdTag URLs automatically. The following setting is added to the player automatically when you are using our [player management](https://docs.3q.video/user-guide-new-ui/players). Otherwise you can configure this directly, see our example below.

```js
<script>
const player = new js3q({
    playoutId: '95ea8442-c3aa-43a3-bfdd-c93f35c6242a',
    ads: {
        adProvider:'traffective'
    }
});
</script>
```


# Playlists

Usually, playlists are created by customers using our **API** or the **UI**. Alternatively, playlists can also be generated directly via the **player configuration** when initializing a player instance.

You can create a playlist by adding a `playlist` object to the player configuration. This works both when embedding the player inline and when creating or updating the player dynamically.

If **player token protection** is enabled, you must provide a `key` and `timestamp` for **each item** in the playlist. When using videos from different projects, the corresponding **private key of each project** must be used to generate the correct key for that video.

If token protection is **not** enabled, the `key` and `timestamp` parameters can be omitted entirely.

### Playlist options <a href="#page_playlist_options" id="page_playlist_options"></a>

The following additional options are available when configuring a playlist

| Value                | Description                                                                                                                                                              | Default  |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------- |
| showPlaylist         | deprecated                                                                                                                                                               | false    |
| disablePlaylist      | disable playlist in general                                                                                                                                              | false    |
| playlistShowList     | show playlist on initial load                                                                                                                                            | false    |
| playlistLabel        | the label for the playlist                                                                                                                                               | `''`     |
| playlistVisibleItems | amount of items shown in playlist                                                                                                                                        | 10       |
| playlistMaxItems     | maximum amount of items in playlist                                                                                                                                      | 500      |
| playlistShowLatest   | audio player only                                                                                                                                                        | false    |
| playlistSticky       | audio player only                                                                                                                                                        | false    |
| playlistContainer    | Specify an external container for the playlist to display it outside the player. This parameter can be either an HTML element as object or the element's ID as a string. | `''`     |
| autoShuffle          | randomizes the order of the playlist                                                                                                                                     | false    |
| playlistMode         | The feature allows you to showcase the playlist in a horizontal layout or to build a video carousel. Available options: `normal`, `carousel`, `carousel-only`            | `normal` |

### Video Carousel

A video carousel displays multiple videos in a rotating format, offering users the ability to browse through various clips seamlessly. This design allows for showcasing diverse content without overcrowding the interface.

```js
<script src="https://player.3qsdn.com/js3q.latest.js"></script>
<div id="my-player"></div>
<div id="carousel"></div>
<script>
const player = new js3q({
    "playoutId": "3ceb2c09-c40c-4ee2-b52d-7fb041e60646",
    "container": "my-player",
    "playlistMode": "carousel",
    "playlistContainer": "carousel"
})
</script>
```

[Demo](https://player.3qsdn.com/?config=%7B%22dataid%22%3A%223ceb2c09-c40c-4ee2-b52d-7fb041e60646%22%2C%22playlistMode%22%3A%22carousel%22%2C%22playlistContainer%22%3A%22player-carousel%22%2C%22autoplay%22%3Afalse%7D)

### **Swipeable Video**&#x20;

Swipeable videos enable users to transition between clips with a simple swipe gesture. This interactive approach is intuitive, providing a smooth and engaging experience, particularly on mobile platforms.

#### Vertical video carousel

```html
<script src="https://player.3qsdn.com/js3q.latest.js"></script>
<div id="my-player"></div>
<div id="carousel"></div>
<script>
const player = new js3q({
    "playoutId": "3ceb2c09-c40c-4ee2-b52d-7fb041e60646",
    "container": "my-player",
    "playlistMode": "carousel-only",
    "playlistContainer": "carousel"
})
</script>
```

[Demo](https://player.3qsdn.com/?config=%7B%22dataid%22%3A%224d1f8724-fcb7-45fa-a278-9b4e05c7997a%22%2C%22playlistMode%22%3A%22carousel-only%22%2C%22playlistContainer%22%3A%22player-carousel%22%2C%22autoplay%22%3Afalse%2C%22aspect%22%3A1.77778%7D)

#### Vertical video reel player

```html
<script src="https://player.3qsdn.com/js3q.latest.js"></script>
<div id="my-player"></div>
<script>
const player = new js3q({
    "playoutId": "3ceb2c09-c40c-4ee2-b52d-7fb041e60646",
    "container": "my-player",
    "reels":true
})
</script>
```

[Demo](https://player.3qsdn.com/?config=%7B%22dataid%22%3A%224d1f8724-fcb7-45fa-a278-9b4e05c7997a%22%2C%22reels%22%3Atrue%2C%22autoplay%22%3Afalse%7D)

### Manual playlists

Our platform enables you to create playlists through our backend, allowing for both automatic and manual configurations. Below is an example of how you can manually create your own playlist without using our backend system.

```html
<div id="player"></div>
<script src="//player.3qsdn.com/js3q.latest.js"></script>
<script>
  const player = new js3q({
    playoutId: '5c3b0910-8850-11e7-9273-002590c750be', // First video
    container: 'player1',
    key: { key }, // Key and timestamp are only required if token protection is active
    timestamp: { timestamp },
    playlist: {
      0: {
        playoutId: 'f98baa26-38ce-11e8-bcfd-0cc47a188158', // Data ID and title are required
        title: '3Q Logo Animation.mov',
        poster:
          'https://sdn-global-prog-cache.3qsdn.com/thumbs/2015/3144/688639/W6YXmD4g2vZf3Gqh.jpg',
        key: 0, // Key and timestamp are only required if token protection is active
        timestamp: 0,
      },
      1: {
        playoutId: 'd52fcc10-7022-11e9-8d5b-0cc47a188158',
        title: '3Q Logo Animation.mov',
        poster:
          'https://sdn-global-prog-cache.3qsdn.com/thumbs/2015/3144/688639/W6YXmD4g2vZf3Gqh.jpg',
        key: 0,
        timestamp: 0,
      },
    }
  })
</script>
```


# Call to Action

Our CtA (Call-to-Action) interface is an amazing tool to create interactive videos for marketing, quizzing, shopping and everything else you can imagine.

<figure><img src="/files/NfkSYbVyZZhIiSwfdRPn" alt="" width="563"><figcaption></figcaption></figure>

The CtA interface creates a bridge between the player and your frontend. You only have to define the Call-to-Action element by using JavaScript’s native object and HTML.

```html
<div id="player"></div>
<script>
  new js3q({
    dataid: '5c3b0910-8850-11e7-9273-002590c750be',
    container: 'player',
    cta: {
      0: {
        time: 3,
        html: `<h1>Call-to-Action</h1>
               <p>Create a whatever you want Call-to-Action by using the CtA interface. This CtA is skippable. The next one will start after 15 seconds.</p>`,
        skippable: true,
      },
      1: {
        time: 15,
        html: `<h1>Do you like the video?</h1>
               <p>Subscribe our newsletter.</p>
               <form id="form1">
                 <input id="email" placeholder="e-mail address" type="email"/>
                 <br/>
                 <button ctacallback resume style="font-size:15px;color:white;background:#d534c8;">Subscribe</button>
               </form>`,
        btnCallback: (data) => {
          console.log(data)
        },
        skippable: false,
      },
    },
  })
</script>
```

### The CtA Element <a href="#page_the_cta_element" id="page_the_cta_element"></a>

The Call-to-Action element is configured with the following parameters (JavaScript native object):

| Key         | Type     | Description                                                                                                                               |
| ----------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| time        | Integer  | The position (in seconds) of the audio/video playback when the CtA should start                                                           |
| html        | String   | The html you want to place. **Please read the description below carefully.**                                                              |
| btnCallback | Function | This parameter is optional. If you use forms or inputs in the CtA element, you will receive them in the function you define or reference. |
| skippable   | Boolean  | If true, the CtA can be skipped.                                                                                                          |
| autoplay    | Boolean  | If false, the CtA element is only displayed when triggered manually.                                                                      |

In this example we create a CtA with a form for subscribing to a newsletter. To receive the e-mail address from the user, you have to create an input field for the address: `<input required id="email" placeholder="e-mail address" type="email"/>` and a buttonElement: `<button ctacallback resume>Subscribe</button>`.

The attribute `ctacallback` means that when the button is pressed, the `btnCallback` function is triggered. `resume` says that the video is now continuing. For example, if you want to intercept a failure scenario or use a multi-level Call-to-Action element for quizzing, you can use another attribute of `next = '1'` (1 being the ID of the CtA) to call that element.

If the CtA element is not skippable (`skippable:false`), the playback is blocked while the CtA element is being displayed as long the CtA element is not submitted.

```javascript
1: {
  time:15,
  html: `<h1>Do you like the video?</h1>
         <p>Subscribe our newsletter.</p>

         <form id="form1">
           <input id="email" placeholder="e-mail address" type="email"/>
           <br/>
           <button ctacallback resume>
             Subscribe
           </button>
         </form>`,
   btnCallback: (data) => {
    // Here you can code whatever you like. For example submitting the form.
  },
  skippable:false
}
```

### The Callback <a href="#page_the_callback" id="page_the_callback"></a>

The example above provides the following `data` during the `btnCallback`:

```javascript
{
  ctaId: "1",
  formInputs: {
    0: {
      id: "email",
      type: "email",
      value: "example@email.com"
    }
  }
}
```

The `ctaId` key is the identifier which CtA element is used.

### Multi-Level CtA <a href="#page_multi_level_cta" id="page_multi_level_cta"></a>

As described above, you can also create multi-level CtA. Below you find a simple quizzing:

```javascript
cta: {
  0: {
    time:50,
    html: `<h1>Do you like the video?</h1>
           <form id="form1">
             <button ctacallback resume style="font-size:15px;color:white;">Yes</button>
             <button ctacallback next="1" style="font-size:15px;color:red;">No</button>
           </form>`,
    btnCallback: function (data) {
      // Here you can code whatever you like. For example submitting the form with Ajax.
    },
    autoplay:true,
    skippable:false
  },
  1: {
    time:15,
    html: '<h1>:-(</h1><p>Okay, we will try it again.</p>',
    autoplay:false,
    skippable:true
  }
}
```

### Methods <a href="#page_methods" id="page_methods"></a>

| Method        | Type       | Description |
| ------------- | ---------- | ----------- |
| callcta       | Integer    |             |
| callctaObject | CtA Object |             |
| closecta      |            |             |

### Events <a href="#page_events" id="page_events"></a>

| Event          | Description |
| -------------- | ----------- |
| cta.displaying |             |
| cta.closed     |             |

### Live Demo <a href="#page_live_demo" id="page_live_demo"></a>

{% embed url="<https://codepen.io/3qgmbh/pen/rNYVbMJ>" %}


# Branding

```html
<div id="player"></div>
<script>
  new js3q({
    dataid: '5c3b0910-8850-11e7-9273-002590c750be',
    container: 'player',
    branding: {
      position: 'top-left',
      src: '//unsplash.it/48/48',
      text1: 'Branding',
      text2: 'Powered by 3Q',
    },
  })
</script>
```

| value    | type     | description                                              | default        |
| -------- | -------- | -------------------------------------------------------- | -------------- |
| position | `String` | `top-left`, `top-right`, `bottom-left` or `bottom-right` | `bottom-right` |
| src      | `String` | `<img>` source (image is per default 48x48px)            |                |
| text1    | `String` | top text                                                 |                |
| text2    | `String` | bottom text                                              |                |
| href     | `String` | external link                                            |                |

{% embed url="<https://codepen.io/3qgmbh/embed/jOaPRBB>" %}


# Watermark Protection

The Watermark Protection component shows the defined text in the defined time-range for the defined duration randomly or defined placed on the display

| key                 | type              | description                        | default               |
| ------------------- | ----------------- | ---------------------------------- | --------------------- |
| text                | string            | display text                       |                       |
| color               | string            | font-color                         | rgba(255,255,255,0.3) |
| size                | number            | font-size in px                    | 10                    |
| interval\_min       | number            | min duration between showups in ms | 250                   |
| interval\_max       | number            | max duration between showups in ms | 500                   |
| appereance\_min     | number            | min duration of showup in ms       | 1                     |
| appereance\_max     | number            | max duration of showup in ms       | 20                    |
| placement\_mode     | string            | `random` or `fixed`                | `random`              |
| placement\_position | \[number, number] | position for fixed placement       | \[0, 0]               |

{% embed url="<https://codepen.io/3qgmbh/embed/abVOxWa>" %}


# Comments

Comments are currently available **only for livestreams**.

To enable comments, pass the option `comments: true` in the player setup configuration.\
This will render the comments in the default player layout.

Alternatively, you can pass a **custom HTML element** instead of `true` to embed the comments anywhere on your page.

You may also provide the **ID of a container element**, similar to how the player container itself is defined.

**Example placing comments outside of the videoplayer:**

```html
<div id="player"></div>
<div id="comments"></div>

<script type="text/javascript">
  let comments = document.querySelector('#comments')
  let player = new js3q({
    dataid: '5c3b0910-8850-11e7-9273-002590c750be',
    container: 'player',
    comments: comments,
    comments_username: 'Preset for the username',
    comments_dataid: '16a61ee1-784c-11ea-97a4-002590c750be',
  })
</script>
```

### Additional Options <a href="#page_additional_options" id="page_additional_options"></a>

| Key                         | Type    | Description                                                                                 |
| --------------------------- | ------- | ------------------------------------------------------------------------------------------- |
| comments\_username          | String  | The Preset for the username                                                                 |
| comments\_dataid            | String  | The dataid of another live-stream                                                           |
| comments\_align             | String  | "top" (default) or "bottom" - Alignment for the auto scroll, after the user added a comment |
| comments\_permanentUsername | Boolean | Lock the username, after the first message was sent. (or, if \`comments\_username\` is set) |

### Live Demo <a href="#page_live_demo" id="page_live_demo"></a>

{% embed url="<https://codepen.io/3qgmbh/pen/ZEaGPYG>" %}


# Live Reactions

Caution: Realtime Events must be active in order to use live reactions

<figure><img src="/files/aoNw1Gz7DB6FITEraCQx" alt=""><figcaption></figcaption></figure>

### Config <a href="#page_live_demo" id="page_live_demo"></a>

```javascript
{
  disableRealtimeEvents: false,
  liveReactions: {
    enabled: true,
    position: 'sidebar', // alternatively: "controlbar"
    items: ['👏', '👍', '❤️'], // or
    itemImages: ['http://url-to-image.png'] // optional
  }
}
```

### Live Demo <a href="#page_live_demo" id="page_live_demo"></a>

{% embed url="<https://codepen.io/3qgmbh/details/GggQzbV>" %}


# Quizzing

You can pause the video on defined timestamps and display quizzing questions.

There are several types of question types:

## SingleChoice (Text)

{% embed url="<https://codepen.io/3qgmbh/embed/popZYma>" %}

### **Configuration**

```javascript
{
  "quizzingquestions": [
    {
      "id": 1,
      "QuestionType": "singlechoice_text",
      "secondsFromStart": 10,
      "skippable": true,
      "Text": "Find the correct answer",
      "FeedbackTextRight": "<h3>Well done!</h3><p>You are really smart!</p>",
      "FeedbackTextWrong": "<h3>Wrong answer</h3><p>Please try again</p>",
      "Answers": [
        {
          "id": 10,
          "Text": "I am a wrong answer"
        },
        {
          "id": 11,
          "Text": "I am the right answer",
          "IsRight": true
        },
        {
          "id": 12,
          "Text": "I am not the right answer"
        },
        {
          "id": 13,
          "Text": "I am a wrong answer"
        }
      ]
    }
  ]
}
```

## SingleChoice (Image)

{% embed url="<https://codepen.io/3qgmbh/embed/xxpJmWG>" %}

### **Configuration**

```javascript
{
  "quizzingquestions": [
    {
      "id": 2,
      "QuestionType": "singlechoice_image",
      "secondsFromStart": 10,
      "Text": "Where is the doll?",
      "FeedbackTextRight": "<h3>Well done!</h3><p>You are really smart!</p>",
      "FeedbackTextWrong": "<h3>Wrong answer</h3><p>Please try again</p>",
      "Answers": [
        {
          "id": 20,
          "Text": "Barbie",
          "IsRight": true,
          "Thumb": "https://sdn-global-prog-cache.3qsdn.com/uploads/5985/files/21/02/12/2749914/603e4cb1389071614695601.jpeg"
        },
        {
          "id": 21,
          "Text": "Filmstreifen",
          "Thumb": "https://sdn-global-prog-cache.3qsdn.com/uploads/5985/files/21/02/12/2749914/603e4cb13e8921614695601.jpeg"
        },
        {
          "id": 22,
          "Text": "Klappe",
          "Thumb": "https://sdn-global-prog-cache.3qsdn.com/uploads/5985/files/21/02/12/2749914/603e4cb14000f1614695601.jpeg"
        },
        {
          "id": 23,
          "Text": "FB Camera",
          "Thumb": "https://sdn-global-prog-cache.3qsdn.com/uploads/5985/files/21/02/12/2749914/603e4cb1417541614695601.jpeg"
        }
      ]
    }
  ]
}
```

## MultipleChoice (Text)

{% embed url="<https://codepen.io/3qgmbh/embed/eYyjadp>" %}

### **Configuration**

```javascript
{
  "quizzingquestions": [
    {
      "id": 3,
      "QuestionType": "multiplechoice_text",
      "secondsFromStart": 10,
      "Text": "Can you find the correct answers?",
      "FeedbackTextRight": "<h3>Well done!</h3><p>You are really smart!</p>",
      "FeedbackTextWrong": "<h3>Wrong answer</h3><p>Please try again</p>",
      "Answers": [
        {
          "id": 30,
          "Text": "I am correct",
          "IsRight": true
        },
        {
          "id": 31,
          "Text": "I am wrong"
        },
        {
          "id": 32,
          "Text": "I am wrong"
        },
        {
          "id": 33,
          "Text": "I am correct",
          "IsRight": true
        },
        {
          "id": 34,
          "Text": "I am wrong"
        },
        {
          "id": 35,
          "Text": "I am wrong"
        }
      ]
    }
  ]
}
```

## MultipleChoice (Image)

{% embed url="<https://codepen.io/3qgmbh/embed/zYpLQoB>" %}

### **Configuration**

```javascript
{
  "quizzingquestions": [
    {
      "id": 4,
      "QuestionType": "multiplechoice_image",
      "secondsFromStart": 10,
      "Text": "Can you find the correct answers?",
      "FeedbackTextRight": "<h3>Well done!</h3><p>You are really smart!</p>",
      "FeedbackTextWrong": "<h3>Wrong answer</h3><p>Please try again</p>",
      "Answers": [
        {
          "id": 40,
          "Text": "Filmstreifen",
          "Thumb": "https://sdn-global-prog-cache.3qsdn.com/uploads/5985/files/21/02/12/2749914/603e4d3264dd31614695730.jpeg"
        },
        {
          "id": 41,
          "Text": "München",
          "IsRight": true,
          "Thumb": "https://sdn-global-prog-cache.3qsdn.com/uploads/5985/files/21/02/12/2749914/603e4d326679f1614695730.jpeg"
        },
        {
          "id": 42,
          "Text": "Barbie",
          "Thumb": "https://sdn-global-prog-cache.3qsdn.com/uploads/5985/files/21/02/12/2749914/603e4d3267c111614695730.jpeg"
        },
        {
          "id": 43,
          "Text": "Klappe",
          "IsRight": true,
          "Thumb": "https://sdn-global-prog-cache.3qsdn.com/uploads/5985/files/21/02/12/2749914/603e4d32692851614695730.jpeg"
        }
      ]
    }
  ]
}
```

## Assignment

{% embed url="<https://codepen.io/3qgmbh/embed/dyJVwKy>" %}

### **Configuration**

```javascript
{
  "quizzingquestions": [
    {
      "id": 5,
      "QuestionType": "assignment",
      "secondsFromStart": 10,
      "Text": "Assign the persons to the companies",
      "FeedbackTextRight": "<h3>Well done!</h3><p>You are really smart!</p>",
      "FeedbackTextWrong": "<h3>Wrong answer</h3><p>Please try again</p>",
      "Answers": [
        {
          "id": 50,
          "Text": "Steve Jobs",
          "Bucket": "Apple"
        },
        {
          "id": 51,
          "Text": "Bill Gates",
          "Bucket": "Microsoft"
        },
        {
          "id": 52,
          "Text": "Steve Ballmer",
          "Bucket": "Microsoft"
        },
        {
          "id": 53,
          "Text": "Steve Wozniak",
          "Bucket": "Apple"
        }
      ]
    }
  ]
}
```

## Cloze

{% embed url="<https://codepen.io/3qgmbh/embed/yLpqwyg>" %}

### **Configuration**

```javascript
{
  quizzingquestions: [
    {
      id: 6,
      QuestionType: "close",
      secondsFromStart: 10,
      Text: "Fill in the blanks",
      FeedbackTextRight: "<h3>Well done!</h3><p>You are really smart!</p>",
      FeedbackTextWrong: "<h3>Wrong answer</h3><p>Please try again</p>",
      Answers: [
        {
          "Text": "Zwei flinke __Boxer__ jagen die quirlige __Eva__ und ihren __Mops__ durch Sylt."
        }
      ]
    }
  ]
}
```

## Reflection

{% embed url="<https://codepen.io/3qgmbh/embed/rNprgzL>" %}

### **Configuration**

```javascript
{
  quizzingquestions: [
    {
      id: 7,
      secondsFromStart: 10,
      QuestionType: "reflection",
      Text: "This is just a text. We recommend using CtA instead"
    }
  ]
}
```


# Hivestreaming (E-CDN)

You can use the P2P E-CDN service providers **Kollective** and **Hive Streaming** by following the configuration instructions below.

Both [**Hive Streaming**](https://www.hivestreaming.com/) and [**Kollective**](https://de.kollective.com/) are fully integrated into our API and can also be controlled through our web interface.

**Hive Streaming**

```html
<script type="text/javascript">
  var player = new js3q({
    playoutId: '5c3b0910-8850-11e7-9273-002590c750be',
    container: 'player',
    p2p: {
      hive: {
        ticketUrl: 'your-hive-ticket-url',
        techOrder: ['HiveJS'], // optional, Techs available: "HiveJava", "HiveJS", "StatsJS",
      },
    },
  });
</script>
```


# Multi Channel Livestreams

### Configuration

```html
<div id="player"></div>
<script>
  const player = new js3q({
    container: 'player',
    dataid: '...',
    // additional configurations
    multichannelstream: {
      items: [
        {
          dataid: '...',
          default: true,
          description: 'Lorem Ipsum',
          language: 'de',
          position: 1,
          poster: 'https://unsplash.it/400/400',
          title: 'Lorem Ipsum',
        },
        {
          dataid: '...',
          default: false,
          description: 'Lorem Ipsum',
          language: 'en',
          position: 2,
          poster: 'https://unsplash.it/400/400',
          title: 'Lorem Ipsum',
        },
      ],
    },
  })
</script>
```

The Multi-Channel Livestream Module is based on the Playlist plugin. Therefore the same Methods can be used:

### Player Methods

| Name             | Parameters    | Description                                             |
| ---------------- | ------------- | ------------------------------------------------------- |
| getPlaylistItems |               | Returns an array of all available items in the playlist |
| playlistItem     | index: number | Sets the active medium to the given index               |
| next             |               | Plays the next item in the playlist                     |
| previous         |               | Plays the previous item in the playlist                 |


# Switch between different livestreams

This tutorial helps if you want to switch between live-streams or videos. In this example we create a button in the player controls and switch the language.

### HTML <a href="#page_html" id="page_html"></a>

```html
<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <style>
      body,
      html {
        width: 100%;
        height: 100%;
        background: #efefef;
        margin: 0;
        padding: 0;
      }

      #player-wrapper {
        width: 50%;
        margin: 0 auto;
      }
    </style>
  </head>

  <body>
    <div id="player-wrapper">
      <!-- Player container -->
      <div id="player"></div>
    </div>
    <script src="https://player.3qsdn.com/js3q.latest.js"></script>
    <script>
      // In this case German stream
      const dataid_main = '66e68995-11ca-11e8-9273-002590c750be'

      // In this case Italian stream
      const dataid_second = '1bc8cbdc-73c5-11e8-ae4b-0cc47a188158'

      let js3qVideoPlayer
      let currentDataId = ''

      function addSwitchButton() {
        // Replace the prefix 'player' with your container ID
        let Controls = document.getElementById('player-chrome')

        if (document.getElementById('player-switch-button')) return

        let childElement = Controls.appendChild(
          document.createElement('js3q-button')
        )
        childElement.setAttribute('id', 'player-switch-button')
        childElement.setAttribute('class', 'sdn-button-right sdnicbsun-chat')

        // Tooltip
        childElement.setAttribute('tooltip', 'Switch language')
        childElement.addEventListener('click', function () {
          if (currentDataId === dataid_main) {
            loadPlayer(dataid_second)
          } else {
            loadPlayer(dataid_main)
          }
        })
      }

      function loadPlayer(dataid) {
        currentDataId = dataid

        if (
          js3qVideoPlayer &&
          typeof js3qVideoPlayer.destroy() === 'function'
        ) {
          js3qVideoPlayer.pause()
          js3qVideoPlayer.destroy()
        }

        js3qVideoPlayer = new js3q({
          dataid: dataid,
          container: 'player',
          autoplay: true,
        })

        // Event listener player ready event
        js3qVideoPlayer.on('media.ready', function (data) {
          addSwitchButton()
        })
      }

      // Init main stream;
      loadPlayer(dataid_main)
    </script>
  </body>
</html>
```

### Live Demo <a href="#page_live_demo" id="page_live_demo"></a>

{% embed url="<https://codepen.io/3qgmbh/pen/oNopeKE>" %}


# Use the Player with Require

### Implementing 3q.js with require.js

For using 3q.js player in combination with require.js, you have to add this configuration to require.js.

```javascript
// require.js config
requirejs.config({
  // Other configuration
  shim: {
    // Other shims
    'https://player.3qsdn.com/bin/hls.min.v1.6.13.js': {
      deps: ['require'],
      exports: 'Hls',
    },
  },
})

// later in code
window.Hls = Hls
```


# Use Video Loop as background

In order to play a video as a background-element of your webpage, you can realize that with a few easy configurations:

```javascript
{
    playoutId: 'your playout id',
    controls: false,
    autoplay: true,
    muted: true,
    loop: true
}
```

Example: <https://codepen.io/3qgmbh/pen/YPPEBZM>


# Using a custom player for streaming

This tutorial helps if you want to use a custom player for streaming.

### Fetch the streaming URLs <a href="#page_fetch_the_streaming_urls" id="page_fetch_the_streaming_urls"></a>

With our client-side rest API, you can fetch all data provided for a file or live-stream in easy way. For protection you need to generate a key as described [here](/player-web-sdk/basic-usage).

You can fetch the streaming URLs with the following URL. Please do not forget to replace the macro parameters with your own data.

```
https://playout.3qsdn.com/config/[DATAID]?key=[KEY]&timestamp=[TIMESTAMP]
```

**Error Status codes** If your token is not correct or the video is not published, you’ll get certain status codes which you have to handle.

* 200: Everything is okay
* 404: File does not exists
* 403: Token is not correct
* 401: Geo-Blocked
* 410: File is not published (releaseStatus)

After you called the URL, you’ll receive a JSON file which looks like this, it provides also metadata, etc.

{% hint style="info" %}
If your project is using content protection, the call to this JSON must have exactly the same user agent header as the player.
{% endhint %}

```json
{
  "dataid": "a719ecb1-b61f-11ea-97a4-002590c750be",
  ...
  "streamContent": "demand",
  "streamType": "video",
  "sources": {
    "hls": "https://sdn-global-streaming-cache.3qsdn.com/stream/.../manifest.m3u8",
    "dash": "https://sdn-global-streaming-cache.3qsdn.com/stream/.../manifest.mpd",
    "progressive": [
      {
        "src": "https://sdn-global-prog-cache.3qsdn.com/stream/.../57-XW8JQvNYrxfMHFBDqd9k.mp4",
        "type": "video/mp4",
        "height": 720
      },
      {
        "src": "https://sdn-global-prog-cache.3qsdn.com/stream/.../6-ZN2XYHqhdjtvC6M7PKD3.mp4",
        "type": "video/mp4",
        "height": 144
      },
      {
        "src": "https://sdn-global-prog-cache.3qsdn.com/stream/.../5-nGK4F8hVpX3RdBCWtDm6.mp4",
        "type": "video/mp4",
        "height": 240
      },
      {
        "src": "https://sdn-global-prog-cache.3qsdn.com/stream/.../4-KP6vg7JY2Rc9F3XDZxzr.mp4",
        "type": "video/mp4",
        "height": 360
      },
      {
        "src": "https://sdn-global-prog-cache.3qsdn.com/stream/.../3-v7rxNPKYDzgLH9Vtb3Gj.mp4",
        "type": "video/mp4",
        "height": 480
      },
      {
        "src": "https://sdn-global-prog-cache.3qsdn.com/stream/.../2-cMJT4DCB8drvPYRhz7x6.mp4",
        "type": "video/mp4",
        "height": 720
      }
    ]
  }
  ...
}
```

As you can see in the example, you can now pick up the URLs you need and put them in the custom player. We always suggest to use streaming formats (HLS, DASH) instead of progressive download.


