Track Brightcove video playback to Adobe Media Analytics through Adobe Launch

Publishers that have lots of video content usually use a third-party platform to manage the delivery of that content. One of the most common video delivery platforms is Brightcove.

One benefit of using Brightcove is that it provides an integration with Adobe Analytics (and Google Analytics), so that video playback events can be tracked automatically without any additional code. Unfortunately, this also locks publishers into only being able to report against the dimensions and metrics that Brightcove has setup. Also, there is no facility to integrate Brightcove with Adobe Media Analytics, which provides a "heartbeat" approach to video measurement.

In this three-part series, I describe how to use Adobe Experience Platform Launch (or Adobe Launch, as it's more commonly known) to track video playback to Adobe Analytics or Adobe Media Analytics:
  1. Part one: tracking video playback events using Adobe Analytics' Custom Links.
  2. Part two: tracking video playback events to Adobe Media Analytics, Adobe's media tracking and reporting solution.
  3. Part three (this part): tracking Brightcove video playback events to Adobe Media Analytics, instead of Brightcove's own analytics integration.
(Each part if written as a standalone guide, so you don't need to read them one-by-one.)

There are a few things to note:
Adobe Experience Platform Launch, more commonly known as Adobe Launch, is Adobe's tag management solution. To find out more about Adobe Launch, you can read it from Adobe's documentation.

Recipe to track Brightcove video playback events to Adobe Media Analytics through Adobe Launch

  1. Installation of the Adobe Media Analytics extension.
  2. A Rule to associate with video playback.
  3. Events in the Rule to detect the different kinds of playback events, like "Play", "Pause", etc. including "Loaded".
  4. Conditions in the Rule to ensure that Brightcove's JavaScript library is available.
  5. Action in the Rule to send the tracking data to Adobe Media Analytics.
In these instructions, I show how to track the "loaded", "play", "pause", and "ended" video playback events.

1. Install the Adobe Media Analytics extension.

The full name of the extension is "Adobe Media Analytics for Audio and Video", but most of the time, it's just referred to as "Adobe Media Analytics", or its much older name, "MediaHeartbeat". In fact, you'll see references to MediaHeartbeat later when configuring the Action for the Rule.

Adobe Media Analytics extension configuration
Fill up as much or as little information as you want in the extension. This is to track information about your player.

The most important field is the "Variable Name". This needs to be a name that is not used with any other JavaScript variable in your website. Remember the name that is chosen here because it will be needed when configuring the Action for the Rule later.

You should also have installed the Experience Cloud ID Service extension. Adobe Media Analytics depends on this Service.

2. Create the Rule to associate with video playback.

Only one Rule is really needed to work with all of the video playback events. This will be explained in the next step when creating the Events.

3. Create the Events in the Rule, one for each kind of video playback event.

Adobe Launch's Core extension can detect all of the common media-related event types, and the names of the event types are quite self-explanatory. This saves us the trouble of having to write custom code to detect these events from the web browser.
Adobe Launch's Core extension's "Media" Event types

To start, choose the "Media Play" Event type. Then, setup the rest of the Event Configuration as needed. This is where the CSS selector of the <VIDEO> element in the web page needs to be specified. Use the ID selector of the <VIDEO> element, which should be the <VIDEO-JS>'s id attribute postfixed with "_html5_api". For example, if the <VIDEO-JS> id is "vjs_video_3", then the CSS selector for the <VIDEO> element should be "#vjs_video_3__html5_api". Don't forget the "#" so that the CSS selector is based on the ID.
Adobe Launch's Core extension's "Media" Event configuration

This is what my "Media Play" Event's configuration looks like:
"Media Play" Event configuration

I have also updated the configuration's name to reflect the setup. In that way, when I look at my Rule's setup, I can instantly see how I have configured my "Media Play" Event without needing to open up the Event later on.
Rule with "Media Play" Event

The beauty of an Adobe Launch's Rule is that you can add more than one Event to it. Then, when any of the Events are met while the user browses your website, the Rule will be triggered immediately.

For our purpose, that means that instead of creating separate Rules for each type of video playback event, all of the playback events can be included under one Rule, like so:
Rule with four "Media" Events

But when it comes to the actual tracking, how will Adobe Launch know which event to track? For example, I wouldn't want to track a "pause" as a "play" incorrectly. That will be solved in the later section, when setting the Actions for this Rule.

4. Create the Conditions to check that Brightcove's JavaScript library is available

In order for Brightcove video tracking to work properly, some information about the video will need to be obtained from Brightcove via its JavaScript library. When the <VIDEO-JS> embed code is used for showing Brightcove videos, that JavaScript library should have been included in the embed code. But it's good to verify that in the Rule as well, since the Action (which will be setup next) depends on that library to execute without error.

The first Condition checks that Brightcove's videojs JavaScript object is available. videojs is created automatically by Brightcove's JavaScript library. It is the only way to interact with the Brightcove video programmatically.

From the Core extension, choose the "Regular" Logic Type and "Custom Code" Condition Type. Click "Open Editor" and add the following code:

return !!videojs;

This single line checks that videojs exists, and returns the Boolean value true or false accordingly.

The second Condition verifies that the <VIDEO> element (not the <VIDEO-JS> element!) that triggered the "Media" Events has a playerId variable.

From the Core extension, choose the "Regular" Logic Type and "Value Comparison" Condition Type. Enter %this.playerId% as the data element, and select "Is Truthy" from the dropdown list.
  • this refers to the <VIDEO> element, the element that was indicated by the CSS selector in the Events.
  • playerId refers to the unique ID that Brightcove has assigned to the player when its JavaScript library runs.
  • "Is Truthy" is a simple condition to check if the data element has any value. (Learn more about "truthy" from a Mozilla Developer Network article.)
Condition to check that playerId exists

After setting up the two Conditions, the Rule should look like this:
Rule with two Conditions

5. Create the Action to send the tracking data to Adobe Media Analytics

At this point, the Adobe Analytics, Adobe Media Analytics and Experience Cloud ID Service extensions should all be installed in the Adobe Launch property already. Otherwise, the rest of these instructions are moot.

Only one Action is needed, and it contains all of the Custom Code needed to track the video playback to Adobe Media Analytics.
At the time of this writing, there is no other way to send tracking data to Adobe Media Analytics except with Custom Code.
Add an Action from the Core extension and Action Type "Custom Code". Click "Open Editor" and add the following code:

// Initialise Adobe MediaHeartbeat.
// "adobeMediaAnalytics" is the global variable declared inside the Adobe Media Analytics extension.
var adobeMediaAnalytics = window["adobeMediaAnalytics"];
var MediaHeartbeat = adobeMediaAnalytics.MediaHeartbeat;

// Get a reference to the Brightcove player object.
var playerId = this.playerId;
var player = videojs.players[playerId];

// Private function to retrieve metadata from the Brightcove player.
// When this code runs, the Brightcove JavaScript library may not have
// finished loading the metadata from Brightcove yet.
// So use an exponential backoff to keep trying for up to 5 seconds.
function getMediaInfo_(player, nextTryDelay) {
  var mediaInfo;

  if (!!player.mediainfo) {
    mediaInfo = player.mediainfo;
  } else if (nextTryDelay < 5000) {
    setTimeout(function () {
      nextTryDelay = nextTryDelay *= 2;
      mediaInfo = getMediaInfo_(player, nextTryDelay);
    }, nextTryDelay);
  }

  return mediaInfo;
}

// Get the Brightcove video metadata.
var mediaInfo = getMediaInfo_(player, 500);

// Get a reference to the Media event type
var eventType = event.nativeEvent.type;

var mediaDelegate = mediaDelegate || null;
// Create the mediaDelegate if this is a new video or it cannot be found
if (eventType === 'loadeddata' || !mediaDelegate) {
  // Set the fixed variables needed for the QoS object.
  var startupTime = new Date().getTime(),
      fps = 30, // Brightcove does not support frame rate, so this should be set to the media's actual frames per second
      droppedFrames = 0; // Brightcove does not support dropped frames

  // Create the delegate for MediaHeartbeat.
  mediaDelegate = {
    getCurrentPlaybackTime: function () {
      // Get the current played time.
      return player.currentTime();
    },
    getQoSObject: function () {
       // Get the current playback rate.
      var bitrate = player.playbackRate();
      return MediaHeartbeat.createQoSObject(bitrate, startupTime, fps, droppedFrames);
    },
  };
}

var mediaObject = mediaObject || null;
 // Create the mediaObject if this is a new video or it cannot be found
if (eventType === 'loadeddata' || !mediaObject) {
  // Create the mediaObject from the <VIDEO> / <AUDIO> element's media.
  var mediaName = mediaInfo.name,
      mediaId = mediaInfo.name,
      mediaLength = mediaInfo.duration,
      mediaStreamType = MediaHeartbeat.StreamType.VOD,
      mediaType = MediaHeartbeat.MediaType.Video;
  mediaObject = MediaHeartbeat.createMediaObject(mediaName, mediaId, mediaLength, mediaStreamType, mediaType);
}

// Track the mediaObject using the mediaDelegate.
MediaHeartbeat.getInstance(mediaDelegate).then(function (instance) {
  switch (eventType) {
    case 'loadeddata':
      instance.trackSessionStart(mediaObject);
      break;
    case 'play':
      instance.trackPlay();
      break;
    case 'pause':
       // a "pause" event is sent with the "ended" event, so don't track the pause in that case
      if (Math.ceil(player.currentTime()) < Math.ceil(mediaInfo.duration)) {
        instance.trackPause();
      }
      break;
    case 'ended':
      instance.trackComplete();
      break;
  }
}).catch(function(err){
  _satellite.logger.error(err);
});


Here's what each part does:

// Initialise Adobe MediaHeartbeat.
// "adobeMediaAnalytics" is the global variable declared inside the Adobe Media Analytics extension.
var adobeMediaAnalytics = window["adobeMediaAnalytics"];
var MediaHeartbeat = adobeMediaAnalytics.MediaHeartbeat;


Recall from when setting up the Adobe Media Analytics extension that the "Variable Name", adobeMediaAnalytics, had been set. This is where that variable is used. It provides access to the MediaHeartbeat object, the guts of Adobe Media Analytics that provides important functions needed by the rest of the code.

// Get a reference to the Brightcove player object.
var playerId = this.playerId;
var player = videojs.players[playerId];


The first line gets the ID of the Brightcove player from the <VIDEO> element that triggered the "Media" Events.

The second line gets a reference to the actual Brightcove player object, using Brightcove's videojs object, which maintains a list of all players found in the web page. playerId is used to ensure that the correct player object is referenced.

// Private function to retrieve metadata from the Brightcove player.
// When this code runs, the Brightcove JavaScript library may not have
// finished loading the metadata from Brightcove yet.
// So use an exponential backoff to keep trying for up to 5 seconds.
function getMediaInfo_(player, nextTryDelay) {
  var mediaInfo;

  if (!!player.mediainfo) {
    mediaInfo = player.mediainfo;
  } else if (nextTryDelay < 5000) {
    setTimeout(function () {
      nextTryDelay = nextTryDelay *= 2;
      mediaInfo = getMediaInfo_(player, nextTryDelay);
    }, nextTryDelay);
  }

  return mediaInfo;
}

// Get the Brightcove video metadata.
var mediaInfo = getMediaInfo_(player, 500);


This next part is somewhat of a hack. To track the video playback meaningfully, that video's name and duration need to be obtained from Brightcove. But Brightcove's JavaScript library doesn't actually load this metadata until after the user has started playing the video.

Therein lies a problem: to track the video start, its metadata is needed. But this metadata isn't available yet when the video starts, which also takes a while for the various network transfers.

To handle this, an exponential backoff is used to try to get the video metadata continuously, trying for up to 5 seconds. (Learn more about exponential backoff from a Wikipedia article.) When the metadata is finally available, it is referenced to in mediaInfo for later use.

Note: there is no error handling if, after 5 seconds, the metadata is still not available. If it isn't available after such a long delay, then chances are, there is some other problem with the web page, the video player, or even Brightcove itself, all of which is outside the scope of this article.

// Get a reference to the Media event type
var eventType = event.nativeEvent.type;


This gets the HTML5 media event type from Adobe Launch's event object. The returned event type corresponds to Adobe Launch's "Media" Event types.

var mediaDelegate = mediaDelegate || null;

Adobe Media Analytics uses a mediaDelegate to get runtime information during the video playback. (Learn more about delegates from a StackOverflow post.) It requires two functions: getCurrentPlaybackTime and getQoSObject. If either of them are missing, then Adobe Media Analytics throws an error and refuses to run.

There are two situations where the mediaDelegate needs to be created:
  1. A new video has been loaded in the player, so a correspondingly new mediaDelegate should be created for it to ensure that the right metadata is retrieved for that video.
  2. Since this Action can be run for any of the "Media" Events, the code checks if this mediaDelegate has been created already (which it should have, but this is a fallback, just in case something had messed up previously). If it hasn't, then it is created.
  // Set the fixed variables needed for the QoS object.
  var startupTime = new Date().getTime(),
      fps = 30, // Brightcove does not support frame rate, so this should be set to the media's actual frames per second
      droppedFrames = 0; // Brightcove does not support dropped frames


"QoS" stands for "Quality of Service". Adobe Media Analytics uses it to be able to report on the quality of the video playback that the user experiences. This could be used for server and bandwidth optimisation.

The QoS object needs four variables. But of those four, only bitrate can be derived from the video itself. This is because of HTML5's limitations (which Brightcove's player is built upon), so there is no information about the playback's frames per second nor the number of dropped frames, and there is nothing that can be done about that. So those are set to fixed values.

  // Create the delegate for MediaHeartbeat.
  mediaDelegate = {
    getCurrentPlaybackTime: function () {
      // Get the current played time.
      return player.currentTime();
    },
    getQoSObject: function () {
      // Get the current playback rate.
      var bitrate = player.playbackRate();
      return MediaHeartbeat.createQoSObject(bitrate, startupTime, fps, droppedFrames);
    },
  };


With all of the required information available, the mediaDelegate can be created now.

var mediaObject = mediaObject || null;

Similar to the mediaDelegate, there are two situations where the mediaObject needs to be created:
  1. A new video has been loaded in the player, so a correspondingly new mediaObject should be created for it to ensure that the right object is set to represent that video.
  2. Since this Action can be run for any of the "Media" Events, the code checks if this mediaObject has been created already (which, again, it should have, but this is still made available as a fallback, just in case). If it hasn't then it is created.
  // Create the mediaObject from the <VIDEO> / <AUDIO> element's media.
  var mediaName = mediaInfo.name,
      mediaId = mediaInfo.name,
      mediaLength = mediaInfo.duration,
      mediaStreamType = MediaHeartbeat.StreamType.VOD,
      mediaType = MediaHeartbeat.MediaType.Video;
  mediaObject = MediaHeartbeat.createMediaObject(mediaName, mediaId, mediaLength, mediaStreamType, mediaType);


mediaInfo, which was referenced earlier, is used here to retrieve the video's name and duration. For convenience, the video name is used with both the mediaName and mediaId fields of the mediaObject, though the Brightcove video ID could have been used as well.

Refer to the MediaHeartbeat documentation for a full list of stream types.

Refer to the MediaHeartbeat documentation for a full list of media types.

// Track the mediaObject using the mediaDelegate.
MediaHeartbeat.getInstance(mediaDelegate).then(function (instance) {...}).catch(function (err) {...});


When tracking video playback, MediaHeartbeat needs to get the current time and Quality of Service from the video via the mediaDelegate. But such metadata can only be obtained while the video is playing.

So during tracking, MediaHeartbeat needs to:
  • Request for metadata about the current time and Quality of Service from the video.
  • Wait for the metadata to be sent back to it.
  • Continue with tracking.
In JavaScript parlance, getInstance() returns a "promise" that the video metadata will be made available. When that promise is fulfilled (i.e. the video metadata is available), the then() part runs to perform the actual tracking. (Learn more about "promises" from JavaScript.info.)

If an error occurs while trying to get that instance, then the error from MediaHeartbeat is logged to Adobe Launch's log (accessible through your web browser's console or the Adobe Experience Cloud Debugger add-on for your web browser).

  switch (eventType) {
    case 'loadeddata':
      instance.trackSessionStart(mediaObject);
      break;
    case 'play':
      instance.trackPlay();
      break;
    case 'pause':
      // a "pause" event is sent with the "ended" event, so don't track the pause in that case
      if (Math.ceil(player.currentTime()) < Math.ceil(mediaInfo.duration)) {
        instance.trackPause();
      }
      break;
    case 'ended':
      instance.trackComplete();
      break;
  }


For each "Media" Event type, the appropriate Adobe Media Analytics tracking function is called.

The tricky part is with the "pause" event. Most web browsers report a "pause" when the video has finished playing. If this were left alone, then that would always cause an extra "pause" to be tracked. But we don't want that, because the user hadn't really pressed "Pause" at the end of the video.

So I check if the current time is equal to the video's duration. If so, I assume that the user has really finished watching the video, and so I ignore the extra "pause" event emitted by the web browser.

Notice that Adobe Media Analytics' trackSessionEnd() is not used here. This is a judgement call that I had made. A playback session lasts for as long as the video is available to the user. Therefore, the session ends when the video is unavailable, which is normally when the user navigates away from the current page. Unfortunately, Adobe Launch does not have a native Event type for this kind of condition, so I have not configured anything like it here.

trackSessionEnd() could have been called with the "ended" event. But if the user were to re-play the video, that re-play would not be tracked by Adobe Media Analytics.


Putting all of the above together, the Rule should look like this, with four Events, two Conditions and one Action:
Complete Rule configuration

Testing the Rule

After you save the Rule, add it to a library, and build that library in Adobe Launch, you can see this Rule in action when you play, pause or finish watching a Brightcove video in your website.

The best way to validate the setup is by looking for image requests to Adobe Media Analytics in your web browser's "Network" console. Refer to Adobe Media Analytics' documentation for validating the image requests:

Test 1: Standard Playback
Test 2: Media Interruption

Now, you can track Brightcove video playback events to Adobe Media Analytics through Adobe Launch. If you don't want to use Adobe Media Analytics, you can adapt the tracking guide for video playback to Adobe Analytics with Custom Links.

Update (23 June 2020): Adobe has released a new Adobe Launch extension, "Video Tracking | Brightcove", which can track Brightcove video playback events to Adobe Analytics with  Custom Links.

If you're using the Brightcove IFrame player, you can refer to my other guide on how to detect video playback events from the IFrame in your web page's parent window.

Comments

Popular posts from this blog

How to "unpivot" a table in BigQuery

Adobe Analytics and Google Analytics terminologies cheat sheet

Track Brightcove IFRAME video playback (bonus: with Adobe Launch)