/** * Video Analytics Tracker * Tracks views, watch time, and retention automatically */ class VideoAnalyticsTracker { constructor(videoId, options = {}) { this.videoId = videoId; this.playerElement = options.playerElement || document.querySelector('video'); this.trackingInterval = options.trackingInterval || 10000; // 10 seconds this.startTime = Date.now(); this.lastTrackedSecond = 0; this.watchedMinutes = new Set(); this.hasTrackedView = false; this.isPlaying = false; this.init(); } init() { if (!this.playerElement) { console.warn('Video player element not found'); return; } this.attachEventListeners(); this.startPeriodicTracking(); this.detectTrafficSource(); } attachEventListeners() { // Track when video starts playing this.playerElement.addEventListener('play', () => { this.isPlaying = true; if (!this.hasTrackedView) { this.trackView(); this.hasTrackedView = true; } }); // Track when video pauses this.playerElement.addEventListener('pause', () => { this.isPlaying = false; this.trackProgress(); }); // Track when video ends this.playerElement.addEventListener('ended', () => { this.isPlaying = false; this.trackCompletion(); }); // Track time updates (for retention) this.playerElement.addEventListener('timeupdate', () => { this.trackRetention(); }); // Track when user leaves page window.addEventListener('beforeunload', () => { this.trackProgress(); }); } async trackView() { try { await fetch('/api/analytics.php?action=track', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ video_id: this.videoId, event_type: 'view', source_type: this.trafficSource?.type, source_detail: this.trafficSource?.detail }) }); console.log('View tracked'); } catch (error) { console.error('Failed to track view:', error); } } async trackProgress() { const currentTime = this.playerElement.currentTime; const duration = this.playerElement.duration; if (!duration || duration === 0) return; const watchDuration = Math.floor(currentTime); const completionPercentage = Math.min((currentTime / duration) * 100, 100); try { await fetch('/api/analytics.php?action=track', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ video_id: this.videoId, event_type: 'watch_time', watch_duration: watchDuration, completion_percentage: completionPercentage.toFixed(2) }) }); } catch (error) { console.error('Failed to track progress:', error); } } async trackCompletion() { try { await fetch('/api/analytics.php?action=track', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ video_id: this.videoId, event_type: 'completion', completion_percentage: 100 }) }); console.log('Completion tracked'); } catch (error) { console.error('Failed to track completion:', error); } } trackRetention() { const currentTime = Math.floor(this.playerElement.currentTime); const currentMinute = Math.floor(currentTime / 60); // Track each minute once if (!this.watchedMinutes.has(currentMinute)) { this.watchedMinutes.add(currentMinute); fetch('/api/analytics.php?action=track', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ video_id: this.videoId, event_type: 'view', minute: currentMinute }) }).catch(err => console.error('Failed to track retention:', err)); } } detectTrafficSource() { const urlParams = new URLSearchParams(window.location.search); const referrer = document.referrer; // Check URL parameters first if (urlParams.get('ref')) { this.trafficSource = { type: 'external', detail: urlParams.get('ref') }; } else if (urlParams.get('list')) { this.trafficSource = { type: 'playlist', detail: urlParams.get('list') }; } else if (urlParams.get('notification')) { this.trafficSource = { type: 'notification', detail: urlParams.get('notification') }; } else if (referrer) { // Parse referrer try { const refUrl = new URL(referrer); const hostname = refUrl.hostname; if (hostname === window.location.hostname) { // Internal referrer if (refUrl.pathname.includes('/search')) { this.trafficSource = { type: 'search', detail: refUrl.search }; } else if (refUrl.pathname.includes('/browse')) { this.trafficSource = { type: 'suggested', detail: 'browse' }; } else { this.trafficSource = { type: 'internal', detail: refUrl.pathname }; } } else { // External referrer if (hostname.includes('google')) { this.trafficSource = { type: 'search', detail: 'google' }; } else if (hostname.includes('facebook') || hostname.includes('twitter') || hostname.includes('reddit')) { this.trafficSource = { type: 'social', detail: hostname }; } else { this.trafficSource = { type: 'external', detail: hostname }; } } } catch (e) { this.trafficSource = { type: 'direct', detail: '' }; } } else { this.trafficSource = { type: 'direct', detail: '' }; } console.log('Traffic source:', this.trafficSource); } startPeriodicTracking() { // Periodically save progress while playing this.trackingTimer = setInterval(() => { if (this.isPlaying) { this.trackProgress(); } }, this.trackingInterval); } destroy() { if (this.trackingTimer) { clearInterval(this.trackingTimer); } // Final progress update this.trackProgress(); } } // Auto-initialize if video element and video ID exist document.addEventListener('DOMContentLoaded', () => { const videoElement = document.querySelector('video'); const videoId = new URLSearchParams(window.location.search).get('v'); if (videoElement && videoId) { window.videoAnalyticsTracker = new VideoAnalyticsTracker(videoId, { playerElement: videoElement }); } });