Skip to main content
Track video engagement to build video-based cohorts and understand viewing behavior.

Overview

Video tracking enables:
  • Behavioral cohorts based on video consumption
  • Engagement metrics for video content
  • Contextual targeting on video pages
  • Ad performance insights

Video Events

Videoview

Track when video playback starts:
permutive.track('Videoview', {
  video_id: 'video-123',
  video_title: 'Product Demo',
  video_duration: 180,  // seconds
  categories: ['product', 'tutorial'],
  autoplay: false
});

VideoEngagement

Track engagement during playback:
permutive.track('VideoEngagement', {
  video_id: 'video-123',
  video_title: 'Product Demo',
  video_duration: 180,
  engaged_time: 60,      // seconds watched
  completion: 0.33       // 33% complete
});

VideoCompletion

Track when video ends:
permutive.track('VideoCompletion', {
  video_id: 'video-123',
  video_title: 'Product Demo',
  video_duration: 180,
  total_engaged_time: 165,  // seconds watched
  completion: 0.92          // 92% complete
});

Event Properties

Standard Properties

PropertyTypeDescription
video_idstringUnique video identifier
video_titlestringVideo title
video_durationnumberTotal duration in seconds
categoriesarrayVideo categories
engaged_timenumberTime watched in seconds
completionnumberPercentage complete (0-1)

Extended Properties

permutive.track('Videoview', {
  // Required
  video_id: 'video-123',
  video_title: 'Product Demo',
  video_duration: 180,

  // Content classification
  categories: ['technology', 'tutorials'],
  tags: ['software', 'demo', 'product'],

  // Metadata
  series: 'Product Tutorials',
  season: 2,
  episode: 5,
  release_date: '2024-01-15',

  // Playback context
  autoplay: false,
  muted: false,
  player: 'jw-player',
  quality: '1080p',

  // Monetization
  premium: true,
  ad_supported: true
});

Player Integration

Generic Pattern

function initVideoTracking(player, videoData) {
  var hasStarted = false;
  var lastEngagementTime = 0;

  // Track video start
  player.on('play', function() {
    if (!hasStarted) {
      permutive.track('Videoview', {
        video_id: videoData.id,
        video_title: videoData.title,
        video_duration: videoData.duration,
        categories: videoData.categories
      });
      hasStarted = true;
    }
  });

  // Track engagement periodically
  setInterval(function() {
    if (player.isPlaying()) {
      var currentTime = player.getCurrentTime();
      if (currentTime > lastEngagementTime) {
        permutive.track('VideoEngagement', {
          video_id: videoData.id,
          video_title: videoData.title,
          video_duration: videoData.duration,
          engaged_time: currentTime,
          completion: currentTime / videoData.duration
        });
        lastEngagementTime = currentTime;
      }
    }
  }, 10000);  // Every 10 seconds

  // Track completion
  player.on('ended', function() {
    permutive.track('VideoCompletion', {
      video_id: videoData.id,
      video_title: videoData.title,
      video_duration: videoData.duration,
      total_engaged_time: player.getCurrentTime(),
      completion: 1
    });
  });
}

JW Player

jwplayer('player').on('ready', function() {
  var player = this;
  var videoData = {
    id: player.getPlaylistItem().mediaid,
    title: player.getPlaylistItem().title,
    duration: player.getPlaylistItem().duration
  };

  // Use Permutive JW Player addon or manual tracking
  initVideoTracking(player, videoData);
});
Permutive offers dedicated video player integrations. See Video Integrations for JW Player, Brightcove, YouTube, and more.

YouTube (iframe API)

var player;
function onYouTubeIframeAPIReady() {
  player = new YT.Player('player', {
    videoId: 'VIDEO_ID',
    events: {
      'onReady': onPlayerReady,
      'onStateChange': onPlayerStateChange
    }
  });
}

var hasStarted = false;

function onPlayerStateChange(event) {
  if (event.data === YT.PlayerState.PLAYING && !hasStarted) {
    permutive.track('Videoview', {
      video_id: player.getVideoData().video_id,
      video_title: player.getVideoData().title,
      video_duration: player.getDuration()
    });
    hasStarted = true;
  }

  if (event.data === YT.PlayerState.ENDED) {
    permutive.track('VideoCompletion', {
      video_id: player.getVideoData().video_id,
      video_title: player.getVideoData().title,
      video_duration: player.getDuration(),
      total_engaged_time: player.getDuration(),
      completion: 1
    });
  }
}

HTML5 Video

var video = document.getElementById('my-video');
var videoData = {
  id: video.dataset.videoId,
  title: video.dataset.videoTitle,
  duration: 0
};
var hasStarted = false;

video.addEventListener('loadedmetadata', function() {
  videoData.duration = video.duration;
});

video.addEventListener('play', function() {
  if (!hasStarted) {
    permutive.track('Videoview', {
      video_id: videoData.id,
      video_title: videoData.title,
      video_duration: videoData.duration
    });
    hasStarted = true;
  }
});

video.addEventListener('timeupdate', function() {
  // Track engagement at intervals
  if (Math.floor(video.currentTime) % 10 === 0) {
    permutive.track('VideoEngagement', {
      video_id: videoData.id,
      engaged_time: video.currentTime,
      completion: video.currentTime / video.duration
    });
  }
});

video.addEventListener('ended', function() {
  permutive.track('VideoCompletion', {
    video_id: videoData.id,
    video_title: videoData.title,
    video_duration: videoData.duration,
    total_engaged_time: video.duration,
    completion: 1
  });
});

Page Context for Video

Set page context for video pages:
permutive.addon('web', {
  page: {
    type: 'video',
    video: {
      id: 'video-123',
      title: 'Product Demo',
      duration: 180,
      categories: ['product', 'tutorial'],
      series: 'How-To Guides'
    }
  }
});
This enables contextual cohorts based on video content.

Video Ad Tracking

Track video ad events:
// Ad request
permutive.track('VideoAdRequest', {
  video_id: 'video-123',
  ad_position: 'preroll',
  ad_slot_id: 'slot-1'
});

// Ad impression
permutive.track('VideoAdImpression', {
  video_id: 'video-123',
  ad_id: 'ad-456',
  ad_position: 'preroll',
  advertiser: 'Brand Name'
});

// Ad completion
permutive.track('VideoAdComplete', {
  video_id: 'video-123',
  ad_id: 'ad-456',
  ad_duration: 30,
  watched_duration: 30
});

// Ad skip
permutive.track('VideoAdSkipped', {
  video_id: 'video-123',
  ad_id: 'ad-456',
  skip_time: 5  // Skipped after 5 seconds
});

Best Practices

// Good - track play, engagement, complete
player.on('play', () => trackVideoview());
setInterval(() => trackEngagement(), 10000);
player.on('ended', () => trackCompletion());

// Avoid - tracking every second
player.on('timeupdate', () => track());  // Too frequent
// Use the same video_id across all events
var videoId = 'video-123';

permutive.track('Videoview', { video_id: videoId, ... });
permutive.track('VideoEngagement', { video_id: videoId, ... });
permutive.track('VideoCompletion', { video_id: videoId, ... });
// Correct - actual watched time vs duration
var completion = watchedTime / totalDuration;

// Avoid - using current position (user may have skipped)
var completion = currentPosition / totalDuration;
// Handle seek/skip
player.on('seeked', function() {
  // Don't count skipped content as engaged time
});

// Handle replay
player.on('ended', function() {
  // Reset tracking for potential replay
});

// Handle pause
player.on('pause', function() {
  // Stop engagement timer
});

Engagement Tracking Patterns

Milestone-Based

var milestones = [0.25, 0.50, 0.75, 1.0];
var trackedMilestones = [];

player.on('timeupdate', function() {
  var completion = player.currentTime / player.duration;

  milestones.forEach(function(milestone) {
    if (completion >= milestone && !trackedMilestones.includes(milestone)) {
      permutive.track('VideoEngagement', {
        video_id: videoId,
        milestone: milestone,
        completion: completion
      });
      trackedMilestones.push(milestone);
    }
  });
});

Time-Based

var engagementInterval = setInterval(function() {
  if (player.isPlaying()) {
    permutive.track('VideoEngagement', {
      video_id: videoId,
      engaged_time: player.getCurrentTime(),
      completion: player.getCurrentTime() / player.getDuration()
    });
  }
}, 10000);  // Every 10 seconds

player.on('ended', function() {
  clearInterval(engagementInterval);
});

Troubleshooting

Problem: Player events not firing Permutive tracks.Solutions:
  • Verify player events are firing (add console.log)
  • Check video data is populated (id, title, duration)
  • Ensure SDK is loaded before player initializes
  • Check for JavaScript errors in console
Problem: Multiple Videoview events for single play.Solutions:
  • Use a flag to prevent duplicate start tracking
  • Reset flag on new video or replay
  • Check player doesn’t fire multiple play events
Problem: Completion percentage is wrong.Solutions:
  • Ensure duration is populated before calculating
  • Use actual watched time, not current position
  • Handle seek events appropriately