Skip to content

Event Handling Guide ​

MediaFox provides a comprehensive event system that allows you to respond to various playback events. This guide covers all available events and best practices for event handling.

Overview ​

MediaFox's event system is built on a type-safe EventEmitter that supports all standard HTML media events plus custom MediaFox-specific events. Events provide notifications about state changes, user interactions, and system status.

Available Events ​

Playback Events ​

typescript
// Core playback events
player.on('play', () => {
  console.log('Playback started');
});

player.on('pause', () => {
  console.log('Playback paused');
});

player.on('ended', () => {
  console.log('Playback completed');
});

player.on('seeking', () => {
  console.log('User is seeking...');
});

player.on('seeked', () => {
  console.log('Seek operation completed');
});

player.on('timeupdate', (currentTime: number) => {
  console.log(`Current time: ${currentTime}s`);
});

player.on('ratechange', (rate: number) => {
  console.log(`Playback rate changed to: ${rate}x`);
});

Loading Events ​

typescript
// Media loading lifecycle
player.on('loadstart', () => {
  console.log('Started loading media');
});

player.on('loadedmetadata', () => {
  console.log('Media metadata loaded');
});

player.on('loadeddata', () => {
  console.log('First frame loaded');
});

player.on('canplay', () => {
  console.log('Media can start playing');
});

player.on('canplaythrough', () => {
  console.log('Media can play through without buffering');
});

player.on('progress', (buffered: TimeRanges) => {
  console.log('Download progress updated');
});

player.on('waiting', () => {
  console.log('Waiting for data...');
});

player.on('stalled', () => {
  console.log('Media loading stalled');
});

Audio Events ​

typescript
// Audio-specific events
player.on('volumechange', (volume: number, muted: boolean) => {
  console.log(`Volume: ${Math.round(volume * 100)}%, Muted: ${muted}`);
});

Error Events ​

typescript
// Error handling
player.on('error', (error: Error) => {
  console.error('Player error:', error.message);

  // Handle different error types
  if (error instanceof MediaError) {
    handleMediaError(error);
  } else if (error instanceof NetworkError) {
    handleNetworkError(error);
  }
});

function handleMediaError(error: MediaError) {
  switch (error.code) {
    case MediaError.MEDIA_ERR_ABORTED:
      console.log('Media loading aborted');
      break;
    case MediaError.MEDIA_ERR_NETWORK:
      console.log('Network error while loading media');
      break;
    case MediaError.MEDIA_ERR_DECODE:
      console.log('Media decoding error');
      break;
    case MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED:
      console.log('Media format not supported');
      break;
  }
}

MediaFox-Specific Events ​

typescript
// Track management events
player.on('tracksChanged', (tracks) => {
  console.log('Available tracks:', tracks);
  console.log(`Video tracks: ${tracks.video.length}`);
  console.log(`Audio tracks: ${tracks.audio.length}`);
});

player.on('trackChanged', (type: 'video' | 'audio', trackIndex: number) => {
  console.log(`${type} track changed to index: ${trackIndex}`);
});

// Source management events
player.on('sourceChanged', (newSource: string) => {
  console.log('Media source changed to:', newSource);
});

// Player lifecycle events
player.on('ready', () => {
  console.log('Player is ready for playback');
});

player.on('destroy', () => {
  console.log('Player has been destroyed');
});

Event Listener Management ​

Adding Event Listeners ​

typescript
// Basic event listener
const playHandler = () => {
  console.log('Playing');
};
player.on('play', playHandler);

// Event listener with data
player.on('timeupdate', (currentTime: number) => {
  updateProgressBar(currentTime);
});

// Multiple events with same handler
const loadingHandler = () => {
  showLoadingSpinner(true);
};
player.on('loadstart', loadingHandler);
player.on('waiting', loadingHandler);

// One-time event listener
player.once('loadeddata', () => {
  console.log('First data loaded - this will only fire once');
});

Removing Event Listeners ​

typescript
// Remove specific event listener
player.off('play', playHandler);

// Remove all listeners for an event
player.off('timeupdate');

// Remove all event listeners
player.removeAllListeners();

// Best practice: Store references for cleanup
class PlayerManager {
  private eventHandlers = new Map();

  constructor(private player: MediaFox) {
    this.setupEventListeners();
  }

  private setupEventListeners() {
    const playHandler = this.onPlay.bind(this);
    const errorHandler = this.onError.bind(this);

    this.player.on('play', playHandler);
    this.player.on('error', errorHandler);

    // Store references for cleanup
    this.eventHandlers.set('play', playHandler);
    this.eventHandlers.set('error', errorHandler);
  }

  private onPlay() {
    console.log('Playback started');
  }

  private onError(error: Error) {
    console.error('Error:', error);
  }

  destroy() {
    // Clean up all event listeners
    this.eventHandlers.forEach((handler, event) => {
      this.player.off(event, handler);
    });
    this.eventHandlers.clear();
  }
}

Advanced Event Patterns ​

Event Chaining ​

typescript
class EventChain {
  constructor(private player: MediaFox) {
    this.setupEventChain();
  }

  private setupEventChain() {
    this.player
      .on('loadstart', () => {
        console.log('1. Loading started');
        this.showLoadingState();
      })
      .on('loadedmetadata', () => {
        console.log('2. Metadata loaded');
        this.showMediaInfo();
      })
      .on('loadeddata', () => {
        console.log('3. Data loaded');
        this.hideLoadingState();
      })
      .on('canplay', () => {
        console.log('4. Ready to play');
        this.enableControls();
      });
  }

  private showLoadingState() { /* UI update */ }
  private showMediaInfo() { /* UI update */ }
  private hideLoadingState() { /* UI update */ }
  private enableControls() { /* UI update */ }
}

Event Debouncing ​

typescript
function debounce<T extends (...args: any[]) => void>(
  func: T,
  delay: number
): T {
  let timeoutId: NodeJS.Timeout;
  return ((...args: Parameters<T>) => {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => func(...args), delay);
  }) as T;
}

// Debounce frequent time updates
const debouncedTimeUpdate = debounce((time: number) => {
  updateTimeDisplay(time);
}, 100);

player.on('timeupdate', debouncedTimeUpdate);

// Debounce resize events
const debouncedResize = debounce(() => {
  player.resize();
}, 250);

window.addEventListener('resize', debouncedResize);

Event Throttling ​

typescript
function throttle<T extends (...args: any[]) => void>(
  func: T,
  limit: number
): T {
  let inThrottle: boolean;
  return ((...args: Parameters<T>) => {
    if (!inThrottle) {
      func(...args);
      inThrottle = true;
      setTimeout(() => inThrottle = false, limit);
    }
  }) as T;
}

// Throttle progress updates
const throttledProgress = throttle((buffered: TimeRanges) => {
  updateBufferedIndicator(buffered);
}, 500);

player.on('progress', throttledProgress);

Event Delegation ​

typescript
class EventDelegator {
  private eventQueue: Array<{event: string, data: any, timestamp: number}> = [];

  constructor(private player: MediaFox) {
    this.setupDelegation();
  }

  private setupDelegation() {
    // Capture all events for logging/analytics
    const events = [
      'play', 'pause', 'ended', 'seeking', 'seeked',
      'timeupdate', 'volumechange', 'ratechange',
      'loadstart', 'loadeddata', 'error'
    ];

    events.forEach(eventName => {
      this.player.on(eventName as any, (data?: any) => {
        this.queueEvent(eventName, data);
      });
    });
  }

  private queueEvent(event: string, data: any) {
    this.eventQueue.push({
      event,
      data,
      timestamp: Date.now()
    });

    // Process events in batches
    if (this.eventQueue.length >= 10) {
      this.processEventBatch();
    }
  }

  private processEventBatch() {
    const batch = this.eventQueue.splice(0);

    // Send to analytics, logging, etc.
    this.sendToAnalytics(batch);
    this.logEvents(batch);
  }

  private sendToAnalytics(events: any[]) {
    // Analytics implementation
  }

  private logEvents(events: any[]) {
    events.forEach(({event, data, timestamp}) => {
      console.log(`[${new Date(timestamp).toISOString()}] ${event}:`, data);
    });
  }
}

Framework Integration ​

React Hook for Events ​

typescript
import { useEffect } from 'react';
import { MediaFox } from '@mediafox/core';

function usePlayerEvents(
  player: MediaFox | null,
  events: Record<string, (...args: any[]) => void>
) {
  useEffect(() => {
    if (!player) return;

    // Add all event listeners
    Object.entries(events).forEach(([event, handler]) => {
      player.on(event as any, handler);
    });

    // Cleanup function
    return () => {
      Object.entries(events).forEach(([event, handler]) => {
        player.off(event as any, handler);
      });
    };
  }, [player, events]);
}

// Usage
function VideoPlayer() {
  const [player, setPlayer] = useState<MediaFox | null>(null);

  const eventHandlers = useMemo(() => ({
    play: () => setIsPlaying(true),
    pause: () => setIsPlaying(false),
    timeupdate: (time: number) => setCurrentTime(time),
    error: (error: Error) => setError(error.message)
  }), []);

  usePlayerEvents(player, eventHandlers);

  // Component render...
}

Vue Composition API ​

typescript
import { onMounted, onUnmounted } from 'vue';
import { MediaFox } from '@mediafox/core';

export function usePlayerEvents(
  player: Ref<MediaFox | null>,
  events: Record<string, Function>
) {
  const eventHandlers = new Map();

  const setupEvents = () => {
    if (!player.value) return;

    Object.entries(events).forEach(([event, handler]) => {
      player.value!.on(event as any, handler);
      eventHandlers.set(event, handler);
    });
  };

  const cleanupEvents = () => {
    if (!player.value) return;

    eventHandlers.forEach((handler, event) => {
      player.value!.off(event, handler);
    });
    eventHandlers.clear();
  };

  watchEffect(() => {
    cleanupEvents();
    setupEvents();
  });

  onUnmounted(cleanupEvents);
}

Svelte Action ​

typescript
// playerEvents.ts
export function playerEvents(
  node: HTMLElement,
  { player, events }: { player: MediaFox, events: Record<string, Function> }
) {
  const eventHandlers = new Map();

  function setupEvents() {
    Object.entries(events).forEach(([event, handler]) => {
      player.on(event as any, handler);
      eventHandlers.set(event, handler);
    });
  }

  function cleanupEvents() {
    eventHandlers.forEach((handler, event) => {
      player.off(event, handler);
    });
    eventHandlers.clear();
  }

  setupEvents();

  return {
    update({ player: newPlayer, events: newEvents }) {
      cleanupEvents();
      Object.entries(newEvents).forEach(([event, handler]) => {
        newPlayer.on(event as any, handler);
        eventHandlers.set(event, handler);
      });
    },
    destroy: cleanupEvents
  };
}

// Usage in Svelte component
<div use:playerEvents={{ player, events: {
  play: () => isPlaying = true,
  pause: () => isPlaying = false,
  timeupdate: (time) => currentTime = time
} }}>
  <!-- Player UI -->
</div>

Error Handling Best Practices ​

Comprehensive Error Handler ​

typescript
class ErrorHandler {
  constructor(private player: MediaFox) {
    this.setupErrorHandling();
  }

  private setupErrorHandling() {
    this.player.on('error', this.handleError.bind(this));

    // Handle uncaught errors in player callbacks
    this.player.on('play', this.wrapHandler(() => {
      // Play logic that might throw
    }));
  }

  private handleError(error: Error) {
    console.error('Player error:', error);

    // Categorize errors
    if (this.isNetworkError(error)) {
      this.handleNetworkError(error);
    } else if (this.isMediaError(error)) {
      this.handleMediaError(error);
    } else {
      this.handleGenericError(error);
    }

    // Report to error tracking service
    this.reportError(error);
  }

  private isNetworkError(error: Error): boolean {
    return error.message.includes('network') ||
           error.message.includes('fetch');
  }

  private isMediaError(error: Error): boolean {
    return error instanceof MediaError;
  }

  private handleNetworkError(error: Error) {
    // Show retry UI
    this.showRetryDialog();
  }

  private handleMediaError(error: Error) {
    // Show format error message
    this.showFormatError();
  }

  private handleGenericError(error: Error) {
    // Show generic error message
    this.showGenericError(error.message);
  }

  private wrapHandler(handler: Function) {
    return (...args: any[]) => {
      try {
        return handler(...args);
      } catch (error) {
        this.handleError(error as Error);
      }
    };
  }

  private reportError(error: Error) {
    // Send to analytics/monitoring service
  }

  private showRetryDialog() { /* UI implementation */ }
  private showFormatError() { /* UI implementation */ }
  private showGenericError(message: string) { /* UI implementation */ }
}

Performance Considerations ​

Event Listener Limits ​

typescript
// Monitor event listener count
class EventMonitor {
  constructor(private player: MediaFox) {
    this.monitorListeners();
  }

  private monitorListeners() {
    const originalOn = this.player.on.bind(this.player);
    const originalOff = this.player.off.bind(this.player);

    let listenerCount = 0;

    this.player.on = (event: string, listener: Function) => {
      listenerCount++;
      if (listenerCount > 50) {
        console.warn(`High number of event listeners: ${listenerCount}`);
      }
      return originalOn(event, listener);
    };

    this.player.off = (event: string, listener?: Function) => {
      if (listener) {
        listenerCount--;
      } else {
        // Removing all listeners for this event
        const currentListeners = this.player.listenerCount(event);
        listenerCount -= currentListeners;
      }
      return originalOff(event, listener);
    };
  }
}

Best Practices ​

  1. Always Clean Up: Remove event listeners when components unmount
  2. Use Specific Events: Subscribe to specific events rather than generic ones
  3. Throttle/Debounce: Use throttling for frequent events like timeupdate
  4. Error Boundaries: Implement comprehensive error handling
  5. Memory Management: Monitor listener count and clean up properly
  6. Type Safety: Use TypeScript for better event handling safety
  7. Batch Operations: Group related operations in event handlers
  8. Avoid Side Effects: Keep event handlers focused and predictable

The event system in MediaFox provides powerful capabilities for building responsive and interactive Media Players while maintaining good performance and developer experience.

MIT License