Wistia

to the tippy ↑

JavaScript Player API

Warning: You are heading into developer territory!

Our APIs are designed for programmers. If our APIs were a ski trail, they'd be a nice blue cruiser - anyone can do it, but expect to fall a few times if you don't know what you're doing!

If you're new to web development, we recommend taking a lesson before you hit our slopes. Try a class from Frontend Masters or Skillshare!

The Wistia video player has a JavaScript API which supports a number of ways to interact with and control the video player. It uses the same interface for both Flash and HTML5 versions of the player, and provides convenience functions to accomplish common goals.

Get started

To use the Player API, you need a "handle" to it. When we use the term "handle", we mean a javascript variable that is associated with a Wistia video and defines the Player API methods. You'll see the variable video--and other similar variables--used frequently in examples in these docs; these are all Player API handles. Since Wistia embeds are initialized asynchronously, we recommend the following patterns to acquire player API handles for your videos.

Use window._wq to get a video handle

The first and easiest way is to push a function onto the initialization queue. The handle will be given as an argument of the callback function.

With Standard embeds

We push an object of the form { matcher: callback } onto the queue. Embeds that match--as described in the Wistia.api(matcher) section below--will run the function.

wistia_html.html
1
2
3
4
5
6
7
8
<script src="//fast.wistia.com/assets/external/E-v1.js" async></script>
<div class="wistia_embed wistia_async_abcde12345" style="width:640px;height:360px;"></div>
<script>
window._wq = window._wq || [];
_wq.push({ "abc": function(video) {
  console.log("I got a handle to the video!", video);
}});
</script>

With iframe embeds

The exact same syntax will work with iframe embeds too:

wistia_html.html
1
2
3
4
5
6
7
8
<iframe src="//fast.wistia.net/embed/iframe/abcde12345" allowtransparency="true" frameborder="0" scrolling="no" class="wistia_embed" name="wistia_embed" allowfullscreen mozallowfullscreen webkitallowfullscreen oallowfullscreen msallowfullscreen width="640" height="360"></iframe>
<script src="//fast.wistia.net/assets/external/E-v1.js" async></script>
<script>
window._wq = window._wq || [];
_wq.push({ "abc": function(video) {
  console.log("I got a handle to the video!", video);
}});
</script>

Here's what's happening:

  1. We make sure window._wq is defined as an array.
  2. We push a matcher ("abc") associated with a callback function onto the queue.
  3. E-v1.js loads and the video data is fetched in the background.
  4. The callback function runs when the video has data.

Note that you can push functions onto window._wq at any time—including after E-v1.js has loaded—and expect it to be quickly executed.

You can also target all videos on the page

If you'd like to run your function on all videos on the page, you can use the special "_all" matcher:

wistia_js.js
1
2
3
4
window._wq = window._wq || [];
_wq.push({ "_all": function(video) {
  console.log("This will run for every video on the page. Right now I'm on this one:", video);
}});

This will run immediately on any videos that are currently on the page. It will also be run immediately on videos injected after the page loads.

Wistia.api(matcher)

If you're quite sure your video code will be running after the video has already loaded, you can use Wistia.api(matcher) to get a handle synchronously. If no match is found, Wistia.api(matcher) will return null.

wistia_js.js
1
2
var video = Wistia.api("abc");
console.log("I got a handle to the video!", video);

The matcher argument is compared to the container ID or the hashed ID of the video.

wistia_html.html
1
2
<script src="//fast.wistia.com/assets/external/E-v1.js" async></script>
<div id="my_video" class="wistia_embed wistia_async_abcde12345" style="width:640px;height:360px;"></div>

To access it, you can reference a portion of the video's container ID or hashed ID. For example, any of the following calls would get the same handle to the video:

wistia_js.js
1
2
3
4
5
6
7
8
var video1 = Wistia.api("my_video");
var video2 = Wistia.api("abcde12345");
var video3 = Wistia.api("my_");
var video4 = Wistia.api("abc");

console.log(video1 === video2); // true
console.log(video2 === video3); // true
console.log(video3 === video4); // true

If the same video appears several times on the page, Wistia.api("hashedid") will only return the first instance. If you need a handle for each instance, you'll need to assign unique container IDs and reference those. When assigning custom container IDs, numeric-only IDs are not allowed.

If the first 3 letters of the hashed ID are used, there is a 1 in 46,656 chance that you will have a collision with another video on the page. To be safe, if you have many videos on a page, you may want to be more verbose. For example, increasing your matcher to 4 characters decreases the chance of collision to 1 in 1,679,616. But short access is convenient and can be used on most pages where the number of videos is small.


Methods

addToPlaylist(hashedId, [options], [position])

A video has a "playlist", which is a list of videos to play in sequence. Each playlist must have a unique list of hashed IDs; a hashed ID cannot appear twice within the same playlist.

Use addToPlaylist to push more videos onto the queue. When a video is finished playing, it will play the next one in its playlist.

wistia_js.js
1
2
3
video.addToPlaylist("abcde12345", {
  playerColor: "00ff00"
});

The position argument lets you define where in the playlist the video should be added. It can take any of these forms:

wistia_js.js
1
2
3
4
5
6
7
8
9
10
// Play abcde12345 before hashedid
video.addToPlaylist("abcde12345", {}, { before: "hashedid" });

// Play abcde12345 after hashedid
video.addToPlaylist("abcde12345", {}, { after: "hashedid" });

// Put abcde12345 in the first position
// Note that this will not automatically replace the video too. To do that, you
// should make use of `replaceWith`. See the pre-roll video example below.
video.addToPlaylist("abcde12345", {}, { index: 0 });

Before using this, you might want to see if embed and playlist links covers your use case.

NOTE: This method currently does not work with iframe embeds.

aspect()

Returns the aspect ratio (width / height) of the originally uploaded video.

wistia_js.js
1
2
3
4
5
6
7
if (video.aspect() < 1) {
  console.log("vertical video");
} else if (video.aspect() > 1) {
  console.log("horizontal video");
} else {
  console.log("This video is square.");
}

bind(eventType, callbackFn)

Runs a callback function when a specific event is triggered. Refer to the Events section to see how to respond to the different types events.

wistia_js.js
1
2
3
4
5
6
7
8
9
10
11
video.bind("play", function() {
  console.log("the video played!");
});

video.bind("timechange", function(t) {
  console.log("the time changed to " + t);
});

video.bind("end", function(t) {
  console.log("the video ended");
});

duration()

Returns the duration of the video in seconds. This will return 0 until video.hasData() is true.

wistia_js.js
1
showVideoDurationOnMyPage(video.duration())

email()

Returns the email associated with this viewing session. If no email is associated, it will return null.

An email can be associated with a viewing session by:

  • calling video.email('the@email.com')
  • setting the email embed option
  • entering their email via Turnstile
  • adding wemail=the%40email.com to the URL of the page.

Once an email has been saved for a viewer, it will persist for that web page until they clear their localStorage.

wistia_js.js
1
recordViewerEmail(video.email());

email(val)

Associates the view of this video with the given email value. This email will appear in stats for the video.

wistia_js.js
1
video.email(emailForThisUserInMySystem);

embedded()

Returns true if the video has been embedded, false if it hasn't yet. We define "embedded" as the video's markup having been visibly injected into the DOM.

wistia_js.js
1
2
3
if (video.embedded()) {
  // do this thing
}

embedded(callbackFn)

Runs the callback function immediately if the video has been embedded already. If it has not been embedded yet, it queues the callback function until it is embedded.

wistia_js.js
1
2
3
4
video.embedded(function() {
  // run this function when/if the video is embedded.
  // this will usually fire immediately after `hasData(callbackFn)`.
});

eventKey()

Returns the event_key for the current viewing session. You can get all events for your account from the Stats API.

hasData()

Returns true if the video has received data from the Wistia server, false if not. The data includes information like which video files are available, the name and duration of the video, and its customizations.

hasData(callbackFn)

Runs the callback function immediately if the video has received data already. If it has not received data yet, it queues the callback function until it is received.

wistia_js.js
1
2
3
4
video.hasData(function() {
  // run this function when/if the video has data.
  // this will usually fire immediately before `embedded(callbackFn)`.
});

hashedId()

Returns the hashed ID associated with this video. The hashed ID is an alphanumeric string that uniquely identifies your video in Wistia.

wistia_js.js
1
recordPlayedVideo(video.hashedId(), video.name());

height()

Returns the current height of the video container in pixels.

wistia_js.js
1
2
// e.g. set the height of <div id="next_to_video"> to match the video.
$("#next_to_video").height(video.height());

height(val, [options])

Sets the height of the video container to val in pixels. It is expected that val is an integer. Decimal or string values will be truncated.

If constrain: true is passed as an option, then the width of the video will also be updated to maintain the correct aspect ratio.

wistia_js.js
1
2
video.height(360);
video.height(400, { constrain: true });

look()

Note

For 360° video only. To use the 360° player for a video, first enable the "360° Video" option in the Beta Features section of your Account Settings, then head to the Controls section of the Customize panel and check the "This is a 360° video" checkbox.

Returns an object that represents where the viewer is currently looking. The object contains the current heading, pitch, and fov (field of view) all in degrees.

wistia_js.js
1
video.look() //=> { heading: 90, pitch: 5, fov: 120 }

A heading of 0 is straight ahead. A heading of 90 is looking to the right, -90 is to the left, and 180 is looking directly back.

A pitch of 0 is looking straight ahead. A pitch of 90 is looking straight up, and -90 is straight down.

fov is the horizontal field of view in degrees. A fov of 120 indicates that the viewer is seeing one third of the whole scene (120°/360° = 1/3).

look(options)

For 360° video only.

Sets where the viewer is looking. Provide one or more of heading, pitch, and fov.

wistia_js.js
1
2
3
4
video.look({ heading: 90 }) //=> look to the right
video.look({ pitch: 45 }) //=> look up 45°
video.look({ fov: 180 }) //=> expand the field of view so the viewer can see half the scene
video.look({ heading: 180, pitch: 0 }) //=> look straight back

By default, the view will tween to its new position — that is, it will smoothly animate to the new view. If you'd like it to snap to the new view without any animation, set tween to false like this:

wistia_js.js
1
video.look({ heading: -90, tween: false })

name()

Returns the name of the video, as defined in the Wistia application. Returns null until hasData() is true.

wistia_js.js
1
console.log("Thank you for watching " + video.name() + "!");

pause()

Pauses the video. If this is called and the video's state is "playing", it's expected that it will change to "paused".

wistia_js.js
1
2
3
$("#custom_pause_button").click(function() {
  video.pause()
});

percentWatched()

Returns the percent of the video that has been watched as a decimal between 0 and 1. This is equivalent to computing video.secondsWatched() / Math.floor(video.duration()).

wistia_js.js
1
2
3
4
5
6
7
8
9
$("#next_page").click(function() {
  if (video.percentWatched() > 0.9 && video.percentWatched() < 0.99) {
    if (confirm("But you're so closed to finishing the video -- there's a prize at the end! Move on anyway?")) {
      goToNextPage();
    }
  } else {
    goToNextPage();
  }
});

play()

Plays the video. If this is called, it is expected that the state will change to "playing".

NOTE: On iOS and other mobile devices, videos cannot be issued the "play" command outside the context of a user-driven or video event. For example, "click" and "touch" events are user-driven, and video events include "pause" and "end" (you can bind to these using video.bind(eventType, callbackFn) described above. Because of this restriction, you should avoid calling play() within a setTimeout callback or other asynchronous functions like XHR or javascript promises.

Also for this reason, the play() method will never work with the iframe API on mobile. This is because the iframe API makes use of javascript's postMessage API, which is by its nature asynchronous.

Please refer to Apple's Documentation for the reasons behind this behavior.

playbackRate(r)

Sets the playback rate of the video, from 0 to infinity and beyond (though we would recommend keeping things between 0.5 and 2).

wistia_js.js
1
video.playbackRate(1.25); // sets the playback rate to 1.25x regular speed.

Note

The playbackRate method does not work with the Flash player, which Wistia will sometimes fall back to for legacy browser support.

ready()

Returns true if the video is ready to be played, false if it is not. A video is "ready" if:

  1. it has data from the server,
  2. it is embedded in the DOM,
  3. its javascript interface is available,
  4. metadata required to play is loaded,
  5. it is not hidden via display: none.

The visibility requirement is grounded in practicality. That is, Flash videos cannot be played when they are hidden via display: none, so supporting the opposite with HTML5 videos would set up a fundamental difference between our embed types. But it is also a common use case to embed a video in a hidden tab or a custom lightbox. In these cases, if the video has autoPlay=true, it will still defer playing until it becomes visible.

If you must have your video be hidden AND ready, consider moving it offscreen like position: absolute; left: -99999em instead of using display: none.

ready(callbackFn)

Runs the callback function immediately if the video is ready to be played. If it is not ready yet, it queues the callback function until it is ready.

wistia_js.js
1
2
3
4
video.ready(function() {
  // run this function when/if the video is ready.
  // this will usually fire shortly after `embedded(callbackFn)`.
});

remove()

Removes the video from the page cleanly. This will do garbage collection, cancel asynchronous operations, and stop the video from streaming, none of which are reliable if the video is simply removed from the DOM, e.g. $(".wistia_embed").empty().remove().

wistia_js.js
1
2
3
4
5
6
7
8
9
10
11
function nextPage() {
  $.get("/next_page.html", function() {
    // If a video is defined for this page, remove it cleanly before it is
    // removed from the DOM.
    if (currentVideo) {
      currentVideo.remove();
      currentVideo = null;
    }
    $("#the_content").html(nextPageContent);
  });
}

replaceWith(hashedId, [options])

Replaces the content of the current video with the video identified by hashedId. This video will be loaded with all its customizations, which can be overridden in the options object. This method can be used in conjunction with addToPlaylist(hashedId, [options]) to create custom playlist implementations.

In addition to the normal embed options, you can set the transition option, which defines how to visually transition to the new video. Available values are "slide", "fade", "crossfade", and "none". By default, "fade" is used.

wistia_js.js
1
2
3
$("#video_abcde12345").click(function() {
  video.replaceWith("abcde12345");
});

Before using this, you might want to see if embed and playlist links covers your use case.

NOTE: This method currently does not work with iframe embeds.

secondsWatched()

Returns the number of unique seconds that have been watched for the video. This does not include seconds that have been skipped by seeking.

wistia_js.js
1
2
3
4
5
video.bind("secondchange", function() {
  if (video.secondsWatched() >= 60) {
    console.log("You've watched over a full minute of this video!");
  }
});

secondsWatchedVector()

ADVANCED. Returns an array where each index represents the number of times the viewer has watched each second of the video. For example, if a video is 10 seconds long and the viewer has watched the first three seconds, it will look like this:

[1, 1, 1, 0, 0, 0, 0, 0, 0, 0]

If the viewer has watched the entire video once and rewatched the first 5 seconds, it will look like this:

[2, 2, 2, 2, 2, 1, 1, 1, 1, 1]

This can be used to quickly determine if a viewer has missed or rewatched an important part of a video.

wistia_js.js
1
2
3
4
5
6
7
8
9
10
11
12
video.bind("end", function() {
  var watchedVector = video.secondsWatchedVector();
  var watchedImportantSeconds = 0;
  for (var i = 4; i < 9; i++) {
    if (watchedVector[i] > 0) {
      watchedImportantSeconds += 1;
    }
  }
  if (watchedImportantSeconds < 2) {
    console.log("You should really go back and watch seconds 5 through 10. They're important!");
  }
});

state()

Returns the current state of the video as a string. Possible values are "beforeplay", "playing", "paused", and "ended".

The most common use case for state() is implementing a play/pause toggle button.

wistia_js.js
1
2
3
4
5
6
7
$("#toggle_play").click(function() {
  if (video.state() === "playing") {
    video.pause();
  } else {
    video.play();
  }
});

time()

Returns the current time of the video as a decimal in seconds.

wistia_js.js
1
2
3
$("#leave_comment").click(function() {
  $("#comment").html(commentData + "<span class='time'>left at " + video.time() + " seconds</span>")
});

time(val)

Seeks the video to the time defined by val. It is expected that val is a decimal integer specified in seconds. This method will maintain the state of the video: if the video was playing, it will continue playing after seek. If it was not playing, the video will be paused.

NOTE: On iOS, when seeking from the "beforeplay" state, video.time(val) is subject to the same restrictions as video.play(). However, there is a bit of nuance. If you call video.time(30) before play, the video will not play per the restrictions. But once the viewer clicks the video to play it, it will begin playing 30 seconds in.

unbind(eventType, callbackFn)

Unbind a callback that was setup with bind(eventType, callbackFn).

wistia_js.js
1
2
3
4
5
6
7
var onPlayFunction = function() {
  doThisThing();
};
video.bind("play", onPlayFunction);
$("#dont_do_this_thing_ever").click(function() {
  video.unbind("play", onPlayFunction);
});

Since binding until a condition is met is a common operation with videos, the Player API also supports anonymous function unbinding.

wistia_js.js
1
2
3
4
5
6
video.bind("timechange", function(t) {
  if (t > 30) {
    console.log("Made it past 30 seconds! This will never fire again.");
    return video.unbind;
  }
});

videoHeight()

Returns the height of the video itself in pixels, without anything extra. For example, if the socialbar is enabled and video.height() returns 388, then video.videoHeight() will return 360 because the height of the Social Bar is 28px.

wistia_js.js
1
$("#video_matcher").height(video.videoHeight());

videoHeight(val, [options])

Sets the height of the video to val in pixels. It is expected that val is an integer. Decimal or string values will be truncated.

If constrain: true is passed as an option, then the width of the video will also be updated to maintain the correct aspect ratio.

wistia_js.js
1
2
video.videoHeight(360);
video.videoHeight(400, { constrain: true });

videoWidth()

Returns the width of the video itself in pixels, without anything extra. For example, if the Presentation Sync lab is enabled and video.width() returns 1166, then video.videoWidth() will return 640 because the width of the presentation is 526px.

wistia_js.js
1
$("#video_matcher").width(video.videoWidth());

videoWidth(val, [options])

Sets the width of the video to val in pixels. It is expected that val is an integer. Decimal or string values will be truncated.

If constrain: true is passed as an option, then the height of the video will also be updated to maintain the correct aspect ratio.

wistia_js.js
1
2
video.videoWidth(640);
video.videoWidth(640, { constrain: true });

visitorKey()

Returns the visitor_key of the person watching the video. This is used to associate multiple viewing sessions with a single person. You can use it to filter events in the Stats API.

volume()

Returns the current volume of the video as a decimal between 0 and 1. This value is not dependable until video.ready() returns true.

wistia_js.js
1
$("#custom_volume_monitor").text(Math.round(video.volume() * 100) + "%")

volume(val)

Sets the volume to val. It is expected that val is a decimal between 0 and 1.

wistia_js.js
1
2
3
$("#custom_volume_slider").on("change", function() {
  video.volume($(this).val());
});

width()

Returns the current width of the video container in pixels.

wistia_js.js
1
2
// e.g. set the width of <div id="next_to_video"> to match the video.
$("#next_to_video").width(video.width());

width(val)

Sets the width of the video container to val in pixels. It is expected that val is an integer. Decimal or string values will be truncated.

If constrain: true is passed as an option, then the width of the video will also be updated to maintain the correct aspect ratio.

wistia_js.js
1
2
video.width(640);
video.width(700, { constrain: true });

Events

Use these events when working with the bind and unbind methods.

betweentimes

Fired once when the playhead enters the interval and once when it leaves it. This can run multiple times if the viewer leaves the time interval and re-enters it, either by seeking or by playing through. This event is useful if you have page elements that should be visible only for a specific time interval.

wistia_js.js
1
2
3
4
5
6
7
video.bind("betweentimes", 30, 60, function(withinInterval) {
  if (withinInterval) {
    showMyElement();
  } else {
    hideMyElement();
  }
});

To only show it once using anonymous function unbinding:

wistia_js.js
1
2
3
4
5
6
7
8
video.bind("betweentimes", 30, 60, function(withinInterval) {
  if (withinInterval) {
    showMyElement();
  } else {
    hideMyElement();
    return video.unbind;
  }
});

To only show it once using explicit unbinding:

wistia_js.js
1
2
3
4
5
var showMyElementOnce = function() {
  showMyElement();
  video.unbind('betweentimes', 30, 60, showMyElementOnce);
};
video.bind("betweentimes", 30, 60, showMyElementOnce);

NOTE: This event currently does not fire on iframe embeds.

conversion

Fired when an email is entered in Turnstile. The type argument can be "pre-roll-email", "mid-roll-email", or "post-roll-email".

wistia_js.js
1
2
3
video.bind("conversion", function(type, email, firstName, lastName) {
  recordMyOwnData(email, firstName, lastName);
})

crosstime

Runs the callback function when the time of the video moves from before time to after time. It is expected that time is a decimal value specified in seconds.

This event is meant to be used with "gates" or CTAs. For example, perhaps you have a call to action that should appear after the 30 second mark in your video. Code to show that might look like this:

wistia_js.js
1
2
3
video.bind("crosstime", 30, function() {
  showMyCustomCTA();
});

To only show it once using anonymous function unbinding:

wistia_js.js
1
2
3
4
video.bind("crosstime", 30, function() {
  showMyCustomCTA();
  return video.unbind;
});

To only show it once using explicit unbinding:

wistia_js.js
1
2
3
4
5
var showMyCustomCTAOnce = function() {
  showMyCustomCTA();
  video.unbind('crosstime', 30, showMyCustomCTAOnce);
};
video.bind("crosstime", 30, showMyCustomCTAOnce);

NOTE: This event currently does not fire on iframe embeds.

end

Fired when the video's state changes to "ended".

wistia_js.js
1
2
3
video.bind("end", function() {
  console.log("Lenny was here.");
});

heightchange

Fired whenever the height of the embed changes. If you have element sizes or positions that depend on the height of the video, you can bind to this event.

wistia_js.js
1
2
3
video.bind("heightchange", function() {
  console.log("The height changed to " + video.height());
});

lookchange

For 360° video only. Fired when the viewer changes their heading, pitch, or field of view.

wistia_js.js
1
2
3
video.bind("lookchange", function(look) {
  console.log('Look', look.heading, look.pitch, look.fov);
});

pause

Fired when the video's state changes to "paused".

wistia_js.js
1
2
3
video.bind("pause", function() {
  console.log("The video was just paused!");
});

play

Fired when the video's state changes to "playing". This can fire multiple times for a single viewing session since the viewer can repeatedly pause and play.

wistia_js.js
1
2
3
video.bind("play", function() {
  console.log("The video was just played!");
});

secondchange

Fired when the current second of the video has changed. The second argument will always be passed as an integer. It is equivalent to Math.floor(video.time()).

Technically this is a subset of the "timechange" event, and thus will always fire after "timechange" events but before "seek" events.

wistia_js.js
1
2
3
4
5
video.bind("secondchange", function(s) {
  if (s === 30) {
    // do something at exactly 30 seconds
  }
});

seek

Our player will compare currentTime to lastTime once every 300ms and fire this event if the difference is greater than 1.5 seconds.

Technically this is a subset of the "timechange" event, and thus will always fire after both "timechange" and "secondchange".

wistia_js.js
1
2
3
video.bind("seek", function(currentTime, lastTime) {
  console.log("Whoa, you jumped " + Math.abs(lastTime - currentTime) + " seconds!");
});

timechange

Our player will compare currentTime and lastTime once every 300ms and fire this event if they are different.

Both "secondchange" and "seek" key off this event, and thus "timechange" will always fire before both "secondchange" and "seek".

wistia_js.js
1
2
3
video.bind("timechange", function(t) {
  updateCustomPlayHead(t);
});

volumechange

Fired when the volume changes.

wistia_js.js
1
2
3
video.bind("volumechange", function(v) {
  console.log("The volume changed to " + Math.round(v * 100) + "%");
});

widthchange

Fired whenever the width of the embed changes. If you have element sizes or positions that depend on the width of the video, you can bind to this event.

wistia_js.js
1
2
3
video.bind("widthchange", function() {
  console.log("The width changed to " + video.width());
});

Options

Many behaviors can be defined by setting options instead of using Player API methods. Check out the Embed Options page for a full list.


Examples

To get you making video magic as fast as possible, here are some examples of common JavaScript player API projects.

Start Video Playback at a Specific Time

In this example, you want the video to skip ahead a certain amount of time when the viewer presses 'play'. This utilizes the bind on play functionality built into the API.

wistia_html.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<script charset="ISO-8859-1" src="//fast.wistia.com/assets/external/E-v1.js" async></script>
<div class="wistia_embed wistia_async_29b0fbf547" style="width:640px;height:360px;">&nbsp;</div>
<script>
window._wq = window._wq || [];

// target our video by the first 3 characters of the hashed ID
_wq.push({ "29b": function(video) {

  // on play, seek the video to 10 seconds, then unbind so it
  // only happens once.
  video.bind('play', function() {
    video.time(10);
    return video.unbind;
  });

}});
</script>

Trigger an event at a specific time

In this example, let's assume that we want to run some javascript when the viewer gets 60 seconds into the video. In order to accomplish this, we only need the bind method from the API.

wistia.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<script charset="ISO-8859-1" src="//fast.wistia.com/assets/external/E-v1.js" async></script>
<div class="wistia_embed wistia_async_29b0fbf547" style="width:640px;height:360px;">&nbsp;</div>
<script>
window._wq = window._wq || [];

// target our video by the first 3 characters of the hashed ID
_wq.push({ "29b": function(video) {
  // at 10 seconds, do something amazing
  video.bind('secondchange', function(s) {
    if (s === 10) {
      // Insert code to do something amazing here
      console.log("We just reached " + s + " seconds!");
    }
  });
}});

</script>

The bind function monitors the state of the video in an event loop. Every 300 milliseconds, it checks to see if the video's time position has changed. If it has, it runs your function with the current second (s) as the only argument.

The secondchange will only run once per second while the video is playing. If you need more fine-grained control, try binding to the timechange event instead.


Pause Other Videos When Another is Played

Don't like the barrage of sound that comes from three different videos playing in the same page? This snippet will pause all videos that aren't currently playing:

wistia_html.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<script charset="ISO-8859-1" src="//fast.wistia.com/assets/external/E-v1.js" async></script>
<div class="wistia_embed wistia_async_9kksns1ede" style="width:480px;height:270px;">&nbsp;</div>
<div class="wistia_embed wistia_async_oh34zbesuh" style="width:480px;height:270px;">&nbsp;</div>
<div class="wistia_embed wistia_async_2jvt3wqkye" style="width:480px;height:270px;">&nbsp;</div>

<script>
window._wq = window._wq || [];
_wq.push({ "_all": function(video) {
  // for all existing and future videos, run this function
  video.bind('play', function() {
    // when one video plays, iterate over all the videos and pause each,
    // unless it's the video that just started playing.
    var allVideos = Wistia.api.all();
    for (var i = 0; i < allVideos.length; i++) {
      if (allVideos[i].hashedId() !== video.hashedId()) {
        allVideos[i].pause();
      }
    }
  });
}});
</script>

A/B testing videos against each other

Using a Standard embed code as a template, we can switch out hashed ID's for multiple videos easily. Comparing the viewer analytics in the background will tell you which video reigned supreme!

In this example, we create an array of hashed IDs for possible videos to embed, then randomly select one and embed the video with that hashed ID by adding to the class name of the embed's container. The Wistia library will monitor the DOM for changes like this, and automatically embed a video where it sees an element with the right class.

wistia_html.html
1
2
3
4
5
6
7
8
9
<script charset="ISO-8859-1" src="//fast.wistia.com/assets/external/E-v1.js" async></script>
<div id="thumbnail_test" class="wistia_embed" style="width:640px;height:360px;">&nbsp;</div>

<script>
  var hashedIds = ["wfu7q0s0pf", "ck7avcilwk"];
  var rand = Math.floor(Math.random() * hashedIds.length);
  var hashedId = hashedIds[rand];
  document.getElementById("thumbnail_test").className += " wistia_async_" + hashedId;
</script>

Add Chaptering Links to your Embedded Video

You can do this yourself using the time(val) method described above, OR you could make your life easier and use embed links, which handles chaptering automatically!


Mute the Video on Load

You can do this by setting the volume embed option to 0, like so:

wistia_html.html
1
2
<script charset="ISO-8859-1" src="//fast.wistia.com/assets/external/E-v1.js" async></script>
<div class="wistia_embed wistia_async_5bbw8l7kl5 volume=0" style="width:640px;height:360px;">&nbsp;</div>

Selective Autoplay

Selective Autoplay will automatically play your embedded video based on the presence of a query string you specify.

wistia_html.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script charset="ISO-8859-1" src="//fast.wistia.com/assets/external/E-v1.js" async></script>
<div class="wistia_embed wistia_async_5bbw8l7kl5" style="width:640px;height:360px;">&nbsp;</div>
<script>
window._wq = window._wq || [];
_wq.push(function(W) {
  var playedOnce = false;
  W.api(function(video) {
    if (!playedOnce && /[&?]autoplay/i.test(location.href)) {
      playedOnce = true;
      video.play();
    }
  });
});
</script>

In this example, if "?autoplay" or "&autoplay" appears in the page URL, the first video that initializes will autoplay.


Alert on play just once

With the bind method, every time "play" is triggered, your function will be executed. But sometimes a user will scroll back to the beginning and hit Play again. If you want to avoid your function being executed again, you need to unbind it.

Our library contains a special unbinding pattern for convenience. In the callback function, just return video.unbind.

wistia_js.js
1
2
3
4
5
6
<script>
video.bind("play", function() {
  alert("Played the first time!");
  return video.unbind;
});
</script>

If you are performing asynchronous operations or need more control over unbinding, you can use the unbind method as shown below.

wistia_html.html
1
2
3
4
5
6
7
8
<script>
function playFunc() {
  alert("Played the first time!");
  video.unbind("play", playFunc);
}

video.bind("play", playFunc);
</script>

Add Custom Pre-Roll to Your Videos

You can leverage the addToPlaylist method to play pre-roll before your video.

We simply add the main video to the playlist on the pre-roll video.

wistia_html.html
1
2
3
4
5
6
7
8
<script charset="ISO-8859-1" src="//fast.wistia.com/assets/external/E-v1.js" async></script>
<div class="wistia_embed wistia_async_oefj398m6q" style="width:640px;height:360px;">&nbsp;</div>
<script>
window._wq = window._wq || [];
_wq.push({ "oef": function(video) {
  video.addToPlaylist("5bbw8l7kl5");
}});
</script>

Playing a second video on Post Roll click

You can now handle this behavior by using embed links.


Make the video background transparent

If you are embedding a Wistia video on a website with a white background, the natural black background of the Wistia player can look a little out of place. Instead, using a wmode=transparent string parameter, the background of the player loading can be set to transparent.

So a finished iframe embed code would look something like this:

iframe_example.html
1
2
3
4
5
<iframe src="http://fast.wistia.net/embed/iframe/e4a27b971d?
controlsVisibleOnLoad=true&playerColor=688AAD&version=v1&wmode=transparent"
allowtransparency="true" frameborder="0" scrolling="no"
class="wistia_embed" name="wistia_embed" width="640"
height="360"></iframe>

Or a Standard inline embed would look like this:

wistia_html.html
1
2
3
<script src="//fast.wistia.com/assets/external/E-v1.js" async></script>
<div class="wistia_embed wistia_async_abcde12345 wmode=transparent"
style="width:640px;height:360px;"></div>

Legacy API Embeds

This section exists to help customers transition from our Legacy API embeds to Standard (a.k.a. "async") embeds.

If you have an embed code which look like this, then you have a Legacy API embed:

wistia_html.html
1
2
3
4
5
<div id="wistia_abcde12345" class="wistia_embed" style="width:640px;height:360px;">&nbsp;</div>
<script src="//fast.wistia.com/assets/external/E-v1.js"></script>
<script>
wistiaEmbed = Wistia.embed("abcde12345");
</script>

An equivalent Standard (a.k.a. "async") embed would look like this:

wistia_html.html
1
2
<script src="//fast.wistia.com/assets/external/E-v1.js" async></script>
<div class="wistia_embed wistia_async_abcde12345" style="width:640px;height:360px;"></div>

Going forward, it is recommended that you switch to a "Standard" (a.k.a. "async") embed for all new embed codes. Async embeds can do everything Legacy API embeds can do, but they never block page load, they are less susceptible to mangling, and they are easier to inject dynamically into html.

There are no plans to remove the Legacy API embed syntax; if you have existing Legacy API embeds, they do not need to be re-embedded.

Embed Options Comparison

In Legacy API embeds, options passed to the embed code might look like this:

wistia_html.html
1
2
3
4
5
6
7
8
<div id="wistia_abcde12345" class="wistia_embed" style="width:640px;height:360px;">&nbsp;</div>
<script src="//fast.wistia.com/assets/external/E-v1.js"></script>
<script>
wistiaEmbed = Wistia.embed("abcde12345", {
  autoPlay: true,
  controlsVisibleOnLoad: false
});
</script>

The options there are specified as part of the Wistia.embed function call.

With Standard (a.k.a. "async") embeds, an equivalent embed code would be:

wistia_html.html
1
2
3
<script src="//fast.wistia.com/assets/external/E-v1.js" async></script>
<div class="wistia_embed wistia_async_abcde12345 autoPlay=true
controlsVisibleOnLoad=false" style="width:640px;height:360px;"></div>

The options there are specified as key/val pairs in the container's class attribute.

For more information on setting options, check out the docs on embed options.

Player API Usage Comparison

With Legacy API embeds, each embed code is assigned the wistiaEmbed variable by default. You could use this variable to set up bindings, play on load, etc. You can do the same things with Standard embeds, but they are always loaded asynchronously, so the flow to get Player API access is slightly different.

Setting up bindings with a Legacy API embed:

wistia_html.html
1
2
3
4
5
6
7
8
9
10
<div id="wistia_abcde12345" class="wistia_embed" style="width:640px;height:360px;">&nbsp;</div>
<script src="//fast.wistia.com/assets/external/E-v1.js"></script>
<script>
wistiaEmbed = Wistia.embed("abcde12345");
wistiaEmbed.hasData(function() {
  wistiaEmbed.bind("play", function() {
    console.log("video played", wistiaEmbed.name());
  });
});
</script>

Equivalent code for Standard embeds:

wistia_html.html
1
2
3
4
5
6
7
8
9
10
<script src="//fast.wistia.com/assets/external/E-v1.js" async></script>
<div class="wistia_embed wistia_async_abcde12345" style="width:640px;height:360px;"></div>
<script>
window._wq = window._wq || [];
_wq.push({ "abcde12345": function(video) {
  video.bind("play", function() {
    console.log("video played", video.name());
  });
}});
</script>

The inline script syntax for Standard embeds makes it easier for javascript in external files to get a handle to each video. That is, instead of setting a global variable when the video is embedded, you can access the Player API by hashed ID or DOM ID. It also implicitly waits for data to be returned from the server, so you are guaranteed methods like name() and duration() will return meaningful values.

For more information on using the Player API, scroll to the top of this page.