Skip to main content

Events Reference

The player raises an event whenever something happens: playback starts, the viewer seeks, an ad plays, a link is clicked, an error is recovered. Each event has a stable, dotted id such as media.playing or timedLink.click, and listen() is how you subscribe to it.

const stop = vpPlayer().listen("media.playing", (e) => {
console.log(e.eventId, e.currentTime);
});

// later, when the subscription is no longer needed
stop();

Subscribing with listen()

listen(id, handler, options) is available from player version 2.10.0. Older versions do not have it, so a page that has to run against an earlier release should use on() from the Legacy Listeners section instead.

It behaves the same way for every event:

  • The handler receives exactly one object, and that object always carries eventId. This holds for native media events too.
  • It returns a function that removes the subscription, so there is no need to keep the handler reference around.
  • It does not stop the event from propagating, so your page's own document-level listeners keep working.
  • An unknown id throws instead of registering a listener that never fires. vpPlayer().listenableEvents() returns every id the player accepts.
  • listen("*", handler) receives every event that has an id. Check e.eventId to tell them apart.
OPTIONDESCRIPTION
oncetrue removes the subscription after the first call.
signalAn AbortSignal. Aborting it removes the subscription, so one controller can tear down several at once.
const controller = new AbortController();
vpPlayer().listen("media.playing", onPlay, { signal: controller.signal });
vpPlayer().listen("media.paused", onPause, { signal: controller.signal });
vpPlayer().listen("media.completed", onComplete, { once: true });

// removes the playing and paused subscriptions together
controller.abort();

listen() needs a player that has finished setting up. Calling it earlier throws. Either call it inside the promise returned by setup(), or wait for the vp-listen-ready event on window:

vpPlayer("vp-player").setup(config).then(() => {
vpPlayer("vp-player").listen("media.ready", (e) => {
console.log(`Ready: ${e.title}, ${e.duration}s`);
});
});

Media Events

IDFIRES WHENPAYLOAD
media.readyThe player has initialized and is ready for API calls. This is the earliest point at which any method should be called.currentTime, id, duration, title
media.firstFrameThe first video frame has rendered. Content playback has genuinely begun.
media.playingThe player entered the playing state. One of the flags says why: a fresh start, a resume after pause, or a resume after buffering.currentTime, autoStart, begin, afterBuffer, resume, afterPause
media.pausedThe player entered the paused state.currentTime, reason
media.bufferingPlayback stopped to buffer. This is the browser's own waiting event.
media.bufferChangedMore of the video was buffered. buffered is the seconds buffered ahead, to two decimals.currentTime, buffered
media.bufferFullThe whole video is buffered.currentTime
media.bufferStalledPlayback ran out of buffer on an HLS stream. Fires once per stall.currentTime
media.timeUpdateThe playback position advanced. Fires about every 5 seconds while playing.currentTime, videoDuration, state
media.seekingA seek started.currentTime
media.seekedThe playback position was moved. currentTime is the position after the seek. skipAmount and direction (forward or rewind) are present when the seek came from a skip button or key.currentTime, skipAmount, direction
media.beforeCompleteThe video is about to complete. The player has not yet shown the replay screen or advanced the playlist, so this is the moment to act on the ending.currentTime
media.completedThe video finished playing.currentTime
media.errorA critical playback error occurred.currentTime, code, details

Player Events

IDFIRES WHENPAYLOAD
player.fullscreenChangedThe player entered or left fullscreen.currentTime, fullscreen
player.floatingChangedThe player entered or left the floating mode.currentTime, floating
player.viewableChangedThe player entered or left the viewport.currentTime, viewable
player.resizedThe player container changed size or crossed a layout breakpoint.width, height, breakpoint
player.interactedThe viewer clicked, tapped or pressed a key on the player. source is click, touch or keyboard.currentTime, source
volume.muteChangedThe player was muted or unmuted.currentTime, muted
quality.changedThe quality level changed. level is the level index, or null when auto quality has not settled yet, and reason is manual or auto.currentTime, level, reason
quality.switchedAdaptive streaming moved to another level on its own. Differs from quality.changed, which also covers the viewer's own pick.currentTime, level
quality.levelsUpdatedThe list of available quality levels changed. levels is an array of { index, height, bitrate }.levels
drm.stateChangedThe DRM session changed state. state is initializing, active, key_expired or error; error is present in the error state.state, error
cast.stateChangedA property of the Chromecast session changed, for example field playerState with value PLAYING. event is the raw Cast SDK object and is read-only.field, value, event
subtitle.changedSubtitles were switched on or off. reason is manual or inversion.state, reason
playlist.itemChangedThe playlist moved to another video. Fires before the new video starts.id, title, file, duration

UI Events

IDFIRES WHENPAYLOAD
ui.nextClickThe viewer clicked the next video button.currentTime
ui.relatedOpenedThe related videos panel opened. reasonForOpen is click or pause.currentTime, reasonForOpen
ui.sharingOpenedThe share panel opened.currentTime
IDFIRES WHENPAYLOAD
timedLink.showA timed link became visible or switched to another link.title, url, time, duration, currentTime
timedLink.clickThe viewer clicked a timed link.title, url, time, duration, currentTime

time is the link's configured cue point in seconds and never changes, while currentTime is the playback position at the moment the event fired. A link cued at 30 that the viewer clicks at 0:44 reports time: 30 and currentTime: 44. On a livestream time is always 0 and currentTime is a position on the stream's own timeline, so it is not comparable across viewers.

Ad Events

IDFIRES WHENPAYLOAD
ad.readyThe ad manager loaded an ad and it is about to play.videoCurrentTime, adCurrentTime, adDuration, adTitle, adId, adPlayId, adPosition, muted
ad.impressionThe ad reported its impression, once it started rendering.currentTime, adDuration, adTitle, adId, adPlayId, adPosition
ad.playingAn ad started playing or resumed. begin marks the start, resume an unpause.videoCurrentTime, adCurrentTime, adDuration, adTitle, adId, adPlayId, adPosition, muted, begin, resume
ad.pausedAn ad was paused.videoCurrentTime, adCurrentTime, adDuration, adTitle, adId, adPlayId, adPosition, muted
ad.timeUpdateAd playback progressed.currentTime, adPlayId
ad.skippableThe ad can now be skipped.currentTime, adPlayId
ad.firstQuartile, ad.secondQuartile, ad.thirdQuartileThe ad reached a quarter, half and three quarters of its duration. The payload is the analytics body described under Analytics Events.userId, uniqueViewId, adId, videoId, adScheduleId, projectId, playerId, playlistId, eventType, event, subtitles, duration, videoProjectId, isLive, autoStart, embedOrigin
ad.skippedThe viewer skipped the ad.videoCurrentTime, adCurrentTime, adDuration, adTitle, adId, adPlayId, adPosition, muted
ad.clickThe viewer clicked the ad to open its landing page.currentTime, adPlayId
ad.muteChangedThe ad was muted or unmuted.videoCurrentTime, adCurrentTime, adDuration, adTitle, adId, adPlayId, adPosition, muted
ad.completedThe ad finished playing.videoCurrentTime, adCurrentTime, adDuration, adTitle, adId, adPlayId, adPosition, muted
ad.bidReturnedA header bidding auction returned. type is edgeroll or midroll and position is the ad break index or the midroll time.success, type, position
ad.errorAn error stopped the ad. Content resumes. errorType and vastErrorCode come from the IMA SDK and are absent under the vp engine.currentTime, adPosition, currentAdIndex, errorCode, errorMessage, errorType, vastErrorCode

In ad.playing, ad.paused, ad.muteChanged, ad.skipped and ad.completed, videoCurrentTime is the position of the content video and adCurrentTime the position inside the ad.

Playback Health Events

These events report whether playback worked, so integrations can monitor stream health from the page. Only unexpected failures are reported. Geo-blocked videos, videos that are not published yet, password prompts and paywall locks are intentional outcomes and are not counted as failures.

IDFIRES WHEN
playback.attemptOnce per view when playback is first requested, either by autoplay or by the viewer. Adds autoStart (true when the player started playback on its own) to the shared payload below.
playback.fatalErrorAt most once per view, when the player gives up on the video and shows the error screen. A fatal error always implies an attempt, so the player backfills playback.attempt if it never fired.
playback.errorRecoveredEvery time an error that would have been fatal was survived by a fallback and playback resumed. Adds recoveryReason, recoveryMethod and timeUntilRecovery to the shared payload below.
playback.autoplayBlockedThe browser blocked autoplay, after both the unmuted and the muted attempt were rejected. This is a browser policy, not a playback failure, so no fatal error is reported. Payload: autoStart.
playback.interruptedOnce when a livestream already in playback is disrupted by a sustained buffer stall or a network or media error. Payload: reason (bufferStall, networkError, mediaError or otherError), multiStream, currentTime. Livestreams only.
playback.recoveredOnce after a livestream recovers from an interruption. Payload: currentTime. Livestreams only.
playback.lockedA paywall, registration or general lock stopped playback. Payload: currentTime, reason (paid, registration or general), playerId.
playback.accessDeniedPlayback was refused for a policy reason such as geo-blocking or an unpublished video. Payload: videoId, reason.
playback.preparingThe video is still being encoded, the preparing screen is shown and the player polls for it. Payload: videoId.
playback.preparedThe prepared video became available and playback continues. Payload: videoId. Giving up is reported as playback.fatalError with reason preparingFailed.

A view is one watch of one video: moving to the next video in a playlist, setting a new video through the API, and replaying a finished video each start a new view. All events from the same view share the same uniqueViewId, which is the property to join them on. Ads belong to the view of the video they play in and never produce an attempt of their own.

playback.attempt, playback.fatalError and playback.errorRecovered share the following payload:

PROPERTYDESCRIPTION
eventattempt, fatal-error or error-recovered.
uniqueViewIdThe identifier shared by all events of the current view.
userIdThe viewer identifier used by analytics.
videoIdThe ID of the current video.
playerIdThe configuration ID of the player.
projectIdThe project the player belongs to.
fileThe source URL of the current video, with the query string removed.
isLiveWhether the current video is a live stream.
currentTimeThe playback position when the event was fired.
playerSdkVersionThe version of the player that produced the event, so attempt and error rows can be grouped by player version.

playback.fatalError adds the following properties:

PROPERTYDESCRIPTION
categoryThe broad class of the failure: network, media, drm, unsupported or other.
fatalReasonThe specific reason, see the table below.
locationA numeric code matching fatalReason, useful for grouping in dashboards.
phasestart when playback had not begun yet, playback when the video was already playing.
detailsThe underlying error detail as reported by the streaming library or the browser.
httpCodeThe HTTP status of the failed request, when the failure came from a network response.
extraDetailsA JSON string with additional context, including recoveredErrors, the number of errors recovered in this view.

The reasons and their codes are grouped by the nature of the failure: 1xx the source could not be reached, 2xx the content was unusable, 3xx the browser could not decode it, 4xx encryption or DRM, 5xx the environment or anything else.

REASONCODEMEANING
fileMissing101No source URL was provided for the video.
fileUnreachable102The manifest or file could not be loaded.
levelUnreachable103A quality rendition could not be loaded.
preparingFailed104The video never became available while the preparing screen was polling.
failoverExhausted105Every configured stream was tried and none of them played.
networkError106A network failure without a more specific reason.
manifestEmpty201The manifest loaded but contained no streams.
streamEmpty202The stream loaded but contained no segments.
segmentError203A segment failed to load or parse.
codecUnsupported301The browser cannot decode the codecs in the stream.
decodeError302The browser failed to decode the media.
mediaRecoveryExhausted303Media recovery was attempted repeatedly and playback still did not resume.
keyError401An encryption key could not be loaded or applied.
drmError402The DRM license could not be obtained or was rejected.
browserUnsupported501The browser cannot play the source at all.
playRejected502The browser rejected the play request for a reason other than its autoplay policy.
otherError503An unrecoverable failure that fits none of the above.

playback.errorRecovered reports recoveryReason (networkError, mediaError, bufferStall or otherError) together with the recoveryMethod that worked (streamSwitch, recoverMediaError, codecSwap, levelDrop or retry) and timeUntilRecovery, the milliseconds from the first recovery attempt until media was flowing again.

note

Live streams keep retrying instead of giving up on network failures, so the reasons a live stream can report are not the same as the ones a VOD video can report. When you build health dashboards, split them on isLive. The two are not comparable. playback.interrupted and playback.recovered cover live streams that break and come back while already playing.

Analytics Events

These mirror the rows the player posts to the analytics endpoint, so a page can observe exactly what is being reported. They all share one payload: userId, uniqueViewId, adId, videoId, adScheduleId, projectId, playerId, playlistId, eventType, event, subtitles, duration, videoProjectId, isLive, autoStart, embedOrigin.

IDFIRES WHEN
analytics.embedLoadedThe player embed loaded on the page.
analytics.startedThe video started.
analytics.firstQuartileThe video reached a quarter of its duration.
analytics.secondQuartileThe video reached half of its duration.
analytics.thirdQuartileThe video reached three quarters of its duration.
analytics.completedThe video completed.
analytics.twentyView20 seconds of the video have been watched.
analytics.trueViewThe view counts as a true view, meaning the viewer interacted with the player.
analytics.watchStateEvery 10 seconds while playing. Adds playerState, muted, currentTime, segmentWatchTime, volume and fullscreen, all as strings because the watch state endpoint expects them that way.
analytics.watchedTimeThe accumulated watch time was reported. Payload is id, type and value only.

Legacy Listeners

Integrations written before listen() use on() and its companions. They keep working, and every name they ever accepted still resolves to the right event. New code should use listen(). Every event the player raises now has an id, so nothing requires on() any more.

LISTENERDESCRIPTIONEXAMPLE
on(event)Listens for an event on a player. If the player is removed and set up again, the listener has to be added again.vpPlayer().on(event, callback)
once(event)Listens for an event a single time. The listener removes itself once it has fired.vpPlayer().once(event, callback)
off(event)Removes a listener added with on(). Pass the same callback reference you passed to on().vpPlayer().off(event, callback)
offAll(event)Removes every listener registered for an event on this player.vpPlayer().offAll(event)

on() accepts the id, the older vp-* name and the camelCase alias interchangeably, so on("media.playing"), on("vp-state-playing") and on("play") all subscribe to the same event. The aliases are:

IDALIASES
media.readyready, vp-ready
media.firstFramefirstFrame, vp-first-frame
media.playingplay, vp-state-playing
media.pausedpause, vp-state-paused
media.bufferingwaiting
media.bufferChangedbufferChange, vp-buffer-change
media.bufferFullbufferFull, vp-buffer-full
media.timeUpdatetime, vp-time
media.seekingseek, vp-seeking
media.seekedseeked, vp-seeked
media.beforeCompletebeforeComplete, vp-before-complete
media.completedvp-video-completed
media.errorerror, vp-playback-error
player.fullscreenChangedfullscreen, vp-fullscreen
player.floatingChangedfloat, vp-floating
player.viewableChangedviewable, vp-viewable
volume.muteChangedmute, vp-muted
quality.changedlevelsChanged, vp-quality-change
quality.switchedvisualQuality, hlsLevelSwitched
cast.stateChangedcast, vp-casting
subtitle.changedvp-subtitle-change
playlist.itemChangedplaylistItem, vp-playlist-next
ui.nextClicknextClick, vp-next-click
ui.relatedOpenedrelatedOpen, vp-related-open
ui.sharingOpenedsharingOpen, vp-sharing-open
timedLink.showtimedLinkShow, vp-timed-link-show
timedLink.clicktimedLinkClick, vp-timed-link-click
ad.readyadReady, ad-ready
ad.impressionadImpression, vp-ad-impression
ad.firstQuartileanalytic-ad-25%-completed
ad.secondQuartileanalytic-ad-50%-completed
ad.thirdQuartileanalytic-ad-75%-completed
ad.playingadPlay, vp-ad-play
ad.pausedadPause, vp-ad-pause
ad.timeUpdateadTime, vp-ad-progress
ad.skippableadSkippable, vp-ad-skippable
ad.skippedadSkipped, adSkip, vp-ad-skip
ad.clickadClick, vp-ad-click
ad.muteChangedadMute, vp-ad-mute
ad.completedadComplete, vp-ad-complete
ad.erroradError, vp-ad-error
playback.attemptplayAttempt, vp-play-attempt
playback.fatalErrorfatalError, vp-fatal-error
playback.errorRecoverederrorRecovered, vp-error-recovered
playback.autoplayBlockedautoplayBlocked, vp-autoplay-blocked
playback.interruptedinterrupted, vp-video-interrupted
playback.recoveredrecovered, vp-video-recovered
playback.lockedvideoLocked, vp-video-locked
playback.accessDeniedvp-video-access-denied
playback.preparingvp-video-preparing
playback.preparedvp-video-prepared
media.bufferStalledvp-buffer-stalled
drm.stateChangeddrmState, vp-drm-state
quality.levelsUpdatedvp-levels-updated
player.resizedvp-resize
player.interacteduser-interaction
ad.bidReturnedvp-bid-return
analytics.embedLoadedembed-loaded
analytics.startedstarted, video-started
analytics.firstQuartileanalytics-25%-completed
analytics.secondQuartileanalytics-50%-completed
analytics.thirdQuartileanalytics-75%-completed
analytics.completedcomplete, video-completed
analytics.twentyViewtwenty-view
analytics.trueViewtrue-view
analytics.watchStatevideo-state
analytics.watchedTimewatchedtime

The vp-event catch-all

on("vp-event", ...) receives every event the player raises. The payload carries eventName, the camelCase alias from the table above, and for events that have an id also eventId. listen("*", ...) is the equivalent for events with an id, including the native media.buffering, and delivers a copy of the payload per subscriber.

vpPlayer().on("vp-event", (e) => {
if (e.eventId === "media.playing") {
console.log("Playback started");
}
});
For pages that listen on the DOM directly

The ids are also the DOM event names the player dispatches on its container. The events bubble, so a listener on document or window receives them as well. Until 2.10.0 the container dispatched the legacy names from the table above, and pages that attached addEventListener("vp-fatal-error", ...) to the container, document or window received those events even though the DOM name was never part of the documented API. From 2.10.0 the player dispatches the id instead for the events it raises itself, and from 2.10.1 for all of them, so such a listener stops firing without an error.

If your page does this, switch to listen("playback.fatalError", ...). It works on any element the player is set up in, survives the rename, and is the API we test and support. If you must keep a DOM listener, use the id as the DOM name: addEventListener("playback.fatalError", ...). The vp-event catch-all is unchanged and still bubbles under its old name, with eventName carrying the legacy name and eventId the id.

Do not rely on the legacy DOM names continuing to fire. vp-video-locked is currently dispatched alongside playback.locked as a courtesy for existing integrations, and it is the only one. It will be removed in a future release.