---
title: JS Control & Events
url: https://docs.kaltura.com/docs/embeddable-kaltura-products/kaltura-player/player-embed-js-control
---

## JS Control (Play/Pause/Seek/Volume)

With **dynamic JS embeds**, the player instance exposes standard controls:

```js
// Play / Pause
player.play();
player.pause();

// Seek (seconds)
player.currentTime = 90;

// Volume (0.0 – 1.0)
player.volume = 0.5;
```

These APIs are part of the web player's base interface. Key properties and methods:

| API | Type | Description |
|-----|------|-------------|
| `player.play()` | method | Start playback |
| `player.pause()` | method | Pause playback |
| `player.currentTime` | get/set | Current playback position in seconds |
| `player.duration` | get | Total media duration in seconds |
| `player.volume` | get/set | Volume level (0.0 – 1.0) |
| `player.muted` | get/set | Mute state (boolean) |
| `player.playbackRate` | get/set | Playback speed (1.0 = normal) |
| `player.isLive()` | method | Whether the current media is a live stream |
| `player.ready()` | method | Returns a Promise that resolves when the player is ready for interaction |
| `player.loadMedia({entryId})` | method | Load a new media entry into the player |

### Binding to JS Player Events

The Kaltura player exposes a DOM-style event system and a Promise for readiness. Use it for analytics beacons, UI reactions, gated flows, etc.  

**Player core events consist of two event types:**

- **HTML5 Video Events** — Standard events from the underlying `<video>` element  
- **Player Custom Events** — Events specific to the Kaltura player that extend beyond standard HTML5 video  

All core events are accessible via `player.Event.Core` as shown in the example below.

#### HTML5 Video Events

| Event | Constant | Callback Payload |
|-------|----------|-----------------|
| Play | `PLAY` | No payload. Fired when playback is requested (before media actually starts playing). |
| Pause | `PAUSE` | No payload. Fired when playback is paused. |
| Ended | `ENDED` | No payload. Fired when playback reaches the end of the media. |
| Seeking | `SEEKING` | No payload. The player's `currentTime` property reflects the target seek position. |
| Seeked | `SEEKED` | No payload. Fired when a seek operation completes. |
| Time Update | `TIME_UPDATE` | No payload. Fired continuously during playback (typically every 250ms). Read `player.currentTime` and `player.duration` for position data. |
| Volume Change | `VOLUME_CHANGE` | No payload. Read `player.volume` (0.0-1.0) for the new volume level. |
| Waiting | `WAITING` | No payload. Fired when playback stalls due to buffering. |
| Playing | `PLAYING` | No payload. Fired when playback resumes after buffering or pause. |
| Can Play | `CANPLAY` | No payload. Fired when enough data is buffered to begin playback. |
| Loaded Metadata | `LOADED_METADATA` | No payload. Fired when media metadata (duration, dimensions) is available. Read `player.duration` for the media length. |
| Loaded Data | `LOADED_DATA` | No payload. Fired when the first frame of the media is loaded. |
| Duration Change | `DURATION_CHANGE` | No payload. Fired when `player.duration` changes (e.g., live stream duration updates). |
| Rate Change | `RATE_CHANGE` | No payload. Read `player.playbackRate` for the new rate value. |
| Error | `ERROR` | `event.payload` — object with `severity` (number: 0=CRITICAL, 1=RECOVERABLE), `category` (number: 1=NETWORK, 2=TEXT, 3=MEDIA, 4=MANIFEST, 5=ENCRYPTION), `code` (number), and `data` (error details object). |

#### Player Custom Events

| Event | Constant | Callback Payload |
|-------|----------|-----------------|
| Player State Changed | `PLAYER_STATE_CHANGED` | `event.payload.oldState` — object with `type` (string: `"idle"`, `"loading"`, `"playing"`, `"paused"`, `"buffering"`).  `event.payload.newState` — object with `type` (same values). |
| Media Loaded | `MEDIA_LOADED` | No payload. Fired after `loadMedia()` resolves and media sources are set. |
| First Play | `FIRST_PLAY` | No payload. Fired once per media load — the first time play is requested. |
| First Playing | `FIRST_PLAYING` | No payload. Fired once per media load — the first time media actually renders a frame. |
| Tracks Changed | `TRACKS_CHANGED` | `event.payload.tracks` — array of track objects, each with `type` (string: `"video"`, `"audio"`, `"text"`), `label` (string), `language` (string), `active` (boolean), and `index` (number). |
| Text Track Changed | `TEXT_TRACK_CHANGED` | `event.payload.selectedTextTrack` — track object with `language` (string), `label` (string), `kind` (string: `"subtitles"`, `"captions"`), `active` (boolean), and `index` (number). |
| Audio Track Changed | `AUDIO_TRACK_CHANGED` | `event.payload.selectedAudioTrack` — track object with `language` (string), `label` (string), `active` (boolean), and `index` (number). |
| Video Track Changed | `VIDEO_TRACK_CHANGED` | `event.payload.selectedVideoTrack` — track object with `width` (number), `height` (number), `bitrate` (number), `active` (boolean), and `bandwidth` (number). |
| Enter Fullscreen | `ENTER_FULLSCREEN` | No payload. Fired when the player enters fullscreen mode. |
| Exit Fullscreen | `EXIT_FULLSCREEN` | No payload. Fired when the player exits fullscreen mode. |
| Enter Picture-in-Picture | `ENTER_PICTURE_IN_PICTURE` | No payload. Fired when the player enters PiP mode. |
| Exit Picture-in-Picture | `EXIT_PICTURE_IN_PICTURE` | No payload. Fired when the player exits PiP mode. |
| Source Selected | `SOURCE_SELECTED` | `event.payload.selectedSource` — array of source objects, each with `mimetype` (string), `url` (string), `id` (string), `bandwidth` (number), `width` (number), `height` (number). |
| Change Source Started | `CHANGE_SOURCE_STARTED` | No payload. Fired when a new media source begins loading (e.g., after `loadMedia()`). |
| Change Source Ended | `CHANGE_SOURCE_ENDED` | No payload. Fired when the new media source has been applied. |
| Autoplay Failed | `AUTOPLAY_FAILED` | `event.payload` — object with `error` (string, browser's autoplay rejection reason). |
| Mute Change | `MUTE_CHANGE` | `event.payload.mute` — boolean, the new mute state. |
| Playback Start | `PLAYBACK_START` | No payload. Fired once per session when the first play begins (differs from `FIRST_PLAY` in that it fires after buffering completes). |
| Playback Ended | `PLAYBACK_ENDED` | No payload. Fired when all media in the playlist or single entry finishes. |
| Ad Started | `AD_STARTED` | `event.payload.adType` — string (`"preroll"`, `"midroll"`, `"postroll"`). `event.payload.adPosition` — number (ad index). |
| Ad Completed | `AD_COMPLETED` | `event.payload.adType` — string. `event.payload.adPosition` — number. |
| Ad Skipped | `AD_SKIPPED` | `event.payload.adType` — string. `event.payload.adPosition` — number. |

```html
<script>
  window.player = player;

  function onPlay()  { console.log('PLAY'); }
  function onPause() { console.log('PAUSE'); }
  function onEnded() { console.log('ENDED'); }
  function onError(ev){ console.error('ERROR', ev && ev.payload); }
  function onSeeking() { console.log('SEEKING...'); }
  function onSeeked()  { console.log('SEEKED'); }
  function onTimeUpdate() { console.log("currentTime: ", player.currentTime); }

  player.ready().then(() => {
    console.log("player is ready!");
    const E = player.Event.Core;
    // bind to events:  
    player.addEventListener(E.PLAY,        onPlay);
    player.addEventListener(E.PAUSE,       onPause);
    player.addEventListener(E.ENDED,       onEnded);
    player.addEventListener(E.ERROR,       onError);
    player.addEventListener(E.TIME_UPDATE, onTimeUpdate);
    player.addEventListener(E.SEEKING,     onSeeking);
    player.addEventListener(E.SEEKED,      onSeeked);
  });

  window.dispose = function() {
    console.log('disposing...');
    const E = player.Event.Core;
    window.player.removeEventListener(E.PLAY,        onPlay);
    window.player.removeEventListener(E.PAUSE,       onPause);
    window.player.removeEventListener(E.ENDED,       onEnded);
    window.player.removeEventListener(E.ERROR,       onError);
    window.player.removeEventListener(E.TIME_UPDATE, onTimeUpdate);
    window.player.removeEventListener(E.SEEKING,     onSeeking);
    window.player.removeEventListener(E.SEEKED,      onSeeked);
  }

</script>

```

### PlayKit Plugin Ecosystem

The Kaltura Player v7 (PlayKit) supports 30+ plugins that extend playback with interactive features. Plugins are configured via the `plugins` block in `KalturaPlayer.setup()`:

```javascript
const player = KalturaPlayer.setup({
  targetId: 'kplayer',
  provider: { partnerId: PARTNER_ID, uiConfId: UICONF_ID, ks: KS },
  plugins: {
    dualscreen: {},       // enable Dual Screen plugin
    transcript: {},       // enable Transcript plugin
    navigation: {}        // enable Navigation plugin
  }
});
```

**Key plugins and their configuration parameters:**

#### Dual Screen (`dualscreen`)

Multi-stream PIP / side-by-side layout switching for entries with a secondary stream (e.g., slides + presenter).

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `layout` | string | `"pip"` | Initial layout: `"pip"` (picture-in-picture), `"sbs"` (side-by-side), `"single"` (primary only), `"singleSecondary"` (secondary only) |
| `mainViewEntryId` | string | (auto) | Entry ID for the main (primary) view |
| `secondaryViewEntryId` | string | (auto) | Entry ID for the secondary view |
| `pip.position` | string | `"bottom-right"` | PiP window position: `"top-left"`, `"top-right"`, `"bottom-left"`, `"bottom-right"` |
| `inverse` | boolean | `false` | Swap primary and secondary media in the layout |
| `childSizeMultiplier` | number | `0.3` | PiP window size as a fraction of the player (0.0 - 1.0) |

```javascript
plugins: {
  dualscreen: {
    layout: 'sbs',
    inverse: false,
    childSizeMultiplier: 0.3
  }
}
```

#### Navigation (`navigation`)

Chapter-based navigation with thumbnails from cue points defined on the entry.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `position` | string | `"right"` | Panel position: `"right"`, `"left"`, `"top"`, `"bottom"` |
| `expandOnFirstPlay` | boolean | `true` | Auto-expand the navigation panel on first play |
| `visible` | boolean | `true` | Show the navigation panel |
| `itemsOrder.bySortOrder` | boolean | `true` | Sort chapters by the cue point `sortOrder` field |

```javascript
plugins: {
  navigation: {
    position: 'right',
    expandOnFirstPlay: true
  }
}
```

#### Transcript (`transcript`)

Searchable transcript overlay synced to playback, using caption assets on the entry.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `position` | string | `"right"` | Panel position: `"right"`, `"left"`, `"top"`, `"bottom"` |
| `expandOnFirstPlay` | boolean | `true` | Auto-expand the transcript panel on first play |
| `visible` | boolean | `true` | Show the transcript panel |
| `showTime` | boolean | `true` | Display timestamps next to each line |
| `scrollOffset` | number | `0` | Pixels to offset auto-scroll from the top |
| `searchDebounceTimeout` | number | `250` | Milliseconds to debounce search input |
| `searchNextPrevVisible` | boolean | `true` | Show next/previous navigation in search results |
| `downloadDisabled` | boolean | `false` | Hide the transcript download button |

```javascript
plugins: {
  transcript: {
    position: 'right',
    expandOnFirstPlay: true,
    showTime: true,
    downloadDisabled: false
  }
}
```

#### Q&A (`qna`)

Live Q&A panel for virtual events, enabling audience questions and moderator responses.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `position` | string | `"right"` | Panel position: `"right"`, `"left"` |
| `expandOnFirstPlay` | boolean | `false` | Auto-expand the Q&A panel on first play |
| `visible` | boolean | `true` | Show the Q&A panel |
| `dateFormat` | string | `"mmmm do, yyyy"` | Date display format for Q&A entries |
| `bannerDuration` | number | `5000` | Banner notification duration in milliseconds |

```javascript
plugins: {
  qna: {
    position: 'right',
    expandOnFirstPlay: false
  }
}
```

#### Hotspots (`hotspots`)

Clickable overlay hotspots on video, defined via cue points with custom data.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `visible` | boolean | `true` | Show hotspot overlays |

```javascript
plugins: {
  hotspots: {}
}
```

Hotspot content, position, and timing are defined by cue points on the entry (via the `cuePoint` API service), not by plugin config. The plugin reads these cue points and renders them at the specified times.

#### In-Video Quiz (IVQ) (`ivq`)

Interactive quizzes during playback, pausing the video to present questions at configured cue points.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `visible` | boolean | `true` | Show quiz overlays |

```javascript
plugins: {
  ivq: {}
}
```

Quiz questions, answers, and timing are defined by cue points on the entry (created via the `quiz` plugin in KMC or through the API). The IVQ plugin renders them during playback.

#### SEO (`seo`)

Auto-generates JSON-LD structured data (`VideoObject` schema) for video entries to improve search engine discoverability.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `includeDescription` | boolean | `true` | Include the entry description in the JSON-LD output |
| `includeThumbnail` | boolean | `true` | Include the thumbnail URL in the JSON-LD output |
| `includeUploadDate` | boolean | `true` | Include the upload date in the JSON-LD output |

```javascript
plugins: {
  seo: {}
}
```

#### Downloads (`downloads`)

Adds a download button to the player UI that lists available flavor assets (transcoded versions) for download.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `flavorParamIds` | string | `""` | Comma-separated flavor param IDs to offer for download. Empty string means all available flavors. |
| `presetName` | string | `""` | Name for the download preset group |
| `displayFlavors` | boolean | `true` | Show individual flavor selection (resolution/bitrate) in the download menu |
| `displayCaptions` | boolean | `true` | Show caption download options in the download menu |
| `displayAttachments` | boolean | `true` | Show attachment download options in the download menu |

```javascript
plugins: {
  downloads: {
    flavorParamIds: '',
    displayFlavors: true,
    displayCaptions: true
  }
}
```

#### KAVA (`kava`)

Built-in analytics event reporting that sends playback metrics to the Kaltura Analytics system.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `partnerId` | number | (from provider) | Override the partner ID for analytics reporting |
| `entryId` | string | (from media) | Override the entry ID for analytics reporting |
| `ks` | string | (from provider) | Override the KS for analytics reporting |
| `tamperAnalyticsHandler` | function | `null` | Callback to modify analytics events before sending. Receives the event object and returns modified event or `false` to cancel. |
| `customVar1` | string | `""` | Custom variable 1 (passed to analytics backend) |
| `customVar2` | string | `""` | Custom variable 2 |
| `customVar3` | string | `""` | Custom variable 3 |

```javascript
plugins: {
  kava: {
    customVar1: 'department-engineering',
    customVar2: 'campaign-q4'
  }
}
```

Three shared infrastructure packages power the interactive plugins: `kaltura-cuepoints` (temporal markers), `ui-managers` (shared UI components), and `timeline` (timeline visualization). These are bundled automatically when the plugin is enabled.

> Plugin availability depends on your player configuration (`uiConfId`). Configure plugins in the KMC under Studio > TV Platform (select a player, then edit its plugins) or via the `plugins` config block in `KalturaPlayer.setup()`.

### Player Runtime API

After the player is created with `KalturaPlayer.setup()`, you can control plugins and player behavior at runtime using the service and plugin APIs.

#### player.getService(name)

Retrieve a plugin service by name. Returns `undefined` if the plugin is not loaded for the current player configuration.

```javascript
var svc = player.getService('dualScreen');
if (svc) {
  svc.ready.then(function() {
    console.log('Dual Screen service is ready');
  });
} else {
  console.log('Dual Screen plugin not available in this player config');
}
```

| Method | Returns | Description |
|--------|---------|-------------|
| `player.getService(name)` | service object or `undefined` | Get a plugin's service interface. The `name` is the service registration name (e.g., `'dualScreen'`, `'sidePanels'`). |
| `service.ready` | Promise | Resolves when the service has finished initializing (e.g., secondary media is loaded for dual screen). |

#### player.plugins.*

Access plugin instances directly to call runtime methods. Each plugin is accessible by its config key:

```javascript
// Access the Dual Screen plugin instance
var ds = player.plugins.dualscreen;

// Switch layouts at runtime (after service is ready)
var svc = player.getService('dualScreen');
svc.ready.then(function() {
  ds._switchToPIP({ force: true }, true);        // Switch to PiP layout
  ds._switchToSideBySide({ force: true }, true);  // Switch to side-by-side
  ds._switchToSingleMedia({ force: true }, true); // Switch to single view
  ds._switchToInversePIP({ force: true }, true);  // Switch to inverse PiP
});
```

> `player.configure()` only sets the initial config. It does not trigger runtime changes for plugins like Dual Screen. To control plugins at runtime, use the plugin instance methods (e.g., `player.plugins.dualscreen._switchTo*()`) after the service `ready` promise resolves.

#### player.configure(config)

Updates player configuration at runtime. Effective for `playback` and `ui` settings, but **not** for plugin runtime state changes (plugins read config only at setup time):

```javascript
// These work at runtime:
player.configure({ playback: { volume: 0.5 } });
player.configure({ ui: { css: '.custom { color: red; }' } });

// This does NOT trigger a plugin layout change at runtime:
player.configure({ plugins: { dualscreen: { layout: 'sbs' } } });
// Instead, use: player.plugins.dualscreen._switchToSideBySide({ force: true }, true);
```

#### player.destroy()

Destroys the player instance, removing all event listeners, stopping playback, and cleaning up the DOM. Call this when removing the player from the page:

```javascript
player.destroy();
```
