I'm working on a project that allows content creators to embed YouTube and Vimeo media within otherwise non-video content.  The trick is that the videos need to automatically pause when they pass out of the viewport.

When I first added the feature, both players seemed to do this automatically.  Over the past few weeks, however, we've noticed a change that for whatever reason keeps the videos playing when they pass out of scope.

Playing videos that cannot be stopped or controlled by the site visitor is a huge failure of UX, so I wanted to nip it in the bud.

Vimeo

Controlling Vimeo videos is relatively easy.  According to their API, you can select a video player by its iFrame ID, then use the Froogaloop library to wrap the iFrame in a controllable Vimeo object.  The library gives you access to a convenient [cci].pause()[/cci] method, which I can call automatically when the player passes out of the viewport.

Unfortunately, Froogaloop doesn't contain any sort of "ready" event, so detecting when the library (added to the page dynamically) is ready to receive commands was a bit problematic.  I instead followed their "Calling the API Manually" instructions and coded up a simple, 2-part [cci]postMessage[/cci] routine.

After the page loads, all Vimeo videos are added to an array.  When the page transitions and the video moves out of scope, I fire a JavaScript [cci]postMessage[/cci] routine to tell all of the videos, playing or not, to pause.

$( 'iframe[src*="vimeo.com"]' ).each( function( i, el ) {
vimeo_players.push( el );
} );

// ... elsewhere
function kill_videos() {
var vimeo_command = window.JSON.stringify( { method: 'pause' } );

$.each( vimeo_players, function( i, player ) {
if ( null === player.contentWindow ) {
return;
}

player.contentWindow.postMessage( vimeo_command, 'https://player.vimeo.com' );
} );
}

YouTube

YouTube was a bit trickier.

The YouTube documentation presents an asynchronous script that fires a global callback once it's loaded.  This is convenient, as it presents developers with a way to identify and wrap all of their YouTube videos after the script has fully loaded.

Unfortunately, it results in a lot of extra code.

Take the Vimeo example above.  Detecting the videos is 3 lines of code.  Pausing the videos is 6 lines of code.

In comparison, detecting, wrapping, and pausing YouTube videos using their API is a total of 53 lines of code!  That's more than five times the work to accomplish the exact same task.  There must be a better way.

YouTube and postMessage

As it turns out, YouTube videos also support [cci]postMessage[/cci] commands.  The syntax is slightly different than Vimeo's API, but thanks to [cci]postMessage[/cci] I was able to consolidate the 53-line YouTube monstrosity into a lightweight 9-line call nearly identical to the Vimeo one above:

$( 'iframe[src*="youtube.com"]' ).each( function( i, el ) {
youtube_players.push( el );
} );

// ... elsewhere
function kill_videos() {
// ... vimeo code

var youtube_command = window.JSON.stringify( { event: 'command', func: 'pauseVideo' } );

$.each( youtube_players, function( i, player ) {
if ( null === player.contentWindow ) {
return;
}

player.contentWindow.postMessage( youtube_command, 'https://www.youtube.com' );
} );
}

Proceed with Caution

Using [cci]postMessage[/cci] for YouTube videos is dangerous because it's not a documented system.  I'm comfortable using this method because I've used [cci]postMessage[/cci] before, I know the drawbacks of the API, and I know I can step in and fix code should YouTube every change their system.

If you elect to use a variation of the code I outlined above, keep in mind that it won't work in older versions of Internet Explorer (<IE7) and might have issues with cross-domain messaging in some scenarios.  For now, things work as expected, but there's no guarantee the browser vendors will stay the course and not break things with a future update.