UNPKG

unified-video-framework

Version:

Cross-platform video player framework supporting iOS, Android, Web, Smart TVs (Samsung/LG), Roku, and more

902 lines (762 loc) 24.8 kB
# Video Player Analytics Integration A fully dynamic analytics system for video players that integrates seamlessly with your existing player analytics API while supporting multiple analytics providers. ## Features - **🔄 Dynamic Provider Management** - Add, remove, and configure analytics providers at runtime - **📊 Player Analytics API Integration** - Direct integration with your existing analytics backend - **🚀 Multi-Provider Support** - Send events to multiple analytics services simultaneously - **🔌 Extensible Architecture** - Easy to add custom analytics providers - **📱 Cross-Platform Support** - Works on web, mobile, tablet, and smart TV - **⚡ Real-time Event Tracking** - Comprehensive player event tracking with batching - **💾 Offline Storage** - Event persistence during network outages - **🎯 Smart Batching** - Efficient event batching with retry logic - **🛡️ Error Handling** - Robust error handling and recovery ## Quick Start ### 1. Basic Integration with Player Analytics API #### TypeScript ```typescript import { createDynamicAnalyticsManager, createPlayerAnalyticsProviderConfig, AnalyticsProviderType, DynamicAnalyticsConfig } from './analytics'; // Configure analytics const analyticsConfig: DynamicAnalyticsConfig = { enabled: true, providers: [ { name: 'player-analytics', type: AnalyticsProviderType.PLAYER_ANALYTICS, enabled: true, priority: 1, config: createPlayerAnalyticsProviderConfig( 'https://api.flicknexs.com', // Your API base URL 'your-api-key', // Your API key 'main-player', // Player ID { tenantId: 'your-tenant-id', // Optional heartbeatInterval: 10, // 10 seconds batchSize: 10, // 10 events per batch flushInterval: 30 // 30 seconds } ) } ], globalSettings: { enableConsoleLogging: true, enableErrorReporting: true, sessionTimeout: 60 // 60 minutes } }; // Create analytics manager const analyticsManager = createDynamicAnalyticsManager(analyticsConfig); ``` #### JavaScript (ES6 Modules) ```javascript import { createDynamicAnalyticsManager, createPlayerAnalyticsProviderConfig, AnalyticsProviderType } from './analytics.js'; // Configure analytics const analyticsConfig = { enabled: true, providers: [ { name: 'player-analytics', type: AnalyticsProviderType.PLAYER_ANALYTICS, enabled: true, priority: 1, config: createPlayerAnalyticsProviderConfig( 'https://api.flicknexs.com', // Your API base URL 'your-api-key', // Your API key 'main-player', // Player ID { tenantId: 'your-tenant-id', // Optional heartbeatInterval: 10, // 10 seconds batchSize: 10, // 10 events per batch flushInterval: 30 // 30 seconds } ) } ], globalSettings: { enableConsoleLogging: true, enableErrorReporting: true, sessionTimeout: 60 // 60 minutes } }; // Create analytics manager const analyticsManager = createDynamicAnalyticsManager(analyticsConfig); ``` #### JavaScript (CommonJS) ```javascript const { createDynamicAnalyticsManager, createPlayerAnalyticsProviderConfig, AnalyticsProviderType } = require('./analytics'); // Configure analytics const analyticsConfig = { enabled: true, providers: [ { name: 'player-analytics', type: AnalyticsProviderType.PLAYER_ANALYTICS, enabled: true, priority: 1, config: createPlayerAnalyticsProviderConfig( 'https://api.flicknexs.com', // Your API base URL 'your-api-key', // Your API key 'main-player', // Player ID { tenantId: 'your-tenant-id', // Optional heartbeatInterval: 10, // 10 seconds batchSize: 10, // 10 events per batch flushInterval: 30 // 30 seconds } ) } ], globalSettings: { enableConsoleLogging: true, enableErrorReporting: true, sessionTimeout: 60 // 60 minutes } }; // Create analytics manager const analyticsManager = createDynamicAnalyticsManager(analyticsConfig); ``` ### 2. Start Analytics Session #### TypeScript ```typescript // Start session when video loads const sessionId = analyticsManager.startSession({ id: 'video-123', title: 'Sample Video', type: 'video', duration: 3600, url: 'https://example.com/video.mp4' }, { userId: 'user-456', customData: 'any-custom-data' }); ``` #### JavaScript ```javascript // Start session when video loads const sessionId = analyticsManager.startSession({ id: 'video-123', title: 'Sample Video', type: 'video', duration: 3600, url: 'https://example.com/video.mp4' }, { userId: 'user-456', customData: 'any-custom-data' }); ``` ### 3. Track Events #### TypeScript ```typescript // Track player events analyticsManager.trackEvent('play', playerState); analyticsManager.trackEvent('pause', playerState); analyticsManager.trackEvent('seeking', playerState, { seekFrom: 120, seekTo: 180 }); // Track custom events analyticsManager.trackCustomEvent('quality_change', { newQuality: '1080p', previousQuality: '720p', reason: 'user_selected' }); // End session await analyticsManager.endSession(); ``` #### JavaScript ```javascript // Track player events analyticsManager.trackEvent('play', playerState); analyticsManager.trackEvent('pause', playerState); analyticsManager.trackEvent('seeking', playerState, { seekFrom: 120, seekTo: 180 }); // Track custom events analyticsManager.trackCustomEvent('quality_change', { newQuality: '1080p', previousQuality: '720p', reason: 'user_selected' }); // End session (using async/await) try { await analyticsManager.endSession(); console.log('Analytics session ended successfully'); } catch (error) { console.error('Error ending analytics session:', error); } // Alternative: End session with Promises analyticsManager.endSession() .then(() => { console.log('Analytics session ended successfully'); }) .catch((error) => { console.error('Error ending analytics session:', error); }); ``` ## API Integration The system maps internal events to your player analytics API format: ### Event Mapping | Internal Event | API Event | Description | |---|---|---| | `play` | `play` | Playback started | | `pause` | `pause` | Playback paused | | `ended` | `ended` | Playback completed | | `seeking` | `seeking` | Seeking started | | `seeked` | `seeked` | Seeking completed | | `waiting` | `waiting` | Buffering started | | `stalled` | `stalled` | Buffering ended | | `qualitychange` | `quality_change` | Quality changed | | `volumechange` | `volume_change` | Volume changed | | `fullscreenchange` | `fullscreen_change` | Fullscreen toggled | | `error` | `error` | Player error | | `timeupdate` | `heartbeat` | Regular progress update | | `chapterstart` | `custom_chapter_start` | Chapter started | | `chapterend` | `custom_chapter_end` | Chapter ended | | `chapterskip` | `custom_chapter_skip` | Chapter skipped | ### API Payload Structure Events are sent to your `/analytics/player/ingest` endpoint with this structure: ```json { "session": { "sessionId": "sess_1728567890_abc123", "playerId": "main-player", "timestamp": 1728567890000, "customData": { "userId": "user-456" } }, "events": [ { "eventType": "play", "timestamp": 1728567890000, "currentTime": 120.5, "video": { "id": "video-123", "title": "Sample Video", "type": "video", "duration": 3600 }, "device": { "deviceType": "desktop", "os": "Windows", "browser": "Chrome", "screen": { "width": 1920, "height": 1080 } }, "player": { "volume": 0.8, "muted": false, "playbackRate": 1 } } ] } ``` ## Advanced Usage ### Multiple Analytics Providers #### TypeScript ```typescript const config: DynamicAnalyticsConfig = { enabled: true, providers: [ // Primary: Your Player Analytics API { name: 'player-analytics', type: AnalyticsProviderType.PLAYER_ANALYTICS, enabled: true, priority: 1, config: createPlayerAnalyticsProviderConfig( 'https://api.flicknexs.com', 'your-api-key', 'main-player' ) }, // Secondary: Custom Provider (e.g., Google Analytics) { name: 'google-analytics', type: AnalyticsProviderType.CUSTOM, enabled: true, priority: 2, config: { factory: (config) => new CustomGoogleAnalyticsProvider(config), measurementId: 'G-XXXXXXXXXX' } } ] }; ``` #### JavaScript ```javascript const config = { enabled: true, providers: [ // Primary: Your Player Analytics API { name: 'player-analytics', type: AnalyticsProviderType.PLAYER_ANALYTICS, enabled: true, priority: 1, config: createPlayerAnalyticsProviderConfig( 'https://api.flicknexs.com', 'your-api-key', 'main-player' ) }, // Secondary: Custom Provider (e.g., Google Analytics) { name: 'google-analytics', type: AnalyticsProviderType.CUSTOM, enabled: true, priority: 2, config: { factory: (config) => new CustomGoogleAnalyticsProvider(config), measurementId: 'G-XXXXXXXXXX' } } ] }; ``` ### Dynamic Provider Management #### TypeScript ```typescript // Add provider at runtime analyticsManager.addProvider( 'new-analytics', AnalyticsProviderType.CUSTOM, { factory: (config) => new NewAnalyticsProvider(config) } ); // Toggle provider analyticsManager.toggleProvider('google-analytics', false); // Remove provider await analyticsManager.removeProvider('old-provider'); ``` #### JavaScript ```javascript // Add provider at runtime analyticsManager.addProvider( 'new-analytics', AnalyticsProviderType.CUSTOM, { factory: (config) => new NewAnalyticsProvider(config) } ); // Toggle provider analyticsManager.toggleProvider('google-analytics', false); // Remove provider (using async/await) try { await analyticsManager.removeProvider('old-provider'); console.log('Provider removed successfully'); } catch (error) { console.error('Error removing provider:', error); } // Remove provider (using Promises) analyticsManager.removeProvider('old-provider') .then(() => { console.log('Provider removed successfully'); }) .catch((error) => { console.error('Error removing provider:', error); }); ``` ### Environment-based Configuration #### TypeScript ```typescript export function createEnvironmentAnalytics() { const isDevelopment = process.env.NODE_ENV === 'development'; return createDynamicAnalyticsManager({ enabled: !isDevelopment || process.env.ENABLE_DEV_ANALYTICS === 'true', providers: [ { name: 'player-analytics', type: AnalyticsProviderType.PLAYER_ANALYTICS, enabled: true, config: createPlayerAnalyticsProviderConfig( process.env.ANALYTICS_BASE_URL || 'https://api.flicknexs.com', process.env.ANALYTICS_API_KEY || '', process.env.PLAYER_ID || 'default-player', { heartbeatInterval: isDevelopment ? 5 : 10, batchSize: isDevelopment ? 5 : 10, flushInterval: isDevelopment ? 15 : 30 } ) } ], globalSettings: { enableConsoleLogging: isDevelopment, enableErrorReporting: true, sessionTimeout: isDevelopment ? 30 : 120 } }); } ``` #### JavaScript (ES6 Modules) ```javascript export function createEnvironmentAnalytics() { const isDevelopment = process.env.NODE_ENV === 'development'; return createDynamicAnalyticsManager({ enabled: !isDevelopment || process.env.ENABLE_DEV_ANALYTICS === 'true', providers: [ { name: 'player-analytics', type: AnalyticsProviderType.PLAYER_ANALYTICS, enabled: true, config: createPlayerAnalyticsProviderConfig( process.env.ANALYTICS_BASE_URL || 'https://api.flicknexs.com', process.env.ANALYTICS_API_KEY || '', process.env.PLAYER_ID || 'default-player', { heartbeatInterval: isDevelopment ? 5 : 10, batchSize: isDevelopment ? 5 : 10, flushInterval: isDevelopment ? 15 : 30 } ) } ], globalSettings: { enableConsoleLogging: isDevelopment, enableErrorReporting: true, sessionTimeout: isDevelopment ? 30 : 120 } }); } ``` #### JavaScript (CommonJS) ```javascript function createEnvironmentAnalytics() { const isDevelopment = process.env.NODE_ENV === 'development'; return createDynamicAnalyticsManager({ enabled: !isDevelopment || process.env.ENABLE_DEV_ANALYTICS === 'true', providers: [ { name: 'player-analytics', type: AnalyticsProviderType.PLAYER_ANALYTICS, enabled: true, config: createPlayerAnalyticsProviderConfig( process.env.ANALYTICS_BASE_URL || 'https://api.flicknexs.com', process.env.ANALYTICS_API_KEY || '', process.env.PLAYER_ID || 'default-player', { heartbeatInterval: isDevelopment ? 5 : 10, batchSize: isDevelopment ? 5 : 10, flushInterval: isDevelopment ? 15 : 30 } ) } ], globalSettings: { enableConsoleLogging: isDevelopment, enableErrorReporting: true, sessionTimeout: isDevelopment ? 30 : 120 } }); } module.exports = { createEnvironmentAnalytics }; ``` ## Device Detection The system automatically detects device information: - **Device Type**: mobile, tablet, smart_tv, desktop, tv - **Operating System**: Windows, macOS, Linux, iOS, Android, tvOS, etc. - **Browser**: Chrome, Firefox, Safari, Edge, Opera - **Screen Resolution**: Actual screen dimensions - **Network**: Connection type, bandwidth, RTT (when available) ## Event Batching Events are batched for efficient network usage: - **Batch Size**: Configurable number of events per batch (default: 10) - **Batch Interval**: Time interval for sending batches (default: 30s) - **Retry Logic**: Exponential backoff with jitter - **Offline Storage**: Events persisted in localStorage during network outages ## Engagement Tracking The system tracks comprehensive engagement metrics: - **Total Watch Time**: Actual time spent watching - **Unique Watch Time**: Time spent watching unique content (no replay) - **Completion Percentage**: How much of the video was watched - **Seek Events**: Number of seek operations - **Quality Changes**: Number of quality adjustments - **Fullscreen Toggles**: Number of fullscreen mode changes - **Buffering Time**: Total time spent buffering ## Error Handling Robust error handling ensures analytics don't break your player: - **Provider Failures**: Individual provider failures don't affect others - **Network Errors**: Automatic retry with exponential backoff - **API Errors**: Graceful error handling and logging - **Fallback Behavior**: Analytics failures never break player functionality ## Language Support ### TypeScript Support Full TypeScript support with comprehensive type definitions: ```typescript import type { DynamicAnalyticsConfig, PlayerAnalyticsConfig, AnalyticsEventData, EngagementData, DeviceData } from './analytics'; ``` ### JavaScript Support The analytics system works seamlessly with JavaScript projects: #### ES6 Modules ```javascript // Import the functions you need import { createDynamicAnalyticsManager, createPlayerAnalyticsProviderConfig, AnalyticsProviderType } from './analytics.js'; // All functions work the same as TypeScript examples const manager = createDynamicAnalyticsManager(config); ``` #### CommonJS ```javascript // Require the functions you need const { createDynamicAnalyticsManager, createPlayerAnalyticsProviderConfig, AnalyticsProviderType } = require('./analytics'); // All functions work the same as TypeScript examples const manager = createDynamicAnalyticsManager(config); ``` #### Browser (UMD) ```html <!-- Include the analytics library --> <script src="./analytics.umd.js"></script> <script> // Access via global VideoAnalytics object const { createDynamicAnalyticsManager, createPlayerAnalyticsProviderConfig, AnalyticsProviderType } = VideoAnalytics; const manager = createDynamicAnalyticsManager(config); </script> ``` ## Browser Support - **Modern Browsers**: Chrome, Firefox, Safari, Edge (latest versions) - **Mobile Browsers**: iOS Safari, Chrome Mobile, Samsung Internet - **Smart TV**: Tizen, webOS, Android TV browsers - **Legacy Support**: Graceful degradation for older browsers ## Performance - **Minimal Impact**: < 1% CPU usage during normal operation - **Memory Efficient**: Smart memory management with cleanup - **Network Optimized**: Batching reduces network requests by 90% - **Async Processing**: Non-blocking event processing ## Security - **API Key Protection**: Secure API key handling - **Data Privacy**: No PII collection by default - **HTTPS Only**: All API communication over HTTPS - **GDPR Compliance**: GDPR mode available for EU users ## Complete Integration Examples ### Video Player Integration (JavaScript) ```javascript // analytics-integration.js import { createDynamicAnalyticsManager, createPlayerAnalyticsProviderConfig, AnalyticsProviderType } from './analytics.js'; class VideoPlayerWithAnalytics { constructor(videoElement, options = {}) { this.video = videoElement; this.options = options; this.analyticsManager = null; this.sessionId = null; this.playerState = { currentTime: 0, duration: 0, volume: 1, muted: false, playbackRate: 1, buffered: null }; this.initializeAnalytics(); this.attachEventListeners(); } initializeAnalytics() { const analyticsConfig = { enabled: true, providers: [ { name: 'player-analytics', type: AnalyticsProviderType.PLAYER_ANALYTICS, enabled: true, priority: 1, config: createPlayerAnalyticsProviderConfig( this.options.analyticsBaseUrl || 'https://api.flicknexs.com', this.options.analyticsApiKey || '', this.options.playerId || 'video-player', { heartbeatInterval: 10, batchSize: 10, flushInterval: 30 } ) } ], globalSettings: { enableConsoleLogging: this.options.debug || false, enableErrorReporting: true, sessionTimeout: 60 } }; this.analyticsManager = createDynamicAnalyticsManager(analyticsConfig); } attachEventListeners() { // Core playback events this.video.addEventListener('loadeddata', () => { this.updatePlayerState(); this.startAnalyticsSession(); }); this.video.addEventListener('play', () => { this.updatePlayerState(); this.analyticsManager.trackEvent('play', this.playerState); }); this.video.addEventListener('pause', () => { this.updatePlayerState(); this.analyticsManager.trackEvent('pause', this.playerState); }); this.video.addEventListener('ended', () => { this.updatePlayerState(); this.analyticsManager.trackEvent('ended', this.playerState); this.endAnalyticsSession(); }); this.video.addEventListener('seeking', () => { this.analyticsManager.trackEvent('seeking', this.playerState, { seekFrom: this.playerState.currentTime }); }); this.video.addEventListener('seeked', () => { this.updatePlayerState(); this.analyticsManager.trackEvent('seeked', this.playerState); }); this.video.addEventListener('timeupdate', () => { this.updatePlayerState(); // Heartbeat events are handled automatically by the analytics manager }); this.video.addEventListener('volumechange', () => { this.updatePlayerState(); this.analyticsManager.trackEvent('volumechange', this.playerState); }); this.video.addEventListener('waiting', () => { this.updatePlayerState(); this.analyticsManager.trackEvent('waiting', this.playerState); }); this.video.addEventListener('canplay', () => { this.updatePlayerState(); this.analyticsManager.trackEvent('canplay', this.playerState); }); this.video.addEventListener('error', (event) => { this.updatePlayerState(); this.analyticsManager.trackEvent('error', this.playerState, { error: event.target.error }); }); } updatePlayerState() { this.playerState = { currentTime: this.video.currentTime, duration: this.video.duration || 0, volume: this.video.volume, muted: this.video.muted, playbackRate: this.video.playbackRate, buffered: this.video.buffered }; } startAnalyticsSession() { if (!this.sessionId && this.options.videoInfo) { this.sessionId = this.analyticsManager.startSession( this.options.videoInfo, this.options.userInfo || {} ); } } async endAnalyticsSession() { if (this.sessionId) { try { await this.analyticsManager.endSession(); this.sessionId = null; } catch (error) { console.error('Error ending analytics session:', error); } } } // Custom event tracking trackCustomEvent(eventName, data = {}) { this.analyticsManager.trackCustomEvent(eventName, data); } // Quality change tracking onQualityChange(newQuality, previousQuality) { this.analyticsManager.trackCustomEvent('quality_change', { newQuality, previousQuality, timestamp: Date.now() }); } // Chapter tracking onChapterStart(chapterInfo) { this.analyticsManager.trackCustomEvent('chapter_start', chapterInfo); } onChapterEnd(chapterInfo) { this.analyticsManager.trackCustomEvent('chapter_end', chapterInfo); } } // Usage example const videoElement = document.querySelector('#my-video'); const player = new VideoPlayerWithAnalytics(videoElement, { analyticsBaseUrl: 'https://api.flicknexs.com', analyticsApiKey: 'your-api-key', playerId: 'main-video-player', debug: true, videoInfo: { id: 'video-123', title: 'My Awesome Video', type: 'video', duration: 3600, url: 'https://example.com/video.mp4' }, userInfo: { userId: 'user-456', customData: { plan: 'premium' } } }); // Track custom events player.trackCustomEvent('user_interaction', { action: 'subtitle_toggle', enabled: true }); export { VideoPlayerWithAnalytics }; ``` ## Examples See the following files for comprehensive usage examples: - `/examples/DynamicAnalyticsExample.ts` (TypeScript) - `/examples/DynamicAnalyticsExample.js` (JavaScript) - `/examples/VideoPlayerIntegration.js` (JavaScript Player Integration) Examples include: - Basic integration - Multi-provider setup - Custom provider implementation - Environment-based configuration - Video player integration - Real-time event tracking - Error handling patterns ## Contributing When adding new analytics providers: 1. Implement the `BaseAnalyticsProvider` interface 2. Add provider type to `AnalyticsProviderType` enum 3. Update the `createProvider` method in `DynamicAnalyticsManager` 4. Add comprehensive tests 5. Update documentation ## License Part of the Unified Video Framework - Licensed under your project's license terms. --- Built with ❤️ by the Flicknexs Team