Video Analytics
Mux Data analytics integration guide
Video Analytics Integration
This guide covers how to integrate Mux Data analytics into your video player implementation.
Environment Keys
| Environment | Key |
|---|---|
| Production | nl7g58q1rs9lkojadotho5989 |
| Staging | 0bu16j45b65nhlf6vcqh1f445 |
Required Metadata
When initializing Mux analytics, you must provide the following metadata:
{
// Video identification
video_id: string, // Unique identifier for the video/stream
video_title: string, // Display title of the video
video_poster_url?: string, // Thumbnail/poster image URL
// Stream type configuration
video_stream_type: "live" | "on-demand",
video_source_is_live: boolean,
video_content_type?: string, // e.g., "clip", "vod", "episode"
// Creator & viewer identification
video_creator_id: string, // Channel/creator ID
viewer_user_id: string, // Your platform's viewer ID
// Third-party integration
sub_property_id?: string, // Your internal user ID (for third-party integrations)
// Custom fields
custom_1?: string, // External user ID (optional, for integrators)
// Page context
page_type: string, // e.g., "live", "playlist", "vod"
}Live Stream Example
const metadata = {
video_id: String(stream.id),
video_title: stream.title || `${channel.display_name}'s Stream`,
video_poster_url: channel.cover_photo_url,
video_stream_type: "live",
video_source_is_live: true,
video_creator_id: String(channel.id),
viewer_user_id: String(user.id),
sub_property_id: "YOUR_INTERNAL_USER_ID", // For third-party integrations
page_type: "live",
};Clip/VOD Example
When playing clips or VOD content, adjust these fields:
const metadata = {
video_id: String(clip.id),
video_title: clip.title,
video_poster_url: clip.thumbnail_url,
video_stream_type: "on-demand", // Changed from "live"
video_source_is_live: false, // Changed from true
video_content_type: "clip", // Added: specifies content type
video_creator_id: String(channel.id),
viewer_user_id: String(user.id),
page_type: "playlist",
};Content Type Values
| Value | Use Case |
|---|---|
clip | Short clips, highlights |
vod | Full video on demand |
episode | Series episodes |
movie | Full-length movies |
trailer | Previews/trailers |
event | Recorded events |
short | Short-form content |
Third-Party Integration
If you're integrating as a third party and need to track your own users:
const metadata = {
// ... other required fields
// Use sub_property_id for your internal user tracking
sub_property_id: "your-platform-user-id-12345",
// viewer_user_id is MBG's internal user ID
viewer_user_id: String(mbgUser.id),
};SDK Integration Examples
Installation
npm install mux-embedHTML5 Video Element (Vanilla JavaScript)
<script src="https://src.litix.io/core/4/mux.js"></script>
<video id="my-player" controls>
<source src="https://example.com/video.mp4" type="video/mp4">
</video>
<script>
// Capture player init time as early as possible
const playerInitTime = typeof mux !== 'undefined' ? mux.utils.now() : Date.now();
// Initialize Mux Data monitoring
mux.monitor('#my-player', {
debug: false,
data: {
env_key: 'nl7g58q1rs9lkojadotho5989', // Use staging key for non-prod
// Player metadata
player_name: 'My Video Player',
player_init_time: playerInitTime,
// Video metadata
video_id: 'stream-12345',
video_title: 'My Live Stream',
video_stream_type: 'live',
video_source_is_live: true,
video_creator_id: 'channel-67890',
// Viewer metadata
viewer_user_id: 'user-abc123',
sub_property_id: 'your-internal-user-id',
page_type: 'live',
}
});
</script>React Component
import mux from 'mux-embed';
import React, { useEffect, useRef } from 'react';
export default function VideoPlayer({ stream, channel, user }) {
const videoRef = useRef(null);
useEffect(() => {
if (!videoRef.current) return;
const playerInitTime = mux.utils.now();
mux.monitor(videoRef.current, {
debug: process.env.NODE_ENV === 'development',
data: {
env_key: process.env.NEXT_PUBLIC_MUX_ENV_KEY,
// Player metadata
player_name: 'React Video Player',
player_init_time: playerInitTime,
// Video metadata
video_id: String(stream.id),
video_title: stream.title,
video_stream_type: 'live',
video_source_is_live: true,
video_creator_id: String(channel.id),
// Viewer metadata
viewer_user_id: String(user.id),
page_type: 'live',
}
});
// Cleanup on unmount
return () => {
if (videoRef.current) {
mux.emit(videoRef.current, 'destroy');
}
};
}, [stream, channel, user]);
return (
<video
ref={videoRef}
controls
src={stream.playback_url}
style={{ width: '100%' }}
/>
);
}With HLS.js
import mux from 'mux-embed';
import Hls from 'hls.js';
import { useEffect, useRef } from 'react';
export default function HLSPlayer({ src, metadata }) {
const videoRef = useRef(null);
const hlsRef = useRef(null);
useEffect(() => {
const video = videoRef.current;
if (!video || !src) return;
const playerInitTime = mux.utils.now();
if (Hls.isSupported()) {
hlsRef.current = new Hls();
hlsRef.current.loadSource(src);
hlsRef.current.attachMedia(video);
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
video.src = src;
}
// Initialize Mux monitoring
mux.monitor(video, {
debug: false,
hlsjs: hlsRef.current, // Pass HLS.js instance for better tracking
data: {
env_key: 'nl7g58q1rs9lkojadotho5989',
player_name: 'HLS Player',
player_init_time: playerInitTime,
...metadata,
}
});
return () => {
if (hlsRef.current) {
hlsRef.current.destroy();
}
mux.emit(video, 'destroy');
};
}, [src, metadata]);
return <video ref={videoRef} controls style={{ width: '100%' }} />;
}With NanoPlayer (nanocosmos H5Live)
NanoPlayer may create multiple video elements during ABR (Adaptive Bitrate) switching, especially on iOS. Use the onActiveVideoElementChange event to:
- Get the video element when it's created
- Listen for video element changes during stream switches
Event Data
| Property | Type | Description |
|---|---|---|
activeVideoElement | HTMLVideoElement | The currently active video element |
videoElementList | Array<HTMLVideoElement> | All video elements (multiple on iOS ABR) |
Vanilla JavaScript
var player = new NanoPlayer('playerDiv');
var muxInstance = null;
var config = {
source: {
entries: [{
h5live: {
rtmp: {
url: 'rtmp://bintu-play.nanocosmos.de/play',
streamname: 'XXXXX-YYYYY'
},
server: {
websocket: 'wss://bintu-h5live.nanocosmos.de:443/h5live/stream',
hls: 'https://bintu-h5live.nanocosmos.de:443/h5live/http/playlist.m3u8'
}
}
}]
},
playback: {
autoplay: true,
automute: true,
},
events: {
onActiveVideoElementChange: function(event) {
var activeVideoElement = event.data.activeVideoElement;
var videoElementList = event.data.videoElementList;
if (!activeVideoElement) return;
// Destroy previous Mux instance if video element changed
if (muxInstance) {
mux.emit(muxInstance, 'destroy');
}
// Store reference and initialize Mux on the new active element
muxInstance = activeVideoElement;
var playerInitTime = mux.utils.now();
mux.monitor(activeVideoElement, {
debug: false,
data: {
env_key: 'nl7g58q1rs9lkojadotho5989',
player_name: 'NanoPlayer',
player_init_time: playerInitTime,
video_id: 'stream-12345',
video_title: 'My Live Stream',
video_stream_type: 'live',
video_source_is_live: true,
video_creator_id: 'channel-67890',
viewer_user_id: 'user-abc123',
page_type: 'live',
}
});
console.log('Mux monitoring initialized on:', activeVideoElement.id);
console.log('Total video elements:', videoElementList.length);
},
onDestroy: function() {
// Cleanup Mux when NanoPlayer is destroyed
if (muxInstance) {
mux.emit(muxInstance, 'destroy');
muxInstance = null;
}
}
}
};
player.setup(config).then(function(config) {
console.log('NanoPlayer setup complete');
}).catch(function(error) {
console.error('NanoPlayer setup failed:', error);
});React Component
import { useEffect, useRef } from 'react';
export default function NanoPlayerWithAnalytics({ streamConfig, metadata }) {
const playerRef = useRef(null);
const muxElementRef = useRef(null);
useEffect(() => {
if (typeof NanoPlayer === 'undefined') {
console.error('NanoPlayer not loaded');
return;
}
const player = new NanoPlayer('nanoPlayerDiv');
playerRef.current = player;
const config = {
source: streamConfig.source,
playback: {
autoplay: true,
automute: true,
...streamConfig.playback,
},
events: {
onActiveVideoElementChange: (event) => {
const { activeVideoElement, videoElementList } = event.data;
if (!activeVideoElement) return;
// Cleanup previous Mux instance
if (muxElementRef.current) {
mux.emit(muxElementRef.current, 'destroy');
}
// Initialize Mux on new active element
muxElementRef.current = activeVideoElement;
const playerInitTime = mux.utils.now();
mux.monitor(activeVideoElement, {
debug: process.env.NODE_ENV === 'development',
data: {
env_key: process.env.NEXT_PUBLIC_MUX_ENV_KEY,
player_name: 'NanoPlayer',
player_init_time: playerInitTime,
...metadata,
}
});
},
onDestroy: () => {
if (muxElementRef.current) {
mux.emit(muxElementRef.current, 'destroy');
muxElementRef.current = null;
}
},
},
};
player.setup(config);
return () => {
if (muxElementRef.current) {
mux.emit(muxElementRef.current, 'destroy');
}
if (playerRef.current) {
playerRef.current.destroy();
}
};
}, [streamConfig, metadata]);
return <div id="nanoPlayerDiv" style={{ width: '100%', aspectRatio: '16/9' }} />;
}Important Notes
- ABR on iOS: NanoPlayer may create multiple
<video>elements for smooth ABR switching. Always use theactiveVideoElementfrom the event. - Element Changes: The active element can change during playback. Re-initialize Mux monitoring when this happens.
- Cleanup: Always destroy the previous Mux instance before creating a new one to avoid duplicate tracking.
NanoPlayer Documentation
Updating Metadata Dynamically
Video Change Event
When the video source changes (e.g., playlist advancement), emit a videochange event:
// Using selector
mux.emit('#my-player', 'videochange', {
video_id: 'new-video-456',
video_title: 'Next Video in Playlist',
video_stream_type: 'on-demand',
video_content_type: 'clip',
});
// Using element reference (React)
mux.emit(videoRef.current, 'videochange', {
video_id: newClip.id,
video_title: newClip.title,
video_stream_type: 'on-demand',
video_content_type: 'clip',
});Cleanup / Destroy
Always clean up when unmounting or destroying the player:
// Emit destroy event to end the current view session
mux.emit('#my-player', 'destroy');
// Or with element reference
mux.emit(videoRef.current, 'destroy');Built-in vs Custom Fields
Built-in Mux Dimensions
These fields are natively supported by Mux and appear in dashboard reports:
| Field | Purpose | Level |
|---|---|---|
video_id | Video identifier | basic |
video_title | Video title | basic |
video_stream_type | live/on-demand | basic |
video_content_type | Content categorization | basic |
video_creator_id | Creator identifier | advanced |
viewer_user_id | Viewer identifier | advanced |
sub_property_id | Sub-property grouping | basic |
Custom Fields
Use custom_1 through custom_10 for platform-specific data:
| Field | Usage |
|---|---|
custom_1 | Reserved for external user ID (optional, provided by integrators) |
custom_2 - custom_10 | Available for future use |
Additional Resources
- Mux Metadata Documentation
- Make your dimensions actionable with metadata
- Extend Data with custom metadata
Backend Analytics
The backend automatically processes analytics data using the video_id field to filter views from Mux's API. Ensure your frontend sends the correct video_id to match backend expectations.