Interactive Video Player with HLS.js, AWS MediaTailor, Datazoom Analytics and Brightline
This application demonstrates a complete video streaming solution with ad insertion, real-time analytics, and interactive advertising. It combines multiple technologies to create a premium video experience similar to platforms like YouTube or Hulu.
The application includes two players to demonstrate different configurations:
| Player | Features | Use Case |
|---|---|---|
| Video 1 - Standard | HLS.js only, no ads | Basic video playback |
| Video 2 - Premium | HLS.js + MediaTailor + Datazoom + Brightline | Full experience with ads and analytics |
The on-screen event monitor displays real-time activity:
SISTEMA - SDK initialization and page loadDATAZOOM - Context creation and MediaTailor sessionsMEDIATAILOR - Linear and non-linear ad detectionBRIGHTLINE - Interactive overlay open/closeEVENTO_ANUNCIO - Linear ad start/endDuring linear ads (video ads), a countdown timer displays that:
const hls = new Hls();
hls.loadSource(url);
hls.attachMedia(video);
HLS.js handles adaptive bitrate (ABR) video playback, automatically selecting the best quality based on bandwidth.
// Initialization
datazoom.init({
configuration_id: "2477d200-37bd-4c42-910d-22389350c3bd"
});
// Create MediaTailor session
const session = await datazoom.mediatailor.createSession({
sessionInitUrl: sessionInitPrefix
});
// Create analytics context
const context = datazoom.createContext(hls, {
adapterOptions: { hlsClass: Hls }
});
// Attach MediaTailor session
context.attachMediaTailorSession(session);
Datazoom acts as a bridge between HLS.js and MediaTailor, capturing ad events and sending metrics.
// Device configuration
BL.on_deviceInfo = function() {
return {
configId: "1018",
applicationName: "Mercado Libre Video Player",
// ... more config
};
};
// Event callbacks
BL.on_BL_expanded = function() { video.pause(); };
BL.on_BL_collapsed = function() { video.play(); };
// Initialize SDK
BL.init();
// Open interactive ad
BL.openAd(url, videoId, true, 'mercado-libre', trackingData, duration);
Brightline handles interactive overlays that appear over the video without interrupting playback.
Video ads that interrupt main content:
AD_START and AD_ENDevent.detail.adObject.adData.durationInteractive ads that overlay the video:
NONLINEAR_AD_START and NONLINEAR_AD_ENDAds that appear when user pauses the video:
pause eventMediaTailor provides tracking URLs for each ad:
These URLs are passed to Brightline in the trackingNodeStr parameter for automatic firing by the SDK.
The "View MediaTailor Tracking URLs" button displays:
The header title uses the slideInRight animation that slides text from the right on page load.
Video containers have an elevation effect on hover, creating a sense of depth.
The event monitor uses a terminal style with:
The #BL_Container has special z-index and pointer-events configuration to allow overlays to appear over video while permitting clicks.
Main function that initializes a video player with optional ad insertion and analytics:
const video = document.getElementById(videoId);
const hls = new Hls();
hls.loadSource(url);
hls.attachMedia(video);
Creates an HLS.js instance and attaches it to the video element. HLS.js handles adaptive bitrate streaming automatically.
let pauseAdShown = false;
video.addEventListener('pause', () => {
if (pauseAdShown || video.ended || video.currentTime === 0) return;
pauseAdShown = true;
BL.openAd(pauseAdUrl, videoId, true, 'mercado-libre-pause', '{}', 0);
});
// Listen for resume message from ad creative
window.addEventListener('message', (event) => {
if (event.data.action === 'resumeVideo') {
video.play();
pauseAdShown = false;
}
});
Key Points:
postMessage API for communication between ad iframe and parent page{action: 'resumeVideo'} message to resume playbackconst sessionConfig = { sessionInitUrl: sessionPrefix };
const session = await datazoom.mediatailor.createSession(sessionConfig);
// Create Datazoom analytics context
const context = datazoom.createContext(hls, {
adapterOptions: { hlsClass: Hls }
});
// Link MediaTailor session to Datazoom for ad tracking
context.attachMediaTailorSession(session);
// Get personalized playback URL with ad markers
const playbackUrl = session.getPlaybackUrl();
hls.loadSource(playbackUrl);
What This Does:
createSession() - Calls MediaTailor to create a personalized session with unique tracking URLscreateContext() - Wraps HLS.js player with Datazoom analytics layerattachMediaTailorSession() - Enables Datazoom to detect ad markers in the manifest and fire eventsgetPlaybackUrl() - Returns session-specific URL that includes ad insertion parameterssession.addEventListener(datazoom.mediatailor.SessionUiEvent.AD_START, event => {
const duration = event.detail?.adObject?.adData?.duration || 0;
// Start countdown timer
if (duration > 0) {
startAdCountdown(duration);
}
// Store tracking data for inspection
const adObject = event.detail?.adObject;
if (adObject) {
latestTrackingData = {
timestamp: new Date().toISOString(),
type: 'LINEAR_AD',
duration: duration,
adData: adObject.adData,
trackingEvents: adObject.adData?.trackingEvents || []
};
}
// Disable controls during ad
video.controls = false;
});
session.addEventListener(datazoom.mediatailor.SessionUiEvent.AD_END, event => {
stopAdCountdown();
video.controls = true;
});
Implementation Notes:
event.detail.adObject contains all ad metadata from VAST/VMAP responsesession.addEventListener(datazoom.mediatailor.SessionUiEvent.NONLINEAR_AD_START, event => {
const adData = event.detail?.nonLinearAdsObject?.adData;
if (!adData) return;
const { nonLinearAdList, trackingEvents, duration } = adData;
const nonLinearAd = nonLinearAdList[0];
// Store tracking data
latestTrackingData = {
timestamp: new Date().toISOString(),
type: 'NON_LINEAR_AD',
duration: duration,
trackingUrls: trackingEvents
};
// Select random creative
const adCreatives = [
'https://cdn.mediaplaypen.com/meli/ad/index.html',
'https://cdn.mediaplaypen.com/meli/ad/index2.html'
];
const overrideUrl = adCreatives[Math.floor(Math.random() * adCreatives.length)];
// Prepare tracking for Brightline
const trackingNodeStr = JSON.stringify({
trackingEvents: {
createView: trackingEvents
.filter(e => e.eventType === 'impression')
.map(e => e.beaconUrls)
.flat()
}
});
// Open Brightline overlay
BL.openAd(
overrideUrl, // Creative URL
videoId, // Video element ID
true, // Enable interactivity
'mercado-libre', // Campaign ID
trackingNodeStr, // Tracking URLs
duration // Auto-close duration
);
// Fallback timeout (in case Brightline doesn't auto-close)
setTimeout(() => {
BL.closeAd();
}, (duration + 2) * 1000);
});
Critical Implementation Details:
{trackingEvents: {createView: [url1, url2]}} structurefunction startAdCountdown(duration) {
const countdown = document.getElementById('adCountdown');
const timeDisplay = document.getElementById('adCountdownTime');
let timeLeft = Math.floor(duration);
countdown.style.display = 'block';
function updateCountdown() {
const mins = Math.floor(timeLeft / 60);
const secs = timeLeft % 60;
timeDisplay.textContent = `${mins}:${secs.toString().padStart(2, '0')}`;
if (timeLeft > 0) {
timeLeft--;
} else {
stopAdCountdown();
}
}
updateCountdown();
adCountdownInterval = setInterval(updateCountdown, 1000);
}
function stopAdCountdown() {
document.getElementById('adCountdown').style.display = 'none';
if (adCountdownInterval) {
clearInterval(adCountdownInterval);
adCountdownInterval = null;
}
}
How It Works:
padStart(2, '0') for leading zerossetIntervalfunction logEvent(type, message, action) {
const logContent = document.getElementById('event-log-content');
const time = new Date().toLocaleTimeString('es-ES');
const entry = document.createElement('div');
entry.className = 'event-entry';
entry.innerHTML = `
<span class="event-time">[${time}]</span>
<span class="event-type">${type}</span>
${message}
${action ? `<span class="event-action">β ${action}</span>` : ''}
`;
logContent.appendChild(entry);
// Auto-scroll to bottom
const logBox = logContent.parentElement;
setTimeout(() => {
logBox.scrollTop = logBox.scrollHeight;
}, 0);
}
Usage Examples:
logEvent('SISTEMA', 'Page loaded', 'Initializing SDKs');
logEvent('DATAZOOM', 'MediaTailor session created', session.id);
logEvent('BRIGHTLINE', 'Opening interactive overlay', `Duration: ${duration}s`);
setTimeout ensures scroll happens after DOM updatefunction showTrackingUrls() {
const overlay = document.getElementById('trackingOverlay');
const content = document.getElementById('trackingDetailsContent');
if (latestTrackingData) {
content.textContent = JSON.stringify(latestTrackingData, null, 2);
} else {
content.textContent = 'No tracking data available yet.';
}
overlay.style.display = 'flex';
}
function closeTrackingOverlay() {
document.getElementById('trackingOverlay').style.display = 'none';
}
// Close on outside click
document.addEventListener('DOMContentLoaded', () => {
const overlay = document.getElementById('trackingOverlay');
overlay.addEventListener('click', (e) => {
if (e.target === overlay) {
closeTrackingOverlay();
}
});
});
Key Features:
JSON.stringify(data, null, 2) - Pretty-prints JSON with 2-space indentationdisplay: flex for centering contente.target === overlay ensures clicks on content don't close modalAD_START eventstartAdCountdown(duration)video.controls = false)latestTrackingDataAD_END eventstopAdCountdown()video.controls = true)NONLINEAR_AD_START eventtrackingNodeStr object with impression URLsBL.openAd() with creative URL and tracking dataduration + 2 secondsNONLINEAR_AD_END event is ignored (to avoid premature closes)pause eventBL.openAd() with pause ad creativepostMessage with action: 'resumeVideo'video.play()pauseAdShown flagduration + 2 seconds to force closure.typeof datazoom and typeof BL before initializing.pauseAdShown flag to prevent showing ad repeatedly.The system includes extensive logging:
console.log() for debugging in DevToolslogEvent() to display events to userlatestTrackingData for inspectionVisualizes real-time metrics:
Monitors AWS infrastructure:
// Video 1 - Standard stream
const url1 = 'https://fcd796e21ed48a1abb4824e834c02632.p05sqb.channel-assembly.mediatailor.us-west-2.amazonaws.com/v1/channel/ModernAdsTestStream/index.m3u8';
// Video 2 - MediaTailor stream
const url2 = 'https://36820097a1284a0c9e2fa0344b33a58e.mediatailor.us-west-2.amazonaws.com/v1/master/bb5755666fd4f9f014f0687a72e9a3726a78e563/Meli/index.m3u8';
// Session init for Datazoom
const sessionInitPrefix = 'https://36820097a1284a0c9e2fa0344b33a58e.mediatailor.us-west-2.amazonaws.com/v1/session/bb5755666fd4f9f014f0687a72e9a3726a78e563/Meli/index.m3u8';
2477d200-37bd-4c42-910d-22389350c3bd1018https://cdn.mediaplaypen.com/meli/ad/index.htmlhttps://cdn.mediaplaypen.com/meli/ad/index2.htmlhttps://cdn.mediaplaypen.com/meli/ad/index3.htmlThis implementation demonstrates a complete integration of multiple video advertising technologies. Key points: