# 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