HEX
Server: LiteSpeed
System: Linux node612.namehero.net 4.18.0-553.121.1.lve.el8.x86_64 #1 SMP Thu Apr 30 16:40:41 UTC 2026 x86_64
User: dfwparty (1186)
PHP: 8.3.31
Disabled: NONE
Upload Files
File: /home/dfwparty/santadallas.com/wp-content/plugins/ws-form-pro/public/js/external/media-capture.js
/**
 * MediaCapture
 * A library for injecting video/photo capture functionality into a DOM element
 */
(function(window) {
	'use strict';

	/**
	 * Get complete default configuration object
	 * @returns {Object} Default configuration with all settings, labels, icons, and callbacks
	 * @private
	 */
	function get_default_config() {
		return Object.assign({},
			get_default_capture_settings(),
			get_default_icons(),
			get_default_labels(),
			get_default_callbacks()
		);
	}

	/**
	 * Get default capture settings
	 * @returns {Object} Default capture settings
	 * @private
	 */
	function get_default_capture_settings() {
		return {
			mode: 'video',
			facing_mode: 'user',
			max_duration: 30,
			video_format: 'mp4',
			resolution: '',
			audio_format: 'webm',
			photo_format: 'jpeg',
			photo_quality: 0.9,
			photo_sound_enabled: false,
			photo_sound_url: '',
			countdown_enabled: false,
			countdown_duration: 3,
			countdown_sound_enabled: false,
			countdown_sound_url: '',
			auto_start: false,
			controls_auto_hide: false,
			controls_auto_hide_duration: 3000,
			max_width: '',
			settings_download_enabled: false,
			log: false,
			log_to_console: true,
			qr_scan_enabled: false,
			qr_scan_interval: 200,
			qr_scan_pause_on_detect: true,
			qr_scan_beep: false,
			qr_scan_auto: false,
			qr_scan_overlay_enabled: true
		};
	}

	/**
	 * Get default label strings
	 * @returns {Object} Default label strings
	 * @private
	 */
	function get_default_labels() {
		return {
			label_cancel_retake: 'Cancel',
			label_capture_photo: 'Take Photo',
			label_close_settings: 'Close Settings',
			label_error_camera: 'Unable to access camera. Please check permissions.',
			label_error_camera_microphone: 'Unable to access camera and microphone. Please check permissions.',
			label_error_microphone: 'Unable to access microphone. Please check permissions.',
			label_flip_camera: 'Flip Camera',
			label_mic_active: 'Microphone Active',
			label_mic_muted: 'Microphone Muted',
			label_pause: 'Pause',
			label_play: 'Play',
			label_progress: 'Progress',
			label_resume: 'Resume',
			label_retake: 'Retake Photo',
			label_settings: 'Settings',
			label_settings_audio_input: 'Audio Input',
			label_settings_back: 'Back',
			label_settings_download: 'Download',
			label_settings_video_input: 'Video Input',
			label_start_camera: 'Click to Start Camera',
			label_start_microphone: 'Click to Start Microphone',
			label_start_recording: 'Start Recording',
			label_stop: 'Stop',
			label_volume: 'Volume',
			label_volume_muted: 'Volume Muted',
			label_qr_scanning: 'Scanning for QR code'
		};
	}

	/**
	 * Get default SVG icons
	 * @returns {Object} Default SVG icon strings
	 * @private
	 */
	function get_default_icons() {
		return {
			icon_camera: '<svg viewBox="0 0 20 20"><path d="M17,4.3h-2.3l-1.8-2.2c-.2-.3-.5-.4-.8-.4h-4.3c-.3,0-.6.2-.8.4l-1.7,2.2h-2.3c-1.7,0-3,1.3-3,3v8c0,1.7,1.3,3,3,3h14c1.7,0,3-1.3,3-3V7.3c0-1.7-1.3-3-3-3ZM10,14.8c-2.8,0-5-2.2-5-5s2.2-5,5-5,5,2.2,5,5-2.2,5-5,5ZM10,6.8c-1.7,0-3,1.3-3,3s1.3,3,3,3,3-1.3,3-3-1.3-3-3-3Z"/></svg>',
			icon_checkmark: '<svg viewBox="0 0 20 20"><path d="M7.3,14.2l-4.8-4.8c-0.4-0.4-1-0.4-1.4,0s-0.4,1,0,1.4l5.5,5.5c0.4,0.4,1,0.4,1.4,0l11-11c0.4-0.4,0.4-1,0-1.4s-1-0.4-1.4,0 L7.3,14.2z"/></svg>',
			icon_chevron_right: '<svg viewBox="0 0 20 20"><path d="M7.5,2.5c-0.4-0.4-1-0.4-1.4,0s-0.4,1,0,1.4L13.4,10l-7.3,6.1c-0.4,0.4-0.4,1,0,1.4c0.2,0.2,0.5,0.3,0.7,0.3s0.5-0.1,0.7-0.3 l8-7c0.4-0.4,0.4-1,0-1.4L7.5,2.5z"/></svg>',
			icon_close: '<svg viewBox="0 0 20 20"><path d="M20,2l-2-2-8,8L2,0,0,2l8,8L0,18l2,2,8-8,8,8,2-2-8-8,8-8Z"/></svg>',
			icon_download: '<svg viewBox="0 0 20 20"><path d="M11.4 0c.2 0 .5.3.5.5v8.3h2.6s.2 0 .3.1c.3.2.3.6.1.9-1.5 1.6-2.8 3.4-4.3 4.9s-.7.5-1.2 0c-1.5-1.5-2.8-3.4-4.3-4.9-.2-.3-.1-.6.1-.9s.2-.1.3-.1h2.6V.5c0-.2.3-.4.5-.5h2.8Z"/><path d="M.6 13.8h2.5v3.8h13.8v-3.8h2.5V19c-.1.5-.5.9-1 1H1.6c-.5-.1-.9-.5-1-1v-5.2Z"/></svg>',
			icon_error: '<svg viewBox="0 0 20 20"><path d="M10,0C4.5,0,0,4.5,0,10s4.5,10,10,10,10-4.5,10-10S15.5,0,10,0ZM11,15H9v-2h2v2ZM11,11H9V5h2v6Z"/></svg>',
			icon_flip_camera: '<svg viewBox="0 0 20 20"><path d="M14.1 6.6h-1.4l-1.1-1.3c-.1-.2-.3-.2-.5-.2H8.6c-.2 0-.4.1-.5.2l-1 1.3H5.7c-1 0-1.8.8-1.8 1.8v4.7c0 1 .8 1.8 1.8 1.8H14c1 0 1.8-.8 1.8-1.8V8.4c0-1-.8-1.8-1.8-1.8ZM10 12.8c-1.7 0-2.9-1.3-2.9-2.9S8.4 7 10 7s2.9 1.3 2.9 2.9-1.3 2.9-2.9 2.9Zm0-4.7c-1 0-1.8.8-1.8 1.8s.8 1.8 1.8 1.8 1.8-.8 1.8-1.8S11 8.1 10 8.1Z"/><path d="M7.5.5c-.4.4-1.3 1.2-1.6 1.7s-.2.4.1.6c.7.2 1.4.5 2.1.8s.7.3.8.2c.5 0 0-1.2 0-1.5 3-.6 5.9.9 7.6 3.3s2 6 .4 8.5-.9 1-.3 1.4 1-.7 1.2-1c3.3-6-1.2-13.4-8-13.2s-.8 0-1.2.1c-.1 0-.3-1.2-.5-1.3s-.5.3-.6.5ZM4.5 15.9C1.9 13.5 1.1 9.3 3 6.2s.9-.9.5-1.4-1 .3-1.2.6c-3.2 5.4 0 12.3 6 13.4s2.1.2 3.1 0c.1 0 .2 1.1.5 1.2s.6-.4.7-.5c.5-.5.9-1.1 1.5-1.6.1-.2.2-.4 0-.5s-1.4-.6-1.8-.7-1-.4-1.2-.4-.2 0-.2.1c-.2.3.2 1.1.2 1.4-.7.2-1.6.2-2.4 0-1.6-.2-3.1-1-4.3-2.1Z"/></svg>',
			icon_mic: '<svg viewBox="0 0 14.7 20"><path d="M7.4,12.6c1.7,0,3.2-1.4,3.2-3.2V3.2c0-1.7-1.4-3.2-3.2-3.2s-3.2,1.4-3.2,3.2v6.3c0,1.7,1.4,3.2,3.2,3.2ZM12.6,9.5c0,2.9-2.4,5.3-5.3,5.3s-5.3-2.4-5.3-5.3H0c0,3.7,2.7,6.8,6.3,7.3v3.2h2.1v-3.2c3.6-.5,6.3-3.6,6.3-7.3h-2.1Z"/></svg>',
			icon_mic_muted: '<svg viewBox="0 0 14.7 20"><path d="M14.7,9.5h-1.8c0,.8-.2,1.5-.5,2.2l1.3,1.3c.6-1,.9-2.2,.9-3.5ZM10.5,9.7c0,0,0-.1,0-.2V3.2C10.5,1.4,9.1,0,7.4,0s-3.2,1.4-3.2,3.2v.2l6.3,6.3ZM-.8,1.1l-1.3,1.3,6.3,6.3v.8c0,1.7,1.4,3.2,3.1,3.2s.5,0,.7,0l1.7,1.7c-.7.3-1.6.5-2.4.5-2.9,0-5.6-2.2-5.6-5.4H0C0,13.1,2.9,16,6.3,16.5v3.5h2.1v-3.5c1-.1,1.9-.5,2.7-.9l4.4,4.4,1.3-1.3L-.8,1.1Z"/></svg>',
			icon_pause: '<svg viewBox="0 0 20 20"><path d="M2.5,0h5v20H2.5V0ZM12.5,0h5v20h-5V0Z"/></svg>',
			icon_play: '<svg viewBox="0 0 20 20"><path d="M4.1,0v20l15.7-10L2.1,0Z"/></svg>',
			icon_record: '<svg viewBox="0 0 20 20"><circle cx="10" cy="10" r="10"/></svg>',
			icon_retake: '<svg viewBox="0 0 20 20"><path d="M20,10.4h-2.1c-.2,6.3-7.9,9.9-12.8,5.9l1.7-2.2h.1c3.2,2.5,7.9.3,8.2-3.7h-2.1l3.5-4.4,3.5,4.4Z"/><path d="M4.9,9.6h2.1l-3.5,4.5L0,9.6h2.1c0-2.7,1.7-5.3,4.1-6.6s5.5-1.3,8,.2.6.3.7.5,0,0,0,0l-1.8,2.2s-.6-.3-.7-.4c-3.3-1.8-7.2.4-7.6,4.1Z"/></svg>',
			icon_source_audio: '<svg viewBox="0 0 20 20"><path d="M10,12.6c1.7,0,3.2-1.4,3.2-3.2V3.2c0-1.7-1.4-3.2-3.2-3.2s-3.2,1.4-3.2,3.2v6.3c0,1.7,1.4,3.2,3.2,3.2ZM16.2,9.5c-.5,0-.9.4-1,.9-.4,2.5-2.6,4.4-5.2,4.4s-4.8-1.9-5.2-4.4c0-.5-.5-.9-1-.9s-1.1.6-1.1,1.2c.5,3.2,3,5.6,6.2,6.1v2.2c0,.6.5,1.1,1.1,1.1s1.1-.5,1.1-1.1v-2.2c3.2-.5,5.7-2.9,6.2-6.1.1-.6-.4-1.2-1.1-1.2Z"/></svg>',
			icon_source_video: '<svg viewBox="0 0 20 20"><path d="M15.6,8.3v-3.9c0-.6-.5-1.1-1.1-1.1H1.1c-.6,0-1.1.5-1.1,1.1v11.1c0,.6.5,1.1,1.1,1.1h13.3c.6,0,1.1-.5,1.1-1.1v-3.9l4.4,4.4V3.9l-4.4,4.4Z"/></svg>',
			icon_settings: '<svg viewBox="0 0 20 20"><path d="M17.43,10.98c0.04-0.32,0.07-0.64,0.07-0.98s-0.03-0.66-0.07-0.98l2.11-1.65c0.19-0.15,0.24-0.42,0.12-0.64l-2-3.46 c-0.12-0.22-0.39-0.3-0.61-0.22l-2.49,1c-0.52-0.4-1.08-0.73-1.69-0.98l-0.38-2.65C12.46,0.18,12.25,0,12,0h-4 c-0.25,0-0.46,0.18-0.49,0.42L7.13,3.07c-0.61,0.25-1.17,0.59-1.69,0.98l-2.49-1c-0.23-0.09-0.49,0-0.61,0.22l-2,3.46 c-0.13,0.22-0.07,0.49,0.12,0.64l2.11,1.65c-0.04,0.32-0.07,0.65-0.07,0.98s0.03,0.66,0.07,0.98l-2.11,1.65 c-0.19,0.15-0.24,0.42-0.12,0.64l2,3.46c0.12,0.22,0.39,0.3,0.61,0.22l2.49-1c0.52,0.4,1.08,0.73,1.69,0.98l0.38,2.65 C7.54,19.82,7.75,20,8,20h4c0.25,0,0.46-0.18,0.49-0.42l0.38-2.65c0.61-0.25,1.17-0.59,1.69-0.98l2.49,1 c0.23,0.09,0.49,0,0.61-0.22l2-3.46c0.12-0.22,0.07-0.49-0.12-0.64L17.43,10.98z M10,13c-1.65,0-3-1.35-3-3s1.35-3,3-3s3,1.35,3,3 S11.65,13,10,13z"/></svg>',
			icon_sound: '<svg viewBox="0 0 20 20"><rect x="3.0" y="1.0" width="2.5" height="4" /><rect x="7.5" y="1.0" width="2.5" height="11" /><rect x="12.0" y="1.0" width="2.5" height="10" /></svg>',
			icon_stop: '<svg viewBox="0 0 20 20"><rect x="2" y="2" width="16" height="16"/></svg>',
			icon_volume: '<svg viewBox="0 0 20 20"><path d="M0,6.7v6.6h4.4l5.5,5.5V1.1l-5.5,5.5H0ZM14.9,10c0-2-1.1-3.6-2.8-4.5v8.9c1.6-.8,2.8-2.5,2.8-4.4ZM12.2.3v2.3c3.2,1,5.5,3.9,5.5,7.4s-2.3,6.5-5.5,7.4v2.3c4.4-1,7.7-5,7.7-9.7S16.6,1.3,12.2.3h0Z"/></svg>',
			icon_volume_low: '<svg viewBox="0 0 20 20"><path d="M0,6.7v6.7h4.4l5.6,5.6V1.1l-5.6,5.6H0ZM15,10c0-2-1.1-3.7-2.8-4.5v8.9c1.6-.8,2.8-2.5,2.8-4.5Z"/></svg>',
			icon_volume_muted: '<svg viewBox="0 0 20 20"><path d="M15,10c0-2-1.1-3.7-2.8-4.5v2.5l2.7,2.7c0-.2,0-.5,0-.7ZM17.8,10c0,1-.2,2-.6,2.9l1.7,1.7c.7-1.4,1.1-2.9,1.1-4.6,0-4.8-3.3-8.7-7.8-9.7v2.3c3.2,1,5.6,3.9,5.6,7.5ZM1.4,0L0,1.4l5.3,5.3H0v6.7h4.4l5.6,5.6v-7.5l4.7,4.7c-.7.6-1.6,1-2.5,1.3v2.3c1.5-.3,2.9-1.1,4.1-2l2.3,2.3,1.4-1.4-10-10L1.4,0ZM10,1.1l-2.3,2.3,2.3,2.3V1.1Z"/></svg>',
		};
	}

	/**
	 * Get default callback functions (all null)
	 * @returns {Object} Default callback configuration
	 * @private
	 */
	function get_default_callbacks() {
		return {
			on_photo_captured: null,
			on_photo_retake: null,
			on_photo_cancel: null,
			on_video_captured: null,
			on_video_started: null,
			on_video_stopped: null,
			on_video_paused: null,
			on_video_resumed: null,
			on_video_retake: null,
			on_video_cancel: null,
			on_audio_captured: null,
			on_audio_started: null,
			on_audio_stopped: null,
			on_audio_paused: null,
			on_audio_resumed: null,
			on_audio_retake: null,
			on_audio_cancel: null,
			on_error: null,
			on_log: null,
			on_stream_started: null,
			on_stream_stopped: null,
			on_stream_flipped: null,
			on_settings_opened: null,
			on_settings_closed: null,
			on_result_show: null,
			on_result_hide: null,
			on_cameras_detected: null,
			on_ui_flip_on: null,
			on_ui_flip_off: null,
			on_ui_capture_on: null,
			on_ui_capture_off: null,
			on_ui_retake_on: null,
			on_ui_retake_off: null,
			on_ui_cancel_on: null,
			on_ui_cancel_off: null,
			on_ui_settings_on: null,
			on_ui_settings_off: null,
			on_ui_download_on: null,
			on_ui_download_off: null,
			on_ui_playback_play_on: null,
			on_ui_playback_play_off: null,
			on_ui_playback_pause_on: null,
			on_ui_playback_pause_off: null,
			on_ui_playback_toggle_on: null,
			on_ui_playback_toggle_off: null,
			on_ui_playback_stop_on: null,
			on_ui_playback_stop_off: null,
			on_qr_detected: null,
			on_qr_scan_start: null,
			on_qr_scan_stop: null
		};
	}

	/**
	 * MediaCapture Constructor
	 * @param {HTMLElement} element - The DOM element to inject the capture interface into
	 * @param {Object} options - Configuration options
	 */
	function MediaCapture(element, options) {
		// Validate element
		if (!element || typeof element.appendChild !== 'function') {
			throw new Error('MediaCapture requires a valid DOM element');
		}
		
		// Store references
		this.element = element;
		
		// Default configuration
		this.config = get_default_config();

		// Merge user options with defaults
		this.config = Object.assign({}, this.config, options || {});

		// Sanitize and validate all config values - revert invalid values to defaults
		this.sanitize_config();

		// Internal state
		this.stream = null;
		this.video_element = null;
		this.audio_element = null;
		this.canvas_element = null;
		this.media_recorder = null;
		this.recorded_chunks = [];
		this.timer_interval = null;
		this.is_recording = false;
		this.is_initialized = false;
		this.blob = null;
		this.filename = null;
		this.selected_video_device = null;
		this.selected_audio_device = null;
		this.is_mobile = false;
		this.has_multiple_cameras = false;
		this.has_checked_cameras = false;
		this.has_multiple_microphones = false;
		this.has_checked_microphones = false;
		this.video_width = null; // parsed from resolution
		this.video_height = null; // parsed from resolution
		this.qr_scan_interval_id = null;
		this.qr_scan_active = false;
		this.qr_barcode_detector = null;

		// Bind methods to instance
		this.init = this.init.bind(this);
		this.destroy = this.destroy.bind(this);
		this.stream_start = this.stream_start.bind(this);
		this.stream_stop = this.stream_stop.bind(this);
		this.stream_flip = this.stream_flip.bind(this);
		this.photo_capture = this.photo_capture.bind(this);
		this.photo_retake = this.photo_retake.bind(this);
		this.photo_cancel = this.photo_cancel.bind(this);
		this.video_start = this.video_start.bind(this);
		this.video_stop = this.video_stop.bind(this);
		this.video_pause = this.video_pause.bind(this);
		this.video_resume = this.video_resume.bind(this);
		this.video_retake = this.video_retake.bind(this);
		this.video_cancel = this.video_cancel.bind(this);
		this.audio_start = this.audio_start.bind(this);
		this.audio_stop = this.audio_stop.bind(this);
		this.audio_pause = this.audio_pause.bind(this);
		this.audio_resume = this.audio_resume.bind(this);
		this.audio_retake = this.audio_retake.bind(this);
		this.audio_cancel = this.audio_cancel.bind(this);
		this.settings_open = this.settings_open.bind(this);
		this.settings_close = this.settings_close.bind(this);
		this.show_controls = this.show_controls.bind(this);
		this.hide_controls = this.hide_controls.bind(this);
		this.get_stream = this.get_stream.bind(this);
		this.is_stream_active = this.is_stream_active.bind(this);
		this.set_config = this.set_config.bind(this);
		this.get_config = this.get_config.bind(this);

		// Auto-initialize
		this.init();
	}

	/**
	 * Initialize the MediaCapture component
	 * Creates and injects the UI into the target element
	 * Note: This is called automatically in the constructor. 
	 * You only need to call this manually if you need to reinitialize after destroy().
	 */
	MediaCapture.prototype.init = function() {
		if (this.is_initialized) {
			this.log('Init already called - skipping re-initialization');
			return;
		}

		this.log('Initializing MediaCapture with mode: ' + this.config.mode);

		// Parse resolution if provided
		if (this.config.resolution) {
			var resolution_parts = this.config.resolution.split(',');
			this.video_width = parseInt(resolution_parts[0], 10) || null;
			this.video_height = parseInt(resolution_parts[1], 10) || null;
			this.log('Resolution parsed: ' + this.video_width + 'x' + this.video_height);
		}

		// Build and inject the interface
		this.log('Building interface');
		this.build_interface();

		// Store references to key elements
		this.video_element = this.element.querySelector('.mc-video');
		this.audio_element = this.element.querySelector('.mc-audio');
		this.canvas_element = this.element.querySelector('.mc-canvas');

		// Mark as initialized BEFORE init_functionality (which may call stream_start via auto_start)
		this.is_initialized = true;

		// Initialize functionality and event handlers
		this.log('Initializing functionality and event handlers');
		this.init_functionality();

		// Note: Auto-start is handled in init_functionality
		this.log('MediaCapture initialization complete');
	};

	/**
	 * Centralized logging method
	 * @param {string} message - The message to log
	 * @param {*} args - Additional arguments to log
	 * @private
	 */
	MediaCapture.prototype.log = function(message) {
		if (!this.config.log) {
			return;
		}

		var level = 'log';
		var args = Array.prototype.slice.call(arguments, 1);
		
		// Check if last argument is a log level
		if (args.length > 0) {
			var last_arg = args[args.length - 1];
			if (last_arg === 'warn' || last_arg === 'error' || last_arg === 'log') {
				level = last_arg;
				args = args.slice(0, -1);
			}
		}
		
		// Call on_log callback if provided
		if (typeof this.config.on_log === 'function') {
			this.config.on_log(message, args, level);
		}
		
		// Log to console if enabled
		if (this.config.log_to_console && console && console[level]) {
			var log_args = ['[MediaCapture]', message].concat(args);
			console[level].apply(console, log_args);
		}
	};

	/**
	 * Sanitize and validate config values
	 * Reverts any invalid, null, undefined, or empty string values back to defaults
	 * @private
	 */
	MediaCapture.prototype.sanitize_config = function() {
		// Get default config values from helper function
		var defaults = get_default_config();

		// Helper function to check if value is invalid
		var is_invalid = function(value) {
			return value === null || 
				   value === undefined || 
				   (typeof value === 'string' && value.trim() === '');
		};

		// Sanitize all config values
		for (var key in defaults) {
			if (defaults.hasOwnProperty(key)) {
				var default_value = defaults[key];
				var current_value = this.config[key];
				
				// Skip function types (like log_handler) - they're handled separately
				if (typeof default_value === 'function' || typeof default_value === 'object' && default_value === null) {
					continue;
				}
				
				// For strings, check if empty/null/undefined
				if (typeof default_value === 'string') {
					if (is_invalid(current_value)) {
						this.config[key] = default_value;
					}
				}
				// For numbers, check if invalid or not a number
				else if (typeof default_value === 'number') {
					if (is_invalid(current_value) || isNaN(current_value)) {
						this.config[key] = default_value;
					}
				}
				// For booleans, ensure it's actually a boolean
				else if (typeof default_value === 'boolean') {
					if (typeof current_value !== 'boolean') {
						this.config[key] = default_value;
					}
				}
			}
		}

		// Validate specific enum values
		var valid_modes = ['photo', 'video', 'audio'];
		if (valid_modes.indexOf(this.config.mode) === -1) {
			this.config.mode = defaults.mode;
		}

		var valid_facing_modes = ['user', 'environment'];
		if (valid_facing_modes.indexOf(this.config.facing_mode) === -1) {
			this.config.facing_mode = defaults.facing_mode;
		}

		var valid_photo_formats = ['jpeg', 'png', 'webp'];
		if (valid_photo_formats.indexOf(this.config.photo_format) === -1) {
			this.config.photo_format = defaults.photo_format;
		}

		var valid_video_formats = ['webm', 'mp4'];
		if (valid_video_formats.indexOf(this.config.video_format) === -1) {
			this.config.video_format = defaults.video_format;
		}

		var valid_audio_formats = ['webm', 'mp4', 'ogg', 'wav'];
		if (valid_audio_formats.indexOf(this.config.audio_format) === -1) {
			this.config.audio_format = defaults.audio_format;
		}

		// Validate numeric ranges
		this.config.photo_quality = Math.max(0.1, Math.min(1.0, this.config.photo_quality));
		this.config.countdown_duration = Math.max(1, Math.min(10, this.config.countdown_duration));
		this.config.max_duration = Math.max(1, this.config.max_duration);
		this.config.controls_auto_hide_duration = Math.max(0, this.config.controls_auto_hide_duration);
	};

	/**
	 * Build the media capture interface HTML
	 * @private
	 */
	MediaCapture.prototype.build_interface = function() {
		var html = '';
		var mode = this.config.mode;
		var auto_start = this.config.auto_start;
		var countdown_enabled = this.config.countdown_enabled;
		var controls_auto_hide = this.config.controls_auto_hide;
		var controls_auto_hide_duration = this.config.controls_auto_hide_duration;

		// Wrapper div to contain all library HTML
		html += '<div class="mc-wrapper mc-mode-' + mode + '" role="region" aria-label="Media capture interface">';
		
		// Add aria-live region for screen reader announcements
		html += '<div class="mc-sr-announcer" role="status" aria-live="polite" aria-atomic="true" style="position: absolute; left: -10000px; width: 1px; height: 1px; overflow: hidden;"></div>';

		// Initial start screen (shown when auto_start is false)
		if (!auto_start) {
			var start_screen_class = 'mc-start';
			html += '<div class="' + start_screen_class + '">';
			html += '<div class="mc-start-inner">';
			html += '<div class="mc-start-icon">';
			if (mode === 'audio') {
				html += this.config.icon_mic;
			} else {
				html += this.config.icon_camera;
			}
			html += '</div>';
			if (mode === 'audio') {
				html += '<p class="mc-start-text">' + this.esc_html(this.config.label_start_microphone) + '</p>';
			} else {
				html += '<p class="mc-start-text">' + this.esc_html(this.config.label_start_camera) + '</p>';
			}
			html += '</div>';
			html += '</div>';
		}

		// Error screen (hidden by default)
		var error_screen_class = 'mc-error';
		html += '<div class="' + error_screen_class + '" role="alert" aria-live="assertive" style="display: none;">';
		html += '<div class="mc-error-inner">';
		html += '<div class="mc-error-icon">';
		html += this.config.icon_error;
		html += '</div>';
		if (mode === 'video') {
			html += '<p class="mc-error-text">' + this.esc_html(this.config.label_error_camera_microphone) + '</p>';
		} else if (mode === 'audio') {
			html += '<p class="mc-error-text">' + this.esc_html(this.config.label_error_microphone) + '</p>';
		} else {
			html += '<p class="mc-error-text">' + this.esc_html(this.config.label_error_camera) + '</p>';
		}
		html += '</div>';
		html += '</div>';

		// Preview container with overlay controls
		var preview_class = 'mc-capture';
		html += '<div class="' + preview_class + '"' + (auto_start ? '' : ' style="display: none;"') + '>';

		// Video element for photo/video modes, audio element for audio mode
		if (mode === 'audio') {
			html += '<audio class="mc-audio" style="display: none;" aria-label="Audio recording"></audio>';
		} else {
			// Single video element that serves both live preview and playback
			html += '<video class="mc-video" autoplay playsinline muted aria-label="Camera preview"></video>';
		}

		// Canvas for photo capture (photo mode only)
		if (mode === 'photo') {
			html += '<canvas class="mc-canvas"></canvas>';
		}

		// QR scan overlay (photo mode only, shown when qr_scan_enabled and qr_scan_overlay_enabled)
		if (mode === 'photo' && this.config.qr_scan_enabled && this.config.qr_scan_overlay_enabled) {
			html += '<div class="mc-qr-overlay" aria-hidden="true">';
			html += '<div class="mc-qr-finder">';
			html += '<span class="mc-qr-corner mc-qr-corner-tl"></span>';
			html += '<span class="mc-qr-corner mc-qr-corner-tr"></span>';
			html += '<span class="mc-qr-corner mc-qr-corner-bl"></span>';
			html += '<span class="mc-qr-corner mc-qr-corner-br"></span>';
			html += '</div>';
			html += '</div>';
		}
		if (mode === 'photo' && this.config.qr_scan_enabled) {
			html += '<div class="mc-qr-sr" role="status" aria-live="polite" aria-atomic="true" style="position: absolute; left: -10000px; width: 1px; height: 1px; overflow: hidden;"></div>';
		}

		// Countdown overlay
		if (countdown_enabled) {
			html += '<div class="mc-countdown" style="display: none;">';
			html += '<span class="mc-countdown-number"></span>';
			html += '<span class="mc-countdown-sr" role="status" aria-live="assertive" aria-atomic="true"></span>';
			html += '</div>';
		}

		// Overlay controls bar at bottom
		html += '<div class="mc-capture-controls">';

		// Left column - Record button, Mic level, Timer
		html += '<div class="mc-capture-controls-left">';
		if (mode === 'photo') {
			html += '<button type="button" class="mc-btn mc-capture-btn" aria-label="' + this.esc_attr(this.config.label_capture_photo) + '" title="' + this.esc_attr(this.config.label_capture_photo) + '">';
			html += this.config.icon_camera;
			html += '</button>';
		} else if (mode === 'video') {
			html += '<button type="button" class="mc-btn mc-record-btn" aria-label="' + this.esc_attr(this.config.label_start_recording) + '" title="' + this.esc_attr(this.config.label_start_recording) + '">';
			html += this.config.icon_record;
			html += '</button>';

			// Pause button (video mode only, hidden until recording starts)
			html += '<button type="button" class="mc-btn mc-pause-btn" style="display: none;" aria-label="' + this.esc_attr(this.config.label_pause) + '" title="' + this.esc_attr(this.config.label_pause) + '">';
			html += this.config.icon_pause;
			html += '</button>';

			// Microphone level indicator (video mode only)
			html += '<div class="mc-mic-indicator" tabindex="0" role="button" aria-label="' + this.esc_attr(this.config.label_mic_active) + '" title="' + this.esc_attr(this.config.label_mic_active) + '">';
			html += '<div class="mc-mic-icon">';
			html += this.config.icon_mic;
			html += '</div>';
			html += '<div class="mc-mic-bar">';
			html += '<div class="mc-mic-bar-track">';
			html += '<div class="mc-mic-bar-fill"></div>';
			html += '</div>';
			html += '</div>';
			html += '</div>';

			// Recording timer
			var max_duration = this.config.max_duration;
			var has_max_duration = max_duration && max_duration < Infinity;

			html += '<div class="mc-timer" role="timer" aria-live="off" style="display: none;">';
			html += '<span class="mc-timer-elapsed">00:00</span>';

			if (has_max_duration) {
				// Calculate initial remaining time display
				var initial_mins = Math.floor(max_duration / 60);
				var initial_secs = max_duration % 60;
				var initial_remaining = this.pad_zero(initial_mins) + ':' + this.pad_zero(initial_secs);

				html += '<span class="mc-timer-separator"> / </span>';
				html += '<span class="mc-timer-remaining">' + initial_remaining + '</span>';
			}

			html += '</div>';
		} else if (mode === 'audio') {
			html += '<button type="button" class="mc-btn mc-record-btn" aria-label="' + this.esc_attr(this.config.label_start_recording) + '" title="' + this.esc_attr(this.config.label_start_recording) + '">';
			html += this.config.icon_record;
			html += '</button>';

			// Pause button (audio mode, hidden until recording starts)
			html += '<button type="button" class="mc-btn mc-pause-btn" style="display: none;" aria-label="' + this.esc_attr(this.config.label_pause) + '" title="' + this.esc_attr(this.config.label_pause) + '">';
			html += this.config.icon_pause;
			html += '</button>';

			// Microphone level indicator (audio mode)
			html += '<div class="mc-mic-indicator" tabindex="0" role="button" aria-label="' + this.esc_attr(this.config.label_mic_active) + '" title="' + this.esc_attr(this.config.label_mic_active) + '">';
			html += '<div class="mc-mic-icon">';
			html += this.config.icon_mic;
			html += '</div>';
			html += '<div class="mc-mic-bar">';
			html += '<div class="mc-mic-bar-track">';
			html += '<div class="mc-mic-bar-fill"></div>';
			html += '</div>';
			html += '</div>';
			html += '</div>';

			// Recording timer
			var max_duration = this.config.max_duration;
			var has_max_duration = max_duration && max_duration < Infinity;

			html += '<div class="mc-timer" role="timer" aria-live="off" style="display: none;">';
			html += '<span class="mc-timer-elapsed">00:00</span>';

			if (has_max_duration) {
				// Calculate initial remaining time display
				var audio_initial_mins = Math.floor(max_duration / 60);
				var audio_initial_secs = max_duration % 60;
				var audio_initial_remaining = this.pad_zero(audio_initial_mins) + ':' + this.pad_zero(audio_initial_secs);

				html += '<span class="mc-timer-separator"> / </span>';
				html += '<span class="mc-timer-remaining">' + audio_initial_remaining + '</span>';
			}

			html += '</div>';
		}
		html += '</div>';

		// Right column - Settings
		html += '<div class="mc-capture-controls-right">';

		// Camera flip button (hidden by default, shown on mobile with multiple cameras, not shown for audio mode)
		if (mode !== 'audio') {
			html += '<button type="button" class="mc-btn mc-flip-btn" style="display: none;" aria-label="' + this.esc_attr(this.config.label_flip_camera) + '" title="' + this.esc_attr(this.config.label_flip_camera) + '">';
			html += this.config.icon_flip_camera;
			html += '</button>';
		}

		// Cancel retake button (hidden until retake is clicked)
		html += '<button type="button" class="mc-btn mc-cancel-btn" style="display: none;" aria-label="' + this.esc_attr(this.config.label_cancel_retake) + '" title="' + this.esc_attr(this.config.label_cancel_retake) + '">';
		html += this.config.icon_close;
		html += '</button>';

		html += '<button type="button" class="mc-btn mc-settings-btn" aria-label="' + this.esc_attr(this.config.label_settings) + '" title="' + this.esc_attr(this.config.label_settings) + '" aria-expanded="false">';
		html += this.config.icon_settings;
		html += '</button>';
		html += '</div>';

		html += '</div>'; // End overlay controls

		// Playback controls (hidden by default, shown when playing back recorded video/audio)
		if (mode === 'video' || mode === 'audio') {
			html += '<div class="mc-result-controls" style="display: none;">';

			// Left - Play button, Volume controls, Time display
			html += '<div class="mc-result-controls-left">';
			html += '<button type="button" class="mc-btn mc-play-btn" aria-label="' + this.esc_attr(this.config.label_play) + '" title="' + this.esc_attr(this.config.label_play) + '">';
			html += this.config.icon_play;
			html += '</button>';

			// Volume level indicator
			html += '<div class="mc-volume-indicator" tabindex="0" role="slider" aria-label="' + this.esc_attr(this.config.label_volume) + '" aria-valuemin="0" aria-valuemax="100" aria-valuenow="100" title="' + this.esc_attr(this.config.label_volume) + '">';
			html += '<div class="mc-volume-icon">';
			html += this.config.icon_volume;
			html += '</div>';
			html += '<div class="mc-volume-bar">';
			html += '<div class="mc-volume-fill"></div>';
			html += '<input type="range" class="mc-volume-slider" min="0" max="100" value="100" tabindex="-1">';
			html += '</div>';
			html += '</div>';

			html += '<div class="mc-time" role="status" aria-live="polite"><span class="mc-time-current">0:00</span> / <span class="mc-time-duration">0:00</span></div>';
			html += '</div>';

			// Center - Progress bar
			html += '<div class="mc-result-controls-center">';
			var progress_label = this.config.label_progress;
			html += '<div class="mc-progress" tabindex="0" role="slider" aria-label="' + this.esc_attr(progress_label) + '" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" title="' + this.esc_attr(progress_label) + '"><div class="mc-progress-track"><div class="mc-progress-fill"></div></div></div>';
			html += '</div>';

			// Right - Retake and Settings
			html += '<div class="mc-result-controls-right">';
			var retake_label = this.config.label_retake;
			html += '<button type="button" class="mc-btn mc-retake-btn" aria-label="' + this.esc_attr(retake_label) + '" title="' + this.esc_attr(retake_label) + '">';
			html += this.config.icon_retake;
			html += '</button>';
			html += '<button type="button" class="mc-btn mc-settings-btn" aria-label="' + this.esc_attr(this.config.label_settings) + '" title="' + this.esc_attr(this.config.label_settings) + '" aria-expanded="false">';
			html += this.config.icon_settings;
			html += '</button>';
			html += '</div>';

			html += '</div>'; // End playback controls
		}

		// Result display container (hidden by default, shown when media is captured)
		if (mode === 'photo') {
			// For photos, show the captured image
			html += '<div class="mc-result" style="display: none;">';
			html += '<img class="mc-photo" src="" alt="Captured Photo">';

			// Flash overlay for photo capture
			html += '<div class="mc-flash"></div>';

			// Photo controls bar
			html += '<div class="mc-result-controls">';

			// Left column - Retake button (matches position of capture button)
			html += '<div class="mc-result-controls-left">';
			html += '<button type="button" class="mc-btn mc-retake-btn" aria-label="' + this.esc_attr(this.config.label_retake) + '" title="' + this.esc_attr(this.config.label_retake) + '">';
			html += this.config.icon_retake;
			html += '</button>';
			html += '</div>';

			// Center column - Empty
			html += '<div class="mc-result-controls-center">';
			html += '</div>';

			// Right column - Settings only
			html += '<div class="mc-result-controls-right">';
			html += '<button type="button" class="mc-btn mc-settings-btn" aria-label="' + this.esc_attr(this.config.label_settings) + '" title="' + this.esc_attr(this.config.label_settings) + '" aria-expanded="false">';
			html += this.config.icon_settings;
			html += '</button>';
			html += '</div>';

			html += '</div>'; // End photo controls bar
			html += '</div>'; // End photo player
		}

		html += '</div>'; // End preview container

		// Settings panel (at wrapper level so it can overlay both preview and result)
		var unique_id = 'mc-' + Date.now();
		html += '<div class="mc-settings" role="menu" style="display: none;">';
		
		// Main menu
		html += '<div class="mc-settings-menu mc-settings-menu-main" data-menu="main">';
		
		// Video source menu item (only for photo and video modes)
		if (mode === 'photo' || mode === 'video') {
			html += '<button type="button" class="mc-settings-menu-item" role="menuitem" data-action="video-input">';
			html += '<span class="mc-settings-menu-icon">' + this.config.icon_source_video + '</span>';
			html += '<span class="mc-settings-menu-label">' + this.esc_html(this.config.label_settings_video_input) + '</span>';
			html += '<span class="mc-settings-menu-arrow">' + this.config.icon_chevron_right + '</span>';
			html += '</button>';
		}
		
		// Audio source menu item (for video and audio modes)
		if (mode === 'video' || mode === 'audio') {
			html += '<button type="button" class="mc-settings-menu-item" role="menuitem" data-action="audio-input">';
			html += '<span class="mc-settings-menu-icon">' + this.config.icon_source_audio + '</span>';
			html += '<span class="mc-settings-menu-label">' + this.esc_html(this.config.label_settings_audio_input) + '</span>';
			html += '<span class="mc-settings-menu-arrow">' + this.config.icon_chevron_right + '</span>';
			html += '</button>';
		}
		
		// Download menu item (if enabled, disabled until media is captured)
		if (this.config.settings_download_enabled) {
			html += '<button type="button" class="mc-settings-menu-item mc-download-item" role="menuitem" data-action="download" disabled>';
			html += '<span class="mc-settings-menu-icon">' + this.config.icon_download + '</span>';
			html += '<span class="mc-settings-menu-label">' + this.esc_html(this.config.label_settings_download) + '</span>';
			html += '</button>';
		}
		
		html += '</div>'; // End main menu
		
		// Video input submenu (only for photo and video modes)
		if (mode === 'photo' || mode === 'video') {
			html += '<div class="mc-settings-menu mc-settings-menu-sub" data-menu="video-input" style="display: none;">';
			html += '<button type="button" class="mc-settings-menu-item mc-settings-back-btn" role="menuitem" data-action="back">';
			html += '<span class="mc-settings-menu-label">' + this.esc_html(this.config.label_settings_back) + '</span>';
			html += '</button>';
			html += '<div class="mc-settings-menu-devices mc-video-devices">';
			html += '<!-- Video devices will be populated dynamically -->';
			html += '</div>';
			html += '</div>'; // End video input submenu
		}
		
		// Audio input submenu (for video and audio modes)
		if (mode === 'video' || mode === 'audio') {
			html += '<div class="mc-settings-menu mc-settings-menu-sub" data-menu="audio-input" style="display: none;">';
			html += '<button type="button" class="mc-settings-menu-item mc-settings-back-btn" role="menuitem" data-action="back">';
			html += '<span class="mc-settings-menu-label">' + this.esc_html(this.config.label_settings_back) + '</span>';
			html += '</button>';
			html += '<div class="mc-settings-menu-devices mc-audio-devices">';
			html += '<!-- Audio devices will be populated dynamically -->';
			html += '</div>';
			html += '</div>'; // End audio input submenu
		}
		
		html += '</div>'; // End settings panel

		html += '</div>'; // End mc-wrapper

		// Inject HTML into the element
		this.element.innerHTML = html;
	};

	/**
	 * Helper: Escape HTML entities
	 * @private
	 */
	MediaCapture.prototype.esc_html = function(text) {
		var div = document.createElement('div');
		div.textContent = text;
		return div.innerHTML;
	};

	/**
	 * Helper: Escape HTML attributes
	 * @private
	 */
	MediaCapture.prototype.esc_attr = function(text) {
		return text.replace(/"/g, '&quot;').replace(/'/g, '&#39;');
	};

	/**
	 * Helper: Pad numbers with zero
	 * @private
	 */
	MediaCapture.prototype.pad_zero = function(num) {
		return num < 10 ? '0' + num : num;
	};

	/**
	 * Helper: Format time in M:SS or MM:SS format
	 * @private
	 */
	MediaCapture.prototype.format_time = function(seconds) {
		if (isNaN(seconds)) {
			return '0:00';
		}
		var mins = Math.floor(seconds / 60);
		var secs = Math.floor(seconds % 60);
		return mins + ':' + this.pad_zero(secs);
	};

	/**
	 * Helper: Announce message to screen readers
	 * @param {string} message - Message to announce
	 * @private
	 */
	MediaCapture.prototype.announce_to_screen_reader = function(message) {
		var announcer = this.element.querySelector('.mc-sr-announcer');
		if (announcer) {
			announcer.textContent = message;
		}
	};

	/**
	 * Get supported audio MIME type based on format preference
	 * @param {string} format - Preferred format ('webm', 'mp4', 'ogg', 'wav')
	 * @returns {string} Supported MIME type
	 * @private
	 */
	MediaCapture.prototype.get_supported_audio_mime_type = function(format) {
		// Define format options with their MIME types and codecs
		var format_options = {
			'webm': [
				'audio/webm;codecs=opus',
				'audio/webm'
			],
			'mp4': [
				'audio/mp4',
				'audio/mp4;codecs=mp4a.40.2'
			],
			'ogg': [
				'audio/ogg;codecs=opus',
				'audio/ogg'
			],
			'wav': [
				'audio/wav'
			]
		};

		// Try preferred format first
		if (format && format_options[format]) {
			for (var i = 0; i < format_options[format].length; i++) {
				if (MediaRecorder.isTypeSupported(format_options[format][i])) {
					return format_options[format][i];
				}
			}
		}

		// Fallback order: webm -> mp4 -> ogg -> wav
		var fallback_order = ['webm', 'mp4', 'ogg', 'wav'];
		for (var j = 0; j < fallback_order.length; j++) {
			var fallback_format = fallback_order[j];
			if (fallback_format !== format && format_options[fallback_format]) {
				for (var k = 0; k < format_options[fallback_format].length; k++) {
					if (MediaRecorder.isTypeSupported(format_options[fallback_format][k])) {
						return format_options[fallback_format][k];
					}
				}
			}
		}

		// Final fallback - try basic audio/webm
		return 'audio/webm';
	};

	/**
	 * Get supported video MIME type based on format preference
	 * @param {string} format - Preferred format ('webm', 'mp4')
	 * @returns {string} Supported MIME type
	 * @private
	 */
	MediaCapture.prototype.get_supported_video_mime_type = function(format) {
		// Define format options with their MIME types and codecs
		var format_options = {
			'webm': [
				'video/webm;codecs=vp9,opus',
				'video/webm;codecs=vp8,opus',
				'video/webm'
			],
			'mp4': [
				'video/mp4;codecs=h264,aac',
				'video/mp4'
			]
		};

		// Try preferred format first
		if (format && format_options[format]) {
			for (var i = 0; i < format_options[format].length; i++) {
				if (MediaRecorder.isTypeSupported(format_options[format][i])) {
					return format_options[format][i];
				}
			}
		}

		// Fallback order: webm -> mp4
		var fallback_order = ['webm', 'mp4'];
		for (var j = 0; j < fallback_order.length; j++) {
			var fallback_format = fallback_order[j];
			if (fallback_format !== format && format_options[fallback_format]) {
				for (var k = 0; k < format_options[fallback_format].length; k++) {
					if (MediaRecorder.isTypeSupported(format_options[fallback_format][k])) {
						return format_options[fallback_format][k];
					}
				}
			}
		}

		// Final fallback
		return 'video/webm;codecs=vp8,opus';
	};

	/**
	 * Get supported photo MIME type based on format preference
	 * @param {string} format - Preferred format ('jpeg', 'png', 'webp')
	 * @returns {string} Supported MIME type
	 * @private
	 */
	MediaCapture.prototype.get_supported_photo_mime_type = function(format) {
		// Valid photo formats
		var valid_formats = ['jpeg', 'png', 'webp'];
		
		// Check if format is valid
		if (format && valid_formats.indexOf(format) !== -1) {
			return 'image/' + format;
		}
		
		// Fallback to jpeg
		return 'image/jpeg';
	};

	/**
	 * Initialize media capture functionality
	 * Sets up event handlers and starts camera if auto_start is enabled
	 * @private
	 */
	MediaCapture.prototype.init_functionality = function() {
		var self = this;
		
		self.log('[init_functionality] Starting...');
		var container = this.element;
		var mode = this.config.mode;
		var auto_start = this.config.auto_start;
		
		self.log('[init_functionality] mode:', mode);
		self.log('[init_functionality] auto_start:', auto_start);
		var max_duration = this.config.max_duration;
		var countdown_enabled = this.config.countdown_enabled;
		var countdown_duration = this.config.countdown_duration;
		var countdown_sound_enabled = this.config.countdown_sound_enabled;
		var facing_mode = this.config.facing_mode;
		var video_width = this.video_width;
		var video_height = this.video_height;
		var photo_quality = this.config.photo_quality;
		var photo_format = this.config.photo_format;
		var controls_auto_hide = this.config.controls_auto_hide;
		var controls_auto_hide_duration = this.config.controls_auto_hide_duration;

		// Get the wrapper element for setting CSS variables
		var wrapper = container.querySelector('.mc-wrapper');
		
		// Set up ResizeObserver to dynamically update --mc-wrapper-width for responsive control sizing
		var update_wrapper_width = function() {
			if (wrapper) {
				var wrapper_width = wrapper.offsetWidth;
				wrapper.style.setProperty('--mc-wrapper-width', wrapper_width + 'px');
			}
		};
		
		// Initial width setting
		update_wrapper_width();
		
		// Observe size changes on the wrapper itself
		if (typeof ResizeObserver !== 'undefined' && wrapper) {
			var resize_observer = new ResizeObserver(function() {
				update_wrapper_width();
			});
			resize_observer.observe(wrapper);
			
			// Store observer for cleanup
			self.resize_observer = resize_observer;
		}

		// Constants
		var MILLISECONDS_PER_SECOND = 1000;
		var TIMER_UPDATE_INTERVAL = 1000;
		var DURATION_UPDATE_INTERVAL = 100;
		var PERCENTAGE_MAX = 100;
		var VOLUME_LOW_THRESHOLD = 50;
		var ANALYSER_SMOOTHING = 0.3;
		var ANALYSER_FFT_SIZE = 256;
		var DOUBLE_CLICK_PREVENTION_MS = 300; // Prevent accidental double-clicks

		// Selector constants
		var SELECTORS = {
			VIDEO: '.mc-video',
			CANVAS: '.mc-canvas',
			RESULT_PHOTO: '.mc-photo',
			COUNTDOWN: '.mc-countdown',
			START_SCREEN: '.mc-start',
			PREVIEW_CONTAINER: '.mc-capture',
			ERROR_SCREEN: '.mc-error',
			SETTINGS_PANEL: '.mc-settings',
			SETTINGS_BTN: '.mc-settings-btn',
			OVERLAY_CONTROLS: '.mc-capture-controls',
			PLAYBACK_CONTROLS: '.mc-result-controls',
			CONTROLS: '.mc-result-controls',
			PROGRESS: '.mc-progress',
			TIMER: '.mc-timer',
			TIME_CURRENT: '.mc-time-current',
			TIME_DURATION: '.mc-time-duration',
			RECORD_BTN: '.mc-record-btn',
			PAUSE_BTN: '.mc-pause-btn',
			PLAY_BTN: '.mc-play-btn',
			RETAKE_BTN: '.mc-retake-btn',
			CANCEL_BTN: '.mc-cancel-btn',
			FLIP_BTN: '.mc-flip-btn',
			MIC_INDICATOR: '.mc-mic-indicator',
			MIC_FILL: '.mc-mic-bar-fill',
			VIDEO_SELECT: '.mc-video-select',
			AUDIO_SELECT: '.mc-audio-select',
			VOLUME_INDICATOR: '.mc-volume-indicator',
			VOLUME_SLIDER: '.mc-volume-slider',
			VOLUME_FILL: '.mc-volume-fill',
			VOLUME_ICON: '.mc-volume-icon'
		};

		// Note: Using self.stream instead of local media_stream variable
		// This ensures consistency between internal UI and external API calls
		var media_recorder = null;
		var recorded_chunks = [];
		var is_recording = false;
		var audio_context = null;
		var analyser = null;
		var mic_animation_frame = null;

		// Timestamps for double-click prevention
		var last_record_action_time = 0;
		var last_play_pause_action_time = 0;
		var last_pause_recording_action_time = 0;
		var last_photo_action_time = 0; // Shared between capture and retake for photos
		var last_settings_action_time = 0; // For settings button double-click prevention

		// Cached DOM elements
		var video_element = container.querySelector(SELECTORS.VIDEO);
		var canvas_element = container.querySelector(SELECTORS.CANVAS);
		var result_photo = container.querySelector(SELECTORS.RESULT_PHOTO);
		var countdown_overlay = container.querySelector(SELECTORS.COUNTDOWN);
		var start_screen = container.querySelector(SELECTORS.START_SCREEN);
		var preview_container = container.querySelector(SELECTORS.PREVIEW_CONTAINER);
		var error_screen = container.querySelector(SELECTORS.ERROR_SCREEN);
		var settings_panel = container.querySelector(SELECTORS.SETTINGS_PANEL);
		var settings_btn = container.querySelector(SELECTORS.SETTINGS_BTN);
		var overlay_controls = container.querySelector(SELECTORS.OVERLAY_CONTROLS);
		var playback_controls = container.querySelector(SELECTORS.PLAYBACK_CONTROLS);
		var result_controls = container.querySelector(SELECTORS.CONTROLS);

		// Initialize audio context for countdown beeps if enabled
		var beep_audio_context = null;
		if (countdown_sound_enabled) {
			try {
				beep_audio_context = new (window.AudioContext || window.webkitAudioContext)();
			} catch(e) {
				// Audio API not supported - fail silently
			}
		}

		// Detect if mobile device
		this.is_mobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);

		// Check if browser supports programmatic volume control
		// Most mobile browsers don't allow setting volume programmatically
		var test_video = document.createElement('video');
		var supports_volume_control = true;

		try {
			test_video.volume = 0.5;
			// If volume is still 1.0, the browser ignored our attempt to change it
			if (test_video.volume === 1.0) {
				supports_volume_control = false;
			}
		} catch(e) {
			supports_volume_control = false;
		}

		// Hide volume control if browser doesn't support it
		if (!supports_volume_control) {
			var volume_indicator = container.querySelector('.mc-volume-indicator');
			if (volume_indicator) {
				volume_indicator.style.display = 'none';
			}
		}

		// Enumerate devices and populate selectors
		this.enumerate_devices(container, mode);

		// Function to start camera
		var start_camera_fn = function() {
			// Resume AudioContext if needed for countdown beeps
			if (beep_audio_context && beep_audio_context.state === 'suspended') {
				beep_audio_context.resume();
			}

			// Preload photo sound on user interaction to avoid browser blocking
			if (self.config.photo_sound_enabled && self.config.photo_sound_url && !self.photo_sound_preloaded) {
				try {
					var preload_audio = new Audio(self.config.photo_sound_url);
					preload_audio.volume = 0.01; // Very quiet
					preload_audio.play().then(function() {
						preload_audio.pause();
						self.photo_sound_preloaded = true;
					}).catch(function() {
						// Couldn't preload - sound may not work
					});
				} catch(e) {
					// Preload failed
				}
			}

			// Preload countdown sound on user interaction to avoid browser blocking
			if (self.config.countdown_sound_enabled && self.config.countdown_sound_url && !self.countdown_sound_preloaded) {
				try {
					var preload_countdown = new Audio(self.config.countdown_sound_url);
					preload_countdown.volume = 0.01; // Very quiet
					preload_countdown.play().then(function() {
						preload_countdown.pause();
						self.countdown_sound_preloaded = true;
					}).catch(function() {
						// Couldn't preload - sound may not work
					});
				} catch(e) {
					// Preload failed
				}
			}

			// Use the public start_stream method
			self.stream_start()
				.then(function(stream) {
					// self.stream is already set by stream_start()
					// No need to set media_stream

					// Start mic level monitoring for video and audio modes and store references locally
					if (mode === 'video' || mode === 'audio') {
						self.start_mic_monitoring(container, stream, function(context, analyser_node) {
							audio_context = context;
							analyser = analyser_node;
						});
					}

					// Start auto-hide timer when camera starts (if auto-hide is enabled and not audio mode)
					if (controls_auto_hide && mode !== 'audio' && typeof start_autohide_timer === 'function') {
						start_autohide_timer();
					}
				})
				.catch(function(error) {
					// Error handled by start_stream method
				});
		};

		// Start camera screen click handler - clicking anywhere starts the camera
		if (start_screen) {
			start_screen.addEventListener('click', function() {
				start_camera_fn();
			});

			// Also allow keyboard activation
			start_screen.addEventListener('keydown', function(e) {
				if (e.key === ' ' || e.key === 'Enter') {
					e.preventDefault();
					start_camera_fn();
				}
			});

			// Make start screen focusable and add role for accessibility
			start_screen.setAttribute('tabindex', '0');
			start_screen.setAttribute('role', 'button');
			var start_label = mode === 'audio' ? self.config.label_start_microphone : self.config.label_start_camera;
			start_screen.setAttribute('aria-label', start_label);
		}

		// Auto-start camera if enabled
		if (auto_start) {
			start_camera_fn();
		}

		// Event Handlers

		// Settings button - toggle settings panel
		container.addEventListener('click', function(e) {
			var settings_btn_clicked = e.target.closest('.mc-settings-btn');
			if (settings_btn_clicked) {
				self.log('[settings click] Button clicked:', settings_btn_clicked);
				self.log('[settings click] Button disabled:', settings_btn_clicked.disabled);
				self.log('[settings click] is_recording:', self.is_recording);
				
				e.preventDefault();
				
				// Prevent opening settings during recording
				if (self.is_recording) {
					self.log('[settings click] Blocked: is_recording = true');
					return;
				}
				
				// Prevent accidental double-clicks
				var now = Date.now();
				var time_since_last = now - last_settings_action_time;

				if (time_since_last < DOUBLE_CLICK_PREVENTION_MS && last_settings_action_time > 0) {
					self.log('[settings click] Blocked: double-click prevention');
					return; // Too soon after last settings action, ignore
				}

				last_settings_action_time = now;
				
				var is_visible = settings_panel.style.display !== 'none' && settings_panel.style.display !== '';
				self.log('[settings click] Settings panel is_visible:', is_visible);
				
				if (is_visible) {
					self.log('[settings click] Closing settings');
					self.settings_close_internal(container, settings_panel, settings_btn_clicked);
				} else {
					self.log('[settings click] Opening settings');
					self.settings_open_internal(container, settings_panel, settings_btn_clicked);
				}
			}
		});

		// Keyboard handler for settings button
		container.addEventListener('keydown', function(e) {
			var settings_btn_key = e.target.closest('.mc-settings-btn');
			if (settings_btn_key && (e.key === ' ' || e.key === 'Enter')) {
				e.preventDefault();
				settings_btn_key.click();
			}
		});

		// Escape key closes settings panel or goes back in submenu
		container.addEventListener('keydown', function(e) {
			if (e.key === 'Escape') {
				var is_visible = settings_panel.style.display !== 'none' && settings_panel.style.display !== '';
				if (is_visible) {
					// Check if we're in a submenu
					var active_submenu = settings_panel.querySelector('.mc-settings-menu-sub[style*="display: block"]');
					if (active_submenu) {
						// Go back to main menu
						self.hide_settings_submenu(settings_panel);
					} else {
						// Close settings panel
						self.settings_close_internal(container, settings_panel, settings_btn);
					}
				}
			}
		});

		// Click outside settings panel to close it
		var click_outside_handler = function(e) {
			var is_visible = settings_panel.style.display !== 'none' && settings_panel.style.display !== '';
			if (is_visible) {
				// Check if click is outside the settings panel and settings button
				if (!e.target.closest('.mc-settings') && !e.target.closest('.mc-settings-btn')) {
					self.close_settings(container);
				}
			}
		};
		document.addEventListener('click', click_outside_handler);

		// Reposition settings panel on scroll and resize (like native audio controls)
		var reposition_handler = function() {
			var is_visible = settings_panel.style.display !== 'none' && settings_panel.style.display !== '';
			if (is_visible) {
				self.position_settings_panel(settings_panel, settings_btn);
			}
		};
		window.addEventListener('scroll', reposition_handler, true); // Use capture phase to catch all scroll events
		window.addEventListener('resize', reposition_handler);

		// Settings menu navigation
		settings_panel.addEventListener('click', function(e) {
			var menu_item = e.target.closest('.mc-settings-menu-item');
			if (!menu_item) return;
			
			e.preventDefault();
			e.stopPropagation();
			
			var action = menu_item.getAttribute('data-action');
			var device_id = menu_item.getAttribute('data-device-id');
			var device_type = menu_item.getAttribute('data-device-type');
			
			// Handle back button
			if (action === 'back') {
				self.hide_settings_submenu(settings_panel);
				// Focus first menu item in main menu
				var first_item = settings_panel.querySelector('[data-menu="main"] .mc-settings-menu-item');
				if (first_item) first_item.focus();
			}
			// Handle submenu navigation
			else if (action === 'video-input' || action === 'audio-input') {
				self.show_settings_submenu(settings_panel, action);
				// Focus first menu item in submenu (back button)
				var submenu_first_item = settings_panel.querySelector('[data-menu="' + action + '"] .mc-settings-menu-item');
				if (submenu_first_item) submenu_first_item.focus();
			}
			// Handle download action
			else if (action === 'download') {
				self.download();
				self.settings_close_internal(container, settings_panel, settings_btn);
			}
			// Handle device selection
			else if (device_id && device_type) {
				self.select_device(container, video_element, facing_mode, video_width, video_height, 
								  mode, device_type, device_id, audio_context, analyser);
				self.settings_close_internal(container, settings_panel, settings_btn);
			}
		});

		// Keyboard navigation for settings menu
		settings_panel.addEventListener('keydown', function(e) {
			var current_item = e.target.closest('.mc-settings-menu-item');
			if (!current_item) return;
			
			var visible_menu = settings_panel.querySelector('.mc-settings-menu[style*="display: block"], .mc-settings-menu:not([style*="display: none"])');
			if (!visible_menu) return;
			
			var all_items = Array.from(visible_menu.querySelectorAll('.mc-settings-menu-item:not([disabled])'));
			var current_index = all_items.indexOf(current_item);
			
			if (e.key === 'ArrowDown') {
				e.preventDefault();
				var next_index = (current_index + 1) % all_items.length;
				all_items[next_index].focus();
			} else if (e.key === 'ArrowUp') {
				e.preventDefault();
				var prev_index = (current_index - 1 + all_items.length) % all_items.length;
				all_items[prev_index].focus();
			} else if (e.key === ' ' || e.key === 'Enter' || e.key === 'ArrowRight') {
				e.preventDefault();
				current_item.click();
			} else if (e.key === 'ArrowLeft') {
				e.preventDefault();
				// Check if we're in a submenu
				var active_submenu = settings_panel.querySelector('.mc-settings-menu-sub[style*="display: block"]');
				if (active_submenu) {
					// Go back to main menu
					self.hide_settings_submenu(settings_panel);
				}
			}
		});

		// Capture photo button (overlay)
		container.addEventListener('click', function(e) {
			var capture_btn = e.target.closest('.mc-capture-btn');
			if (capture_btn) {
				// Prevent accidental double-clicks (shared with retake button)
				var now = Date.now();
				var time_since_last = now - last_photo_action_time;

				if (time_since_last < DOUBLE_CLICK_PREVENTION_MS && last_photo_action_time > 0) {
					return; // Too soon after last photo action, ignore
				}

				last_photo_action_time = now;

				if (countdown_enabled) {
					self.countdown(
						countdown_overlay,
						countdown_duration,
						countdown_sound_enabled,
						800,
						beep_audio_context,
						self.config.countdown_sound_url,
						function() {
							// Use the public capture_photo method
							self.photo_capture().catch(function(error) {
								// Error handled by capture_photo method
							});
						}
					);
				} else {
					// Use the public capture_photo method
					self.photo_capture().catch(function(error) {
						// Error handled by capture_photo method
					});
				}
			}
		});

		// Record button (overlay)
		// Track event listener additions
		if (!window._mc_record_listener_count) {
			window._mc_record_listener_count = 0;
		}
		window._mc_record_listener_count++;
		self.log('[INIT] Adding record button listener #' + window._mc_record_listener_count);
		
		container.addEventListener('click', function(e) {
			var record_btn = e.target.closest('.mc-record-btn');
			if (record_btn) {
				self.log('[record_btn click] Button clicked (listener #' + window._mc_record_listener_count + ')');
				self.log('[record_btn click] self.stream:', self.stream);
				self.log('[record_btn click] self.is_recording:', self.is_recording);
				
				if (!self.stream) { 
					self.log('[record_btn click] No stream available, returning');
					return; 
				}

				if (!self.is_recording) {
					self.log('[record_btn click] Starting recording');
					// Start recording
					last_record_action_time = Date.now(); // Set timestamp when starting

					// Determine which mode we're in
					if (mode === 'audio') {
						// Audio mode recording
						if (countdown_enabled) {
							self.countdown(
								countdown_overlay,
								countdown_duration,
								countdown_sound_enabled,
								800,
								beep_audio_context,
								self.config.countdown_sound_url,
								function() {
									self.audio_start_internal(container, self.stream, max_duration);
								}
							);
						} else {
							self.audio_start_internal(container, self.stream, max_duration);
						}
					} else if (mode === 'video') {
						// Video mode recording
						if (countdown_enabled) {
								self.countdown(
									countdown_overlay,
									countdown_duration,
									countdown_sound_enabled,
									800,
									beep_audio_context,
									self.config.countdown_sound_url,
									function() {
										recorded_chunks = [];
										is_recording = true;
										self.recorded_chunks = recorded_chunks; // Sync instance property
										self.is_recording = is_recording; // Sync instance property

										self.start_recording(
											self.stream,
											container,
											max_duration,
											function(recorder, interval) {
												media_recorder = recorder;
												self.media_recorder = recorder; // Sync instance property
												self.timer_interval = interval;
											},
											function(chunk) {
												recorded_chunks.push(chunk);
												// No need to push to self.recorded_chunks - they point to the same array
											},
											'video',
											self.config.video_format
										);

										// Update button to stop state
										record_btn.innerHTML = self.config.icon_stop;
										record_btn.setAttribute('aria-label', self.config.label_stop);
										record_btn.setAttribute('title', self.config.label_stop);
										record_btn.classList.add('mc-recording');

										// Show pause button
										var pause_btn = container.querySelector('.mc-pause-btn');
										if (pause_btn) pause_btn.style.display = '';

										// Hide flip button during recording
										var flip_btn = container.querySelector('.mc-flip-btn');
										if (flip_btn) flip_btn.style.display = 'none';
										
										// Disable all settings buttons during recording
										var settings_btns = container.querySelectorAll('.mc-settings-btn');
										settings_btns.forEach(function(btn) {
											btn.disabled = true;
										});

										// Dispatch UI state events for external buttons
										self.dispatch_ui_state('flip', 'off');
										self.dispatch_ui_state('start', 'off');
										self.dispatch_ui_state('stop', 'on');
										self.dispatch_ui_state('pause', 'on');
										self.dispatch_ui_state('settings-open', 'off');
										self.dispatch_ui_state('settings-close', 'off');

										// Dispatch custom event
										self.dispatch_event('video-start');

										// Trigger callback if provided
										if (self.config.on_video_started) {
											self.config.on_video_started();
										}
									}
								);
						} else {
							recorded_chunks = [];
							is_recording = true;
							self.recorded_chunks = recorded_chunks; // Sync instance property
							self.is_recording = is_recording; // Sync instance property

							self.start_recording(
								self.stream,
								container,
								max_duration,
								function(recorder, interval) {
									media_recorder = recorder;
									self.media_recorder = recorder; // Sync instance property
									self.timer_interval = interval;
								},
								function(chunk) {
									recorded_chunks.push(chunk);
									// No need to push to self.recorded_chunks - they point to the same array
								},
								'video',
								self.config.video_format
							);

							// Update button to stop state
							record_btn.innerHTML = self.config.icon_stop;
							record_btn.setAttribute('aria-label', self.config.label_stop);
							record_btn.setAttribute('title', self.config.label_stop);
							record_btn.classList.add('mc-recording');

							// Show pause button
							var pause_btn = container.querySelector('.mc-pause-btn');
							if (pause_btn) pause_btn.style.display = '';

							// Hide flip button during recording
							var flip_btn = container.querySelector('.mc-flip-btn');
							if (flip_btn) flip_btn.style.display = 'none';
							
							// Disable all settings buttons during recording
							var settings_btns = container.querySelectorAll('.mc-settings-btn');
							settings_btns.forEach(function(btn) {
								btn.disabled = true;
							});

							// Dispatch UI state events for external buttons
							self.dispatch_ui_state('flip', 'off');
							self.dispatch_ui_state('start', 'off');
							self.dispatch_ui_state('stop', 'on');
							self.dispatch_ui_state('pause', 'on');
							self.dispatch_ui_state('settings-open', 'off');
							self.dispatch_ui_state('settings-close', 'off');

							// Dispatch custom event
							self.dispatch_event('video-start');

							// Trigger callback if provided
							if (self.config.on_video_started) {
								self.config.on_video_started();
							}
						}
					}
				} else {
					// Stop recording - prevent accidental double-click
					var time_since_start = Date.now() - last_record_action_time;
					if (time_since_start < DOUBLE_CLICK_PREVENTION_MS) {
						return; // Too soon after starting, ignore this click
					}

					if (!self.media_recorder || self.media_recorder.state === 'inactive') { return; }

					// Stop based on mode
					if (mode === 'audio') {
						// Use audio_stop_internal to ensure UI events are dispatched
						var audio_element = container.querySelector('.mc-audio');
						self.audio_stop_internal(container, audio_element, function() {
							self.timer_interval = null;

							// Hide and reset pause button
							var pause_btn = container.querySelector('.mc-pause-btn');
							if (pause_btn) {
								pause_btn.style.display = 'none';
								pause_btn.classList.remove('mc-paused');
								pause_btn.innerHTML = self.config.icon_pause;
								pause_btn.setAttribute('aria-label', self.config.label_pause);
								pause_btn.setAttribute('title', self.config.label_pause);
							}

							// Remove paused class from timer
							var timer = container.querySelector('.mc-timer');
							if (timer) timer.classList.remove('mc-paused');

							// Hide cancel button and clear backup data
							var cancel_btn = container.querySelector('.mc-cancel-btn');
							if (cancel_btn) cancel_btn.style.display = 'none';
							self.blob_backup = null;
							self.filename_backup = null;
						});
					} else if (mode === 'video') {
						// Use video_stop_internal to ensure UI events are dispatched
						self.video_stop_internal(container, video_element, function() {
							self.timer_interval = null;
							// Note: self.stream is managed by stream lifecycle methods

							// Hide and reset pause button
							var pause_btn = container.querySelector('.mc-pause-btn');
							if (pause_btn) {
								pause_btn.style.display = 'none';
								pause_btn.classList.remove('mc-paused');
								pause_btn.innerHTML = self.config.icon_pause;
								pause_btn.setAttribute('aria-label', self.config.label_pause);
								pause_btn.setAttribute('title', self.config.label_pause);
							}

							// Remove paused class from timer
							var timer = container.querySelector('.mc-timer');
							if (timer) timer.classList.remove('mc-paused');

							// Hide cancel button and clear backup data (new recording replaces old)
							var cancel_btn = container.querySelector('.mc-cancel-btn');
							if (cancel_btn) cancel_btn.style.display = 'none';
							self.blob_backup = null;
							self.filename_backup = null;
							self.video_url_backup = null;

							// Stop mic monitoring
							if (mic_animation_frame) {
								cancelAnimationFrame(mic_animation_frame);
								mic_animation_frame = null;
							}
							if (audio_context && audio_context.state !== 'closed') {
								audio_context.close();
							}
							audio_context = null;
							analyser = null;
						});
					}
				}
			}
		});

		// Pause/Resume button (overlay)
		container.addEventListener('click', function(e) {
			var pause_btn = e.target.closest('.mc-pause-btn');
			if (pause_btn) {
				if (!self.media_recorder) { return; }

				// Prevent accidental double-clicks
				var now = Date.now();
				if (now - last_pause_recording_action_time < DOUBLE_CLICK_PREVENTION_MS) {
					return; // Too soon after last action, ignore
				}
				last_pause_recording_action_time = now;

				if (self.media_recorder.state === 'recording') {
					// Pause based on mode
					if (mode === 'audio') {
						self.audio_pause_internal(container);
					} else if (mode === 'video') {
						self.video_pause_internal(container);
					}
				} else if (self.media_recorder.state === 'paused') {
					// Resume based on mode
					if (mode === 'audio') {
						self.audio_resume_internal(container);
					} else if (mode === 'video') {
						self.video_resume_internal(container);
					}
				}
			}
		});

		// Mic mute toggle (click)
		container.addEventListener('click', function(e) {
			var mic_indicator = e.target.closest('.mc-mic-indicator');
			if (mic_indicator) {
				if (!self.stream) { return; }

				var mic_icon = mic_indicator.querySelector('.mc-mic-icon');
				var audio_tracks = self.stream.getAudioTracks();

				if (audio_tracks.length > 0) {
					var is_muted = !audio_tracks[0].enabled;

					// Toggle mute
					audio_tracks.forEach(function(track) {
						track.enabled = is_muted;
					});

					// Update icon and aria-label
					if (is_muted) {
						// Unmuted
						mic_icon.innerHTML = self.config.icon_mic;
						mic_indicator.setAttribute('aria-label', self.config.label_mic_active);
						mic_indicator.setAttribute('title', self.config.label_mic_active);
						mic_indicator.classList.remove('mc-mic-muted');
					} else {
						// Muted
						mic_icon.innerHTML = self.config.icon_mic_muted;
						mic_indicator.setAttribute('aria-label', self.config.label_mic_muted);
						mic_indicator.setAttribute('title', self.config.label_mic_muted);
						mic_indicator.classList.add('mc-mic-muted');
					}
				}
			}
		});

		// Mic mute toggle (keyboard M key)
		container.addEventListener('keydown', function(e) {
			var mic_indicator_key = e.target.closest('.mc-mic-indicator');
			if (mic_indicator_key && (e.key === 'M' || e.key === 'm' || e.key === ' ' || e.key === 'Enter')) {
				e.preventDefault();
				mic_indicator_key.click();
			}
		});

		// Keyboard handlers for buttons (Space/Enter)
		container.addEventListener('keydown', function(e) {
			var button = e.target.closest('.mc-capture-btn, .mc-record-btn, .mc-pause-btn, .mc-play-btn, .mc-retake-btn, .mc-cancel-btn, .mc-flip-btn');
			if (button && (e.key === ' ' || e.key === 'Enter')) {
				e.preventDefault();
				button.click();
			}
		});

		// Custom video player controls - play/pause button
		container.addEventListener('click', function(e) {
			var play_btn = e.target.closest('.mc-play-btn');
			if (play_btn) {
				// Prevent accidental double-clicks
				var now = Date.now();
				if (now - last_play_pause_action_time < DOUBLE_CLICK_PREVENTION_MS) {
					return; // Too soon after last action, ignore
				}
				last_play_pause_action_time = now;

				// Get the appropriate media element (video or audio)
				var media_element = video_element || self.audio_element;
				
				if (media_element && media_element.paused) {
					media_element.play();
					play_btn.innerHTML = self.config.icon_pause;
					play_btn.setAttribute('aria-label', self.config.label_pause);
				} else if (media_element) {
					media_element.pause();
					play_btn.innerHTML = self.config.icon_play;
					play_btn.setAttribute('aria-label', self.config.label_play);
				}
			}
		});

		// Video element event listeners (only for photo/video modes)
		if (video_element) {
			// Video time update
			video_element.addEventListener('timeupdate', function() {
				// Get effective duration (prioritize data-actual-duration from recording)
				var actual_duration = parseFloat(video_element.getAttribute('data-actual-duration'));
				var effective_duration = 0;
				if (actual_duration && actual_duration > 0) {
					effective_duration = actual_duration;
				} else if (!isNaN(video_element.duration) && isFinite(video_element.duration)) {
					effective_duration = video_element.duration;
				} else if (video_element.buffered.length > 0) {
					effective_duration = video_element.buffered.end(video_element.buffered.length - 1);
				}

				if (effective_duration > 0) {
					var current = self.format_time(video_element.currentTime);
					var duration = self.format_time(effective_duration);
					var time_current = container.querySelector(SELECTORS.TIME_CURRENT);
					var time_duration = container.querySelector(SELECTORS.TIME_DURATION);
					if (time_current) time_current.textContent = current;
					if (time_duration) time_duration.textContent = duration;

					// Update progress bar - just set the percentage!
					var percent = Math.min(PERCENTAGE_MAX, (video_element.currentTime / effective_duration) * PERCENTAGE_MAX);
					var progress_bar = container.querySelector(SELECTORS.PROGRESS);
					if (progress_bar) {
						progress_bar.style.setProperty('--mc-progress-percent', percent + '%');
						progress_bar.setAttribute('aria-valuenow', Math.round(percent).toString());
					}
				}
			});

			// Listen for duration change (for blob videos that start with Infinity)
			video_element.addEventListener('durationchange', function() {
				if (!isNaN(video_element.duration) && isFinite(video_element.duration)) {
					var duration = self.format_time(video_element.duration);
					var time_duration = container.querySelector('.mc-time-duration');
					if (time_duration) time_duration.textContent = duration;
				}
			});

			// Video loaded metadata
			video_element.addEventListener('loadedmetadata', function() {
				if (!isNaN(video_element.duration) && isFinite(video_element.duration)) {
					var duration = self.format_time(video_element.duration);
					var time_duration = container.querySelector('.mc-time-duration');
					if (time_duration) time_duration.textContent = duration;
				}
			});

			// Video ended
			video_element.addEventListener('ended', function() {
				var play_btn = container.querySelector('.mc-play-btn');
				if (play_btn) {
					play_btn.innerHTML = self.config.icon_play;
					play_btn.setAttribute('aria-label', self.config.label_play);
				}
				
				// Dispatch UI state events when video ends (same as pause)
				self.dispatch_ui_state('playback-play', 'on');
				self.dispatch_ui_state('playback-pause', 'off');
				self.dispatch_ui_state('playback-stop', 'off');
			});
			
			// Video play event - dispatch UI state when video starts playing
			video_element.addEventListener('play', function() {
				// Only dispatch if in video mode, not recording, and has recorded content
				// (i.e., not the live preview stream)
				if (self.config.mode === 'video' && !self.is_recording && self.blob) {
					self.dispatch_ui_state('playback-play', 'off');
					self.dispatch_ui_state('playback-pause', 'on');
				}
			});
			
			// Video pause event - dispatch UI state when video is paused
			video_element.addEventListener('pause', function() {
				// Only dispatch if in video mode and not currently recording
				if (self.config.mode === 'video' && !self.is_recording) {
					self.dispatch_ui_state('playback-play', 'on');
					self.dispatch_ui_state('playback-pause', 'off');
				}
			});
		}
		
		// Audio element event listeners (only for audio mode)
		if (self.audio_element) {
			// Audio time update
			self.audio_element.addEventListener('timeupdate', function() {
				// Get effective duration (prioritize data-actual-duration from recording)
				var actual_duration = parseFloat(self.audio_element.getAttribute('data-actual-duration'));
				var effective_duration = 0;
				if (actual_duration && actual_duration > 0) {
					effective_duration = actual_duration;
				} else if (!isNaN(self.audio_element.duration) && isFinite(self.audio_element.duration)) {
					effective_duration = self.audio_element.duration;
				} else if (self.audio_element.buffered.length > 0) {
					effective_duration = self.audio_element.buffered.end(self.audio_element.buffered.length - 1);
				}

				if (effective_duration > 0) {
					var current = self.format_time(self.audio_element.currentTime);
					var duration = self.format_time(effective_duration);
					var time_current = container.querySelector(SELECTORS.TIME_CURRENT);
					var time_duration = container.querySelector(SELECTORS.TIME_DURATION);
					if (time_current) time_current.textContent = current;
					if (time_duration) time_duration.textContent = duration;

					// Update progress bar - just set the percentage!
					var percent = Math.min(PERCENTAGE_MAX, (self.audio_element.currentTime / effective_duration) * PERCENTAGE_MAX);
					var progress_bar = container.querySelector(SELECTORS.PROGRESS);
					if (progress_bar) {
						progress_bar.style.setProperty('--mc-progress-percent', percent + '%');
						progress_bar.setAttribute('aria-valuenow', Math.round(percent).toString());
					}
				}
			});

			// Listen for duration change (for blob audio that start with Infinity)
			self.audio_element.addEventListener('durationchange', function() {
				if (!isNaN(self.audio_element.duration) && isFinite(self.audio_element.duration)) {
					var duration = self.format_time(self.audio_element.duration);
					var time_duration = container.querySelector('.mc-time-duration');
					if (time_duration) time_duration.textContent = duration;
				}
			});

			// Audio loaded metadata
			self.audio_element.addEventListener('loadedmetadata', function() {
				if (!isNaN(self.audio_element.duration) && isFinite(self.audio_element.duration)) {
					var duration = self.format_time(self.audio_element.duration);
					var time_duration = container.querySelector('.mc-time-duration');
					if (time_duration) time_duration.textContent = duration;
				}
			});

			// Audio ended
			self.audio_element.addEventListener('ended', function() {
				var play_btn = container.querySelector('.mc-play-btn');
				if (play_btn) {
					play_btn.innerHTML = self.config.icon_play;
					play_btn.setAttribute('aria-label', self.config.label_play);
				}
				
				// Dispatch UI state events when audio ends (same as pause)
				self.dispatch_ui_state('playback-play', 'on');
				self.dispatch_ui_state('playback-pause', 'off');
				self.dispatch_ui_state('playback-stop', 'off');
			});
			
			// Audio play event - dispatch UI state when audio starts playing
			self.audio_element.addEventListener('play', function() {
				// Only dispatch if in audio mode, not recording, and has recorded content
				// (i.e., not the live preview stream)
				if (self.config.mode === 'audio' && !self.is_recording && self.blob) {
					self.dispatch_ui_state('playback-play', 'off');
					self.dispatch_ui_state('playback-pause', 'on');
				}
			});
			
			// Audio pause event - dispatch UI state when audio is paused
			self.audio_element.addEventListener('pause', function() {
				// Only dispatch if in audio mode and not currently recording
				if (self.config.mode === 'audio' && !self.is_recording) {
					self.dispatch_ui_state('playback-play', 'on');
					self.dispatch_ui_state('playback-pause', 'off');
				}
			});
		}

		// Progress bar scrubbing (mouse and touch support)
		container.addEventListener('mousedown', function(e) {
			var progress_bar = e.target.closest('.mc-progress');
			if (progress_bar) {
				handle_scrub_start(e, progress_bar);
			}
		});

		container.addEventListener('touchstart', function(e) {
			var progress_bar = e.target.closest('.mc-progress');
			if (progress_bar) {
				handle_scrub_start(e, progress_bar);
			}
		});

		function handle_scrub_start(e, progress_bar) {
			// Get the appropriate media element (video or audio)
			var media_element = video_element || self.audio_element;
			if (!media_element) return;
			
			var was_playing = !media_element.paused;

			// Focus the scrubber when clicked
			progress_bar.focus();

			// Pause video while scrubbing for smoother seeking
			if (was_playing) {
				media_element.pause();
			}

			// Get effective duration (prioritize data-actual-duration from recording)
			function get_effective_duration() {
				// First try the data attribute (most accurate for recorded videos)
				var actual_duration = parseFloat(media_element.getAttribute('data-actual-duration'));
				if (actual_duration && actual_duration > 0) {
					return actual_duration;
				}

				// Then try video.duration
				if (isFinite(media_element.duration) && media_element.duration > 0) {
					return media_element.duration;
				}

				// Finally fall back to buffered
				if (media_element.buffered.length > 0) {
					return media_element.buffered.end(media_element.buffered.length - 1);
				}

				return 0;
			}

			function seek(event) {
				var rect = progress_bar.getBoundingClientRect();
				// Get padding from computed style
				var computedStyle = window.getComputedStyle(progress_bar);
				var padding = parseFloat(computedStyle.paddingLeft);

				// Get X position from mouse or touch event
				var clientX;
				if (event.type.indexOf('touch') === 0) {
					// Touch event
					var touch = event.touches[0] || event.changedTouches[0];
					clientX = touch.clientX;
				} else {
					// Mouse event
					clientX = event.clientX;
				}

				// Calculate position - clientX is relative to viewport, rect.left is the element's left edge
				var clickX = clientX - rect.left;
				var totalWidth = rect.width;
				var usableWidth = totalWidth - (padding * 2);

				// Adjust for the padding offset - the bar starts at 'padding' pixels from the left
				var relativeX = clickX - padding;
				var percent = Math.max(0, Math.min(1, relativeX / usableWidth));

				var effective_duration = get_effective_duration();

				if (effective_duration > 0) {
					media_element.currentTime = percent * effective_duration;
				}

				// Immediate visual update of progress bar - just set the percentage!
				progress_bar.style.setProperty('--mc-progress-percent', (percent * 100) + '%');

				// Update time display immediately
				var current = self.format_time(media_element.currentTime);
				var time_current = container.querySelector('.mc-time-current');
				if (time_current) time_current.textContent = current;
			}

			// Seek on initial touch/click
			seek(e);

			// Continue seeking on drag
			function handle_move(e) {
				seek(e);
			}

			document.addEventListener('mousemove', handle_move);
			document.addEventListener('touchmove', handle_move);

			// Clean up on release
			function handle_end() {
				document.removeEventListener('mousemove', handle_move);
				document.removeEventListener('touchmove', handle_move);
				document.removeEventListener('mouseup', handle_end);
				document.removeEventListener('touchend', handle_end);

				// Resume playing if video was playing before scrubbing
				if (was_playing) {
					media_element.play();
				}
			}

			document.addEventListener('mouseup', handle_end, { once: true });
			document.addEventListener('touchend', handle_end, { once: true });

			e.preventDefault();
		}

		// Keyboard navigation for progress bar
		container.addEventListener('keydown', function(e) {
			var progress_bar = e.target.closest('.mc-progress');
			if (progress_bar) {
				// Get the appropriate media element (video or audio)
				var media_element = video_element || self.audio_element;
				if (!media_element) return;
				
				// Get effective duration
				var actual_duration = parseFloat(media_element.getAttribute('data-actual-duration'));
				var duration = 0;
				if (actual_duration && actual_duration > 0) {
					duration = actual_duration;
				} else if (isFinite(media_element.duration) && media_element.duration > 0) {
					duration = media_element.duration;
				} else if (media_element.buffered.length > 0) {
					duration = media_element.buffered.end(media_element.buffered.length - 1);
				}

				if (duration === 0) { return; }

				var handled = false;

				if (e.key === ' ' || e.key === 'Enter') {
					// Space/Enter toggles play/pause
					e.preventDefault();
					var play_btn = container.querySelector('.mc-play-btn');
					if (play_btn) play_btn.click();
					return;
				} else if (e.key === 'ArrowLeft' || e.key === 'ArrowDown') {
					// Seek backward 5% of duration
					var seek_amount = duration * 0.05;
					media_element.currentTime = Math.max(0, media_element.currentTime - seek_amount);
					handled = true;
				} else if (e.key === 'ArrowRight' || e.key === 'ArrowUp') {
					// Seek forward 5% of duration
					var seek_amount = duration * 0.05;
					media_element.currentTime = Math.min(duration, media_element.currentTime + seek_amount);
					handled = true;
				} else if (e.key === 'Home') {
					// Go to start
					media_element.currentTime = 0;
					handled = true;
				} else if (e.key === 'End') {
					// Go to end
					media_element.currentTime = duration;
					handled = true;
				}

				if (handled) {
					e.preventDefault();
					// Update UI immediately - just set the percentage!
					var percent = Math.min(100, (media_element.currentTime / duration) * 100);
					if (progress_bar) {
						progress_bar.style.setProperty('--mc-progress-percent', percent + '%');
						progress_bar.setAttribute('aria-valuenow', Math.round(percent).toString());
					}
					var current = self.format_time(media_element.currentTime);
					var time_current = container.querySelector('.mc-time-current');
					if (time_current) time_current.textContent = current;
				}
			}
		});

		// Volume icon button for muting
		container.addEventListener('click', function(e) {
			var volume_icon = e.target.closest('.mc-volume-icon');
			if (volume_icon) {
				// Get the appropriate media element (video or audio)
				var media_element = video_element || self.audio_element;
				if (!media_element) return;
				
				var volume_control = volume_icon.closest('.mc-volume-indicator');
				var slider = container.querySelector('.mc-volume-slider');
				var bar = container.querySelector('.mc-volume-fill');

				// Focus the volume control wrapper when icon is clicked
				if (volume_control) volume_control.focus();

				if (media_element.muted || media_element.volume === 0) {
					// Unmute and restore volume
					media_element.muted = false;
					var restore_volume = slider.getAttribute('data-last-volume') || PERCENTAGE_MAX;
					restore_volume = parseInt(restore_volume, 10);
					media_element.volume = restore_volume / PERCENTAGE_MAX;
					slider.value = restore_volume;
					if (bar) bar.style.width = restore_volume + '%';
					if (volume_control) volume_control.setAttribute('aria-valuenow', restore_volume.toString());

					// Update icon
					if (restore_volume < VOLUME_LOW_THRESHOLD) {
						volume_icon.innerHTML = self.config.icon_volume_low;
					} else {
						volume_icon.innerHTML = self.config.icon_volume;
					}

					// Update aria-label to Volume
					if (volume_control) {
						volume_control.setAttribute('aria-label', self.config.label_volume);
						volume_control.setAttribute('title', self.config.label_volume);
					}

				} else {
					// Mute and save current volume
					slider.setAttribute('data-last-volume', slider.value);
					media_element.muted = true;
					media_element.volume = 0;
					slider.value = 0;
					if (bar) bar.style.width = '0%';
					if (volume_control) volume_control.setAttribute('aria-valuenow', '0');

					// Update icon to muted
					volume_icon.innerHTML = self.config.icon_volume_muted;

					// Update aria-label and title to Volume Muted
					if (volume_control) {
						volume_control.setAttribute('aria-label', self.config.label_volume_muted);
						volume_control.setAttribute('title', self.config.label_volume_muted);
					}
				}

				if (volume_control) {
					if (media_element.muted) {
						volume_control.classList.add('mc-volume-muted');
					} else {
						volume_control.classList.remove('mc-volume-muted');
					}
				}

				e.stopPropagation();
			}
		});

		// Volume bar mousedown - focus wrapper and prevent slider from getting focus
		container.addEventListener('mousedown', function(e) {
			var volume_bar = e.target.closest('.mc-volume-bar');
			if (volume_bar) {
				var volume_control = volume_bar.closest('.mc-volume-indicator');
				if (volume_control) {
					// Focus the wrapper immediately on mousedown
					volume_control.focus();
					// Don't stop propagation - let the slider still work
				}
			}
		});

		// Prevent slider from taking focus - immediately transfer to parent
		container.addEventListener('focus', function(e) {
			var slider = e.target.closest('.mc-volume-slider');
			if (slider) {
				var volume_control = slider.closest('.mc-volume-indicator');
				if (volume_control) {
					volume_control.focus();
				}
			}
		}, true); // Use capture phase

		// Volume bar click - ensure focus on click (for cases where mousedown didn't fire)
		container.addEventListener('click', function(e) {
			var volume_bar = e.target.closest('.mc-volume-bar');
			if (volume_bar) {
				var volume_control = volume_bar.closest('.mc-volume-indicator');
				if (volume_control) {
					// Focus the wrapper
					volume_control.focus();
					e.stopPropagation();
				}
			}
		});

		// Volume slider input
		container.addEventListener('input', function(e) {
			var slider = e.target.closest('.mc-volume-slider');
			if (slider) {
				// Get the appropriate media element (video or audio)
				var media_element = video_element || self.audio_element;
				if (!media_element) return;
				
				var volume = parseInt(slider.value, 10);
				var volume_control = slider.closest('.mc-volume-indicator');
				var bar = container.querySelector('.mc-volume-fill');
				var icon = container.querySelector('.mc-volume-icon');

				// Focus the volume control wrapper when slider is used
				if (volume_control) volume_control.focus();

				// Update visual bar
				if (bar) bar.style.width = volume + '%';

				// Update audio volume
				media_element.volume = volume / PERCENTAGE_MAX;
				media_element.muted = (volume === 0);

				// Update wrapper aria-valuenow
				if (volume_control) volume_control.setAttribute('aria-valuenow', volume.toString());

				// Update icon based on volume
				if (icon) {
					if (volume === 0) {
						// Muted/zero
						icon.innerHTML = self.config.icon_volume_muted;
						if (volume_control) {
							volume_control.setAttribute('aria-label', self.config.label_volume_muted);
							volume_control.setAttribute('title', self.config.label_volume_muted);
						}
					} else if (volume < VOLUME_LOW_THRESHOLD) {
						// Low volume
						icon.innerHTML = self.config.icon_volume_low;
						if (volume_control) {
							volume_control.setAttribute('aria-label', self.config.label_volume);
							volume_control.setAttribute('title', self.config.label_volume);
						}
					} else {
						// Full volume
						icon.innerHTML = self.config.icon_volume;
						if (volume_control) {
							volume_control.setAttribute('aria-label', self.config.label_volume);
							volume_control.setAttribute('title', self.config.label_volume);
						}
					}
				}

				if (volume_control) {
					if (media_element.muted) {
						volume_control.classList.add('mc-volume-muted');
					} else {
						volume_control.classList.remove('mc-volume-muted');
					}
				}
			}
		});

		// Keyboard handler for volume control wrapper
		container.addEventListener('keydown', function(e) {
			var volume_control = e.target.closest('.mc-volume-indicator');
			if (volume_control) {
				var slider = volume_control.querySelector('.mc-volume-slider');
				var icon = volume_control.querySelector('.mc-volume-icon');
				if (!slider) return;
				
				var current_volume = parseInt(slider.value, 10);
				var handled = false;

				if (e.key === 'M' || e.key === 'm') {
					// M key toggles mute
					if (icon) icon.click();
					handled = true;
				} else if (e.key === 'ArrowUp' || e.key === 'ArrowRight') {
					// Increase volume by 5%
					var new_volume = Math.min(100, current_volume + 5);
					slider.value = new_volume;
					slider.dispatchEvent(new Event('input', { bubbles: true }));
					handled = true;
				} else if (e.key === 'ArrowDown' || e.key === 'ArrowLeft') {
					// Decrease volume by 5%
					var new_volume = Math.max(0, current_volume - 5);
					slider.value = new_volume;
					slider.dispatchEvent(new Event('input', { bubbles: true }));
					handled = true;
				}

				if (handled) {
					e.preventDefault();
				}
			}
		});

		// Retake button
		container.addEventListener('click', function(e) {
			var retake_btn = e.target.closest('.mc-retake-btn');
			if (retake_btn) {
				// Prevent accidental double-clicks (shared with capture button)
				var now = Date.now();
				var time_since_last = now - last_photo_action_time;

				if (time_since_last < DOUBLE_CLICK_PREVENTION_MS && last_photo_action_time > 0) {
					return; // Too soon after last photo action, ignore
				}

				last_photo_action_time = now;

				// For photo mode, use the photo_retake_internal method
				if (mode === 'photo') {
					self.photo_retake_internal(container);
				} else if (mode === 'audio') {
					// For audio mode, use the audio_retake_internal method
					self.audio_retake_internal(container);
				} else {
					// For video mode, handle video retake
					// Backup the current blob and filename before clearing (for cancel functionality)
					self.blob_backup = self.blob;
					self.filename_backup = self.filename;

					self.blob = null;
					self.filename = null;

					// Hide download option
					self.hide_download_option(container);

					// Store video URL for cancel functionality
					self.video_url_backup = video_element.src;

					// Reset recording state variables
					is_recording = false;
					media_recorder = null;
					recorded_chunks = [];
					if (self.timer_interval) {
						clearInterval(self.timer_interval);
						self.timer_interval = null;
					}

					// Clear the recorded video source
					video_element.pause();
					video_element.currentTime = 0;
					video_element.src = '';
					video_element.removeAttribute('data-actual-duration');

					// Reset to muted autoplay for camera preview
					video_element.muted = true;
					video_element.autoplay = true;

					// Reset progress bar using CSS variable
					var progress_bar = container.querySelector(SELECTORS.PROGRESS);
					if (progress_bar) {
						progress_bar.style.setProperty('--mc-progress-percent', '0%');
					}
					var time_current = container.querySelector(SELECTORS.TIME_CURRENT);
					var time_duration = container.querySelector(SELECTORS.TIME_DURATION);
					if (time_current) time_current.textContent = '0:00';
					if (time_duration) time_duration.textContent = '0:00';

					// Reset play button
					var play_btn = container.querySelector('.mc-play-btn');
					if (play_btn) {
						play_btn.innerHTML = self.config.icon_play;
						play_btn.setAttribute('aria-label', self.config.label_play);
					}

					// Hide playback controls, show overlay controls
					if (playback_controls) playback_controls.style.display = 'none';
					if (overlay_controls) overlay_controls.style.display = '';

					// Show cancel button and hide pause button in overlay controls
					var cancel_btn = container.querySelector(SELECTORS.CANCEL_BTN);
					var pause_btn = container.querySelector(SELECTORS.PAUSE_BTN);
					if (cancel_btn) cancel_btn.style.display = '';
					if (pause_btn) pause_btn.style.display = 'none';

					// Reset timer display
					var timer = container.querySelector(SELECTORS.TIMER);
					if (timer) timer.classList.remove('mc-paused');
					var timer_elapsed = container.querySelector('.mc-timer-elapsed');
					if (timer_elapsed) timer_elapsed.textContent = '00:00';

					// Reset remaining time if it exists
					var timer_remaining = container.querySelector('.mc-timer-remaining');
					if (timer_remaining) {
						var initial_mins = Math.floor(max_duration / 60);
						var initial_secs = max_duration % 60;
						timer_remaining.textContent = self.pad_zero(initial_mins) + ':' + self.pad_zero(initial_secs);
					}

					if (settings_panel) settings_panel.style.display = 'none';

					// Restart camera
					self.stream_start_internal(
						container,
						video_element,
						facing_mode,
						video_width,
						video_height,
						mode,
						self.selected_video_device,
						self.selected_audio_device,
						function(stream) {
							self.stream = stream;
							if (mode === 'video') {
								self.start_mic_monitoring(container, stream, function(context, analyser_node) {
									audio_context = context;
									analyser = analyser_node;
								});
							}
						}
					);

					// Reset record button if video mode
					var record_btn = container.querySelector(SELECTORS.RECORD_BTN);
					if (record_btn) {
						record_btn.innerHTML = self.config.icon_record;
						record_btn.setAttribute('aria-label', self.config.label_start_recording);
						record_btn.setAttribute('title', self.config.label_start_recording);
						record_btn.classList.remove('mc-recording');
						record_btn.classList.remove('mc-paused');
					}

					// Show flip button again if multiple cameras available
					if (self.has_multiple_cameras) {
						var flip_btn = container.querySelector('.mc-flip-btn');
						if (flip_btn) flip_btn.style.display = '';
					}

					// Move focus to record button for keyboard users
					if (record_btn) record_btn.focus();
				}

				// Move focus to capture button for keyboard users (photo mode)
				if (mode === 'photo') {
					var capture_btn = container.querySelector('.mc-capture-btn');
					if (capture_btn) capture_btn.focus();
				}
			}
		});

		// Cancel retake button (video/audio/photo modes)
		container.addEventListener('click', function(e) {
			var cancel_btn = e.target.closest('.mc-cancel-btn');
			if (cancel_btn) {
				if (mode === 'photo') {
					// Photo mode: restore the backed up photo
					self.photo_cancel_internal(container);
				} else if (mode === 'audio') {
					// Audio mode: restore the backed up audio
					self.audio_cancel_internal(container);
				} else {
					// Video mode: restore the backed up video
					self.video_cancel_internal(container);
				}
			}
		});

		// Camera flip button (mobile only)
		container.addEventListener('click', function(e) {
			var flip_btn = e.target.closest('.mc-flip-btn');
			if (flip_btn) {
				// Don't allow flipping if no stream or if no multiple cameras
				if (!self.stream || !self.has_multiple_cameras) { 
					return; 
				}

				// Don't allow flipping during recording - it causes playback issues
				if (mode === 'video' && is_recording && media_recorder && media_recorder.state === 'recording') {
					return;
				}

				self.stream_flip_internal(container, video_element, mode, function(new_stream) {
					self.stream = new_stream;
					if (mode === 'video') {
						self.start_mic_monitoring(container, new_stream, function(context, analyser_node) {
							audio_context = context;
							analyser = analyser_node;
						});
					}
				});
			}
		});

		// Auto-hide controls functionality (not for audio mode)
		if (controls_auto_hide && mode !== 'audio') {
			var hide_timeout;
			var controls_ready = false; // Flag to prevent hiding during initial 3 seconds

			// Add auto-hide class to both preview and result containers
			if (preview_container) preview_container.classList.add('mc-controls-autohide');
			if (preview_container) preview_container.classList.add('mc-controls-autohide');

			// Function to show controls
			function show_controls() {
				self.show_controls();
				clearTimeout(hide_timeout);
			}

			// Function to hide controls immediately (only if ready)
			function hide_controls() {
				if (controls_ready) {
					self.hide_controls();
					clearTimeout(hide_timeout);
				}
			}

			// Function to start the auto-hide timer (called after camera starts)
			var start_autohide_timer = function() {
				// Show controls initially and keep visible for controls_auto_hide_duration milliseconds
				show_controls();
				setTimeout(function() {
					controls_ready = true;
					// Hide if mouse is not over the container after controls_auto_hide_duration milliseconds
					if (!preview_container.matches(':hover')) {
						hide_controls();
					}
				}, controls_auto_hide_duration);
			};

			// Helper function to check if element is hovered
			function is_hovered(element) {
				if (!element) return false;
				return element.matches(':hover');
			}

			// Show controls on mouse enter (both containers)
			if (preview_container) {
				preview_container.addEventListener('mouseenter', function() {
					show_controls();
				});
			}

			if (preview_container) {
				preview_container.addEventListener('mouseenter', function() {
					show_controls();
				});
			}

			// Hide controls immediately on mouse leave (only after initial 3 seconds)
			if (preview_container) {
				preview_container.addEventListener('mouseleave', function() {
					hide_controls();
				});
			}

			// Show controls when any control gets focus (keep visible while focused)
			if (overlay_controls) {
				overlay_controls.addEventListener('focusin', function() {
					show_controls();
				});
			}

			if (playback_controls) {
				playback_controls.addEventListener('focusin', function() {
					show_controls();
				});
			}

			if (result_controls) {
				result_controls.addEventListener('focusin', function() {
					show_controls();
				});
			}

			// Hide controls when focus leaves (unless hovering)
			if (overlay_controls) {
				overlay_controls.addEventListener('focusout', function() {
					if (!is_hovered(preview_container)) {
						hide_controls();
					}
				});
			}

			if (playback_controls) {
				playback_controls.addEventListener('focusout', function() {
					if (!is_hovered(preview_container)) {
						hide_controls();
					}
				});
			}

			if (result_controls) {
				result_controls.addEventListener('focusout', function() {
					if (!is_hovered(result_container)) {
						hide_controls();
					}
				});
			}

			// If auto-start is enabled, start the timer immediately (camera already started)
			if (auto_start) {
				start_autohide_timer();
			}
		}
	};

	/**
	 * Destroy the MediaCapture instance
	 * Cleans up streams, elements, and event listeners
	 */
	MediaCapture.prototype.destroy = function() {
		// Disconnect ResizeObserver
		if (this.resize_observer) {
			this.resize_observer.disconnect();
			this.resize_observer = null;
		}
		
		// TODO: Implement additional cleanup logic
	};

	/**
	 * Dispatch a custom event from the MediaCapture element
	 * @param {string} event_name - Name of the event (without 'mc-' prefix)
	 * @param {Object} detail - Event detail data
	 * @private
	 */
	MediaCapture.prototype.dispatch_event = function(event_name, detail) {
		// Merge the provided detail with the instance reference
		var event_detail = Object.assign({}, detail || {}, {
			instance: this
		});
		
		this.log('Dispatching event: mc-' + event_name, (detail ? detail : ''));
		
		var event = new CustomEvent('mc-' + event_name, {
			detail: event_detail,
			bubbles: true,
			cancelable: true
		});
		this.element.dispatchEvent(event);
	};

	/**
	 * Dispatch UI state change event and trigger callback
	 * @param {string} element_name - Name of UI element (flip, capture, retake, cancel, settings)
	 * @param {string} state - State 'on' or 'off'
	 * @private
	 */
	MediaCapture.prototype.dispatch_ui_state = function(element_name, state) {
		var event_name = 'ui-' + element_name + '-' + state;
		var callback_name = 'on_' + event_name.replace(/-/g, '_');
		
		this.log('Dispatching UI state: ' + event_name);
		
		// Dispatch event
		this.dispatch_event(event_name);
		
		// Trigger callback if provided
		if (this.config[callback_name]) {
			this.log('Executing callback: ' + callback_name);
			this.config[callback_name]();
		}
	};

	/**
	 * Enumerate media devices and populate menu items
	 * @private
	 */
	MediaCapture.prototype.enumerate_devices = function(container, mode) {
		var self = this;
		
		self.log('Enumerating media devices for mode: ' + mode);
		
		if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) {
			self.log('enumerateDevices not available', 'warn');
			return;
		}

		navigator.mediaDevices.enumerateDevices()
			.then(function(devices) {
				self.log('Found ' + devices.length + ' total media devices');
				
				var video_devices_container = container.querySelector('.mc-video-devices');
				var audio_devices_container = container.querySelector('.mc-audio-devices');

				if (video_devices_container) {
					// Clear existing items
					video_devices_container.innerHTML = '';
					
					var video_count = 1;
					var video_devices = devices.filter(function(device) {
						return device.kind === 'videoinput';
					});
					
					self.log('Found ' + video_devices.length + ' video input devices');
					
					video_devices.forEach(function(device) {
						var label = device.label || 'Camera ' + video_count++;
						var is_selected = self.selected_video_device === device.deviceId || 
										 (!self.selected_video_device && video_count === 2);
						
						var item_html = '<button type="button" class="mc-settings-menu-item' + 
									   (is_selected ? ' mc-active' : '') + 
									   '" role="menuitemradio" aria-checked="' + (is_selected ? 'true' : 'false') + 
									   '" data-device-id="' + self.esc_attr(device.deviceId) + 
									   '" data-device-type="video">';
						item_html += '<span class="mc-settings-menu-label">' + self.esc_html(label) + '</span>';
						item_html += '<span class="mc-settings-menu-check">' + self.config.icon_checkmark + '</span>';
						item_html += '</button>';
						
						video_devices_container.insertAdjacentHTML('beforeend', item_html);
					});
				}

				if (audio_devices_container && (mode === 'video' || mode === 'audio')) {
					// Clear existing items
					audio_devices_container.innerHTML = '';
					
					var audio_count = 1;
					var audio_devices = devices.filter(function(device) {
						return device.kind === 'audioinput';
					});
					
					self.log('Found ' + audio_devices.length + ' audio input devices');
					
					audio_devices.forEach(function(device) {
						var label = device.label || 'Microphone ' + audio_count++;
						var is_selected = self.selected_audio_device === device.deviceId || 
										 (!self.selected_audio_device && audio_count === 2);
						
						var item_html = '<button type="button" class="mc-settings-menu-item' + 
									   (is_selected ? ' mc-active' : '') + 
									   '" role="menuitemradio" aria-checked="' + (is_selected ? 'true' : 'false') + 
									   '" data-device-id="' + self.esc_attr(device.deviceId) + 
									   '" data-device-type="audio">';
						item_html += '<span class="mc-settings-menu-label">' + self.esc_html(label) + '</span>';
						item_html += '<span class="mc-settings-menu-check">' + self.config.icon_checkmark + '</span>';
						item_html += '</button>';
						
						audio_devices_container.insertAdjacentHTML('beforeend', item_html);
					});
				}
			})
			.catch(function(error) {
				self.log('Error enumerating devices: ' + error.message, 'error');
			});
	};

	/**
	 * Check if camera flip is available (multiple cameras on mobile)
	 * @private
	 */
	MediaCapture.prototype.check_camera_flip_availability = function(container) {
		var self = this;

		self.log('Checking camera flip availability');

		if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) {
			self.log('enumerateDevices not available for camera flip check', 'warn');
			return;
		}

		navigator.mediaDevices.enumerateDevices()
			.then(function(devices) {
				var video_devices = devices.filter(function(device) {
					return device.kind === 'videoinput';
				});

				self.log('Camera flip check found ' + video_devices.length + ' video devices');

				// If there are 2 or more video devices, show the flip button
				if (video_devices.length >= 2) {
					self.has_multiple_cameras = true;
					self.log('Multiple cameras detected, showing flip button');
					var flip_btn = container.querySelector('.mc-flip-btn');
					if (flip_btn) {
						flip_btn.style.display = '';
					}
					
					// Dispatch UI state event
					self.dispatch_ui_state('flip', 'on');
					
					// Dispatch event when multiple cameras are detected
					self.log('Firing cameras-detected event with count: ' + video_devices.length);
					self.dispatch_event('cameras-detected', { count: video_devices.length });
					
					// Trigger callback if provided
					if (self.config.on_cameras_detected) {
						self.config.on_cameras_detected(video_devices.length);
					}
				}
			})
			.catch(function(error) {
				// Failed to enumerate devices - fail silently
			});
	};

	/**
	 * Start microphone level monitoring
	 * @private
	 */
	MediaCapture.prototype.start_mic_monitoring = function(container, stream, callback) {
		// Constants
		var ANALYSER_SMOOTHING = 0.3;
		var ANALYSER_FFT_SIZE = 256;
		var PERCENTAGE_MAX = 100;

		try {
			var audio_context = new (window.AudioContext || window.webkitAudioContext)();
			var analyser = audio_context.createAnalyser();
			var microphone = audio_context.createMediaStreamSource(stream);

			// More responsive settings
			analyser.smoothingTimeConstant = ANALYSER_SMOOTHING;  // Less smoothing for more responsiveness
			analyser.fftSize = ANALYSER_FFT_SIZE;  // Smaller size for faster response

			microphone.connect(analyser);

			var data_array = new Uint8Array(analyser.frequencyBinCount);
			var level_bar = container.querySelector('.mc-mic-bar-fill');

			function update_level() {
				analyser.getByteTimeDomainData(data_array);  // Use time domain for better voice detection

				// Calculate RMS (Root Mean Square) for better level detection
				var sum = 0;
				for (var i = 0; i < data_array.length; i++) {
					var normalized = (data_array[i] - 128) / 128;  // Normalize to -1 to 1
					sum += normalized * normalized;
				}

				var rms = Math.sqrt(sum / data_array.length);
				var percent = Math.min(rms * 300, PERCENTAGE_MAX);  // Amplify and cap at 100%

				if (level_bar) {
					level_bar.style.width = percent + '%';
				}

				requestAnimationFrame(update_level);
			}

			update_level();

			if (callback) {
				callback(audio_context, analyser);
			}
		} catch(error) {
			// Failed to start mic monitoring - fail silently
		}
	};

	/**
	 * Slide element up (jQuery-like animation)
	 * @private
	 */
	MediaCapture.prototype.slide_up = function(element, duration) {
		if (!element) return;
		
		// Check if element has position classes (popup style)
		var is_popup = element.classList.contains('mc-position-above') || element.classList.contains('mc-position-below');
		
		if (is_popup) {
			// Use fade and scale for popup
			element.style.transition = 'opacity ' + duration + 'ms ease, transform ' + duration + 'ms ease';
			element.style.opacity = '0';
			element.style.transform = 'scale(0.95)';
			
			setTimeout(function() {
				element.style.display = 'none';
				element.style.opacity = '';
				element.style.transform = '';
				element.style.transition = '';
			}, duration);
		} else {
			// Use original height animation
			element.style.overflow = 'hidden';
			element.style.transition = 'height ' + duration + 'ms ease';
			element.style.height = element.scrollHeight + 'px';
			
			// Force reflow
			element.offsetHeight;
			
			element.style.height = '0';
			
			setTimeout(function() {
				element.style.display = 'none';
				element.style.height = '';
				element.style.overflow = '';
				element.style.transition = '';
			}, duration);
		}
	};

	/**
	 * Slide element down (jQuery-like animation)
	 * @private
	 */
	MediaCapture.prototype.slide_down = function(element, duration, callback) {
		if (!element) return;
		
		// Check if element has position classes (popup style)
		var is_popup = element.classList.contains('mc-position-above') || element.classList.contains('mc-position-below');
		
		if (is_popup) {
			// Use fade and scale for popup
			element.style.display = 'block';
			element.style.opacity = '0';
			element.style.transform = 'scale(0.95)';
			
			// Force reflow
			element.offsetHeight;
			
			element.style.transition = 'opacity ' + duration + 'ms ease, transform ' + duration + 'ms ease';
			element.style.opacity = '1';
			element.style.transform = 'scale(1)';
			
			setTimeout(function() {
				element.style.opacity = '';
				element.style.transform = '';
				element.style.transition = '';
				if (callback) callback();
			}, duration);
		} else {
			// Use original height animation
			element.style.display = 'block';
			element.style.overflow = 'hidden';
			element.style.height = '0';
			
			var target_height = element.scrollHeight;
			
			element.style.transition = 'height ' + duration + 'ms ease';
			
			// Force reflow
			element.offsetHeight;
			
			element.style.height = target_height + 'px';
			
			setTimeout(function() {
				element.style.height = '';
				element.style.overflow = '';
				element.style.transition = '';
				if (callback) callback();
			}, duration);
		}
	};

	/**
	 * Close settings panel
	 * @private
	 */
	MediaCapture.prototype.close_settings = function(container) {
		var settings_panel = container.querySelector('.mc-settings');
		var settings_btn = container.querySelector('.mc-settings-btn');
		
		if (settings_panel) {
			var is_visible = settings_panel.style.display !== 'none' && settings_panel.style.display !== '';
			if (is_visible) {
				this.settings_close_internal(container, settings_panel, settings_btn);
			}
		}
	};

	/**
	 * Start recording video from the media stream
	 * @param {MediaStream} media_stream - The media stream to record
	 * @param {HTMLElement} container - The container element
	 * @param {number} max_duration - Maximum recording duration in seconds
	 * @param {Function} recorder_callback - Callback function (recorder, timer_interval)
	 * @param {Function} data_callback - Callback function for data chunks
	 * @private
	 */
	MediaCapture.prototype.start_recording = function(media_stream, container, max_duration, recorder_callback, data_callback, mode, format) {
		var self = this;
		
		// Constants
		var TIMER_UPDATE_INTERVAL = 1000;
		var DURATION_UPDATE_INTERVAL = 100;
		var MILLISECONDS_PER_SECOND = 1000;

		// Determine MIME type based on capture mode and format
		var mime_type;
		if (mode === 'audio') {
			mime_type = this.get_supported_audio_mime_type(format);
		} else {
			// Video mode
			mime_type = this.get_supported_video_mime_type(format);
		}

		try {
			var media_recorder = new MediaRecorder(media_stream, {
				mimeType: mime_type
			});

			media_recorder.ondataavailable = function(event) {
				if (event.data && event.data.size > 0) {
					data_callback(event.data);
				}
			};
			
			// Clear any existing error handler
			media_recorder.onerror = null;
			
			// Clear any existing stop handler
			media_recorder.onstop = null;

			media_recorder.start(DURATION_UPDATE_INTERVAL);

			// Show recording timer
			var timer = container.querySelector('.mc-timer');
			if (timer) timer.style.display = '';

			// Start timer and track actual recording duration
			var seconds = 0;
			var start_time = Date.now();
			var timer_remaining_element = container.querySelector('.mc-timer-remaining');
			var has_remaining_display = timer_remaining_element !== null;
			
			var timer_interval = setInterval(function() {
				seconds++;
				var mins = Math.floor(seconds / 60);
				var secs = seconds % 60;
				
				// Update elapsed time
				var timer_elapsed = container.querySelector('.mc-timer-elapsed');
				if (timer_elapsed) {
					timer_elapsed.textContent = self.pad_zero(mins) + ':' + self.pad_zero(secs);
				}
				
				// Calculate and update remaining time (only if element exists)
				if (has_remaining_display) {
					var remaining_seconds = max_duration - seconds;
					if (remaining_seconds > 0) {
						var remaining_mins = Math.floor(remaining_seconds / 60);
						var remaining_secs = remaining_seconds % 60;
						timer_remaining_element.textContent = self.pad_zero(remaining_mins) + ':' + self.pad_zero(remaining_secs);
					} else {
						timer_remaining_element.textContent = '00:00';
					}
				}

				// Auto-stop at max duration
				if (seconds >= max_duration) {
					var record_btn = container.querySelector('.mc-record-btn');
					if (record_btn) record_btn.click();
				}
			}, TIMER_UPDATE_INTERVAL);

			// Store the recorded duration on the media_recorder object so we can access it later
			// Use milliseconds for granular precision, convert to seconds with decimals
			media_recorder.recorded_duration = 0;
			media_recorder.start_time = start_time;
			media_recorder.duration_interval = setInterval(function() {
				var elapsed_ms = Date.now() - start_time;
				media_recorder.recorded_duration = elapsed_ms / MILLISECONDS_PER_SECOND;
			}, DURATION_UPDATE_INTERVAL);

			if (recorder_callback) {
				recorder_callback(media_recorder, timer_interval);
			}

		} catch (error) {
			// Trigger error callback if provided
			if (this.config.on_error) {
				this.config.on_error(error);
			}
		}
	};

	/**
	 * Stop recording video and process the recorded data
	 * @param {MediaRecorder} media_recorder - The media recorder instance
	 * @param {number} timer_interval - The timer interval ID
	 * @param {Array} recorded_chunks - Array of recorded data chunks
	 * @param {HTMLElement} container - The container element
	 * @param {HTMLVideoElement} video_element - The video element
	 * @param {Function} callback - Callback function to execute after stopping
	 * @private
	 */
	MediaCapture.prototype.stop_recording = function(media_recorder, timer_interval, recorded_chunks, container, video_element, callback) {
		var self = this;

		if (media_recorder && media_recorder.state !== 'inactive') {
			
			// Set up the onstop handler BEFORE calling stop()
			media_recorder.onstop = function() {
				// Use the MIME type from the MediaRecorder
				var mime_type = media_recorder.mimeType || 'video/webm';
				var blob = new Blob(recorded_chunks, { type: mime_type });

				// Get the actual recorded duration from the media_recorder
				var actual_duration = media_recorder.recorded_duration || 0;

				// Clean up duration tracking interval
				if (media_recorder.duration_interval) {
					clearInterval(media_recorder.duration_interval);
					media_recorder.duration_interval = null;
				}

				// Only handle video element if it exists (not in audio mode)
				if (video_element) {
					// Revoke old blob URL if it exists to free memory
					if (video_element.src && video_element.src.startsWith('blob:')) {
						URL.revokeObjectURL(video_element.src);
					}

					var video_url = URL.createObjectURL(blob);

					// Switch video from live preview to recorded playback
					video_element.srcObject = null; // Stop using camera stream
					video_element.src = video_url;
					video_element.muted = false; // Unmute for playback
					video_element.autoplay = false; // Don't autoplay recorded video
					video_element.controls = false; // Use custom controls
				}
				
				// Handle audio element if it exists (audio mode)
				if (!video_element && self.audio_element) {
					// Revoke old blob URL if it exists to free memory
					if (self.audio_element.src && self.audio_element.src.startsWith('blob:')) {
						URL.revokeObjectURL(self.audio_element.src);
					}

					var audio_url = URL.createObjectURL(blob);

					// Switch audio from live recording to recorded playback
					self.audio_element.srcObject = null; // Stop using microphone stream
					self.audio_element.src = audio_url;
					self.audio_element.autoplay = false; // Don't autoplay recorded audio
					self.audio_element.controls = false; // Use custom controls
				}

				// Set the duration display immediately using the actual recorded duration
				if (actual_duration > 0) {
					var duration_text = self.format_time(actual_duration);
					var time_duration = container.querySelector('.mc-time-duration');
					if (time_duration) time_duration.textContent = duration_text;

					// Store the actual duration on the media element for scrubbing (only if media element exists)
					if (video_element) {
						video_element.setAttribute('data-actual-duration', actual_duration.toString());
					} else if (self.audio_element) {
						self.audio_element.setAttribute('data-actual-duration', actual_duration.toString());
					}
				}

				// Force media to load metadata (only if media element exists)
				if (video_element) {
					video_element.load();
				} else if (self.audio_element) {
					self.audio_element.load();
				}

				// Store blob and filename
				var filename = 'video-' + Date.now() + '.webm';
				self.blob = blob;
				self.filename = filename;

				// Wait for video to load metadata before getting dimensions
				var set_metadata = function() {
					// Get actual video dimensions (only if video element exists)
					var video_width = null;
					var video_height = null;
					
					if (video_element) {
						video_width = video_element.videoWidth || null;
						video_height = video_element.videoHeight || null;
					}
					
					// If video element doesn't have dimensions, try to get from configured settings
					if (!video_width && self.video_width) {
						video_width = self.video_width;
					}
					if (!video_height && self.video_height) {
						video_height = self.video_height;
					}

					// Create metadata JSON
					var metadata = {
						type: 'video',
						width: video_width,
						height: video_height,
						duration: actual_duration,
						size: blob.size,
						format: 'webm',
						timestamp: Date.now()
					};

					// Trigger callback with blob and filename
					if (self.config.on_video_captured) {
						self.config.on_video_captured(blob, filename, metadata);
					}

					// Show download option in settings if enabled
					self.show_download_option(container);
				};

				// Try to get dimensions immediately (might work if already loaded)
				if (video_element && video_element.videoWidth && video_element.videoHeight) {
					set_metadata();
				} else if (video_element) {
					// Otherwise wait for loadedmetadata event
					video_element.addEventListener('loadedmetadata', set_metadata, { once: true });
				} else {
					// No video element, call set_metadata directly (audio mode)
					set_metadata();
				}

				// Switch from recording controls to playback controls
				var overlay_controls = container.querySelector('.mc-capture-controls');
				var playback_controls = container.querySelector('.mc-result-controls');
				var settings_panel = container.querySelector('.mc-settings');
				
				if (overlay_controls) overlay_controls.style.display = 'none';
				if (playback_controls) playback_controls.style.display = '';
				if (settings_panel) settings_panel.style.display = 'none';

				// Move focus to play button for keyboard users
				var play_btn = container.querySelector('.mc-play-btn');
				if (play_btn) play_btn.focus();

				// Clear the event handlers to prevent memory leaks
				media_recorder.ondataavailable = null;
				media_recorder.onstop = null;
				media_recorder.onerror = null;

				if (callback) {
					callback();
				}
			};

			media_recorder.stop();

			// Stop timer and hide it
			clearInterval(timer_interval);
			var timer = container.querySelector('.mc-timer');
			if (timer) timer.style.display = 'none';
			
			var timer_elapsed = container.querySelector('.mc-timer-elapsed');
			if (timer_elapsed) timer_elapsed.textContent = '00:00';
			
			// Reset remaining time if it exists
			var timer_remaining = container.querySelector('.mc-timer-remaining');
			if (timer_remaining) {
				var initial_mins = Math.floor(self.config.max_duration / 60);
				var initial_secs = self.config.max_duration % 60;
				timer_remaining.textContent = self.pad_zero(initial_mins) + ':' + self.pad_zero(initial_secs);
			}

			// Stop camera stream
			self.stream_stop_internal(video_element);
		}
	};

	/**
	 * Start the camera stream
	 * @param {HTMLElement} container - The container element
	 * @param {HTMLVideoElement} video_element - The video element
	 * @param {string} facing_mode - Camera facing mode ('user' or 'environment')
	 * @param {number} video_width - Desired video width (optional)
	 * @param {number} video_height - Desired video height (optional)
	 * @param {string} mode - Capture mode ('photo' or 'video')
	 * @param {string} video_device_id - Specific video device ID (optional)
	 * @param {string} audio_device_id - Specific audio device ID (optional)
	 * @param {Function} callback - Success callback function
	 * @param {Function} error_callback - Error callback function
	 * @private
	 */
	MediaCapture.prototype.stream_start_internal = function(container, video_element, facing_mode, video_width, video_height, mode, video_device_id, audio_device_id, callback, error_callback) {
		var self = this;
		
		// Track getUserMedia calls
		if (!window._mc_get_user_media_count) {
			window._mc_get_user_media_count = 0;
		}
		window._mc_get_user_media_count++;
		self.log('[stream_start_internal] getUserMedia call #' + window._mc_get_user_media_count);
		self.log('[stream_start_internal] Called with mode:', mode);
		
		var video_constraints = {};

		// Only set width/height if they are provided (not empty/blank)
		if (video_width && video_height) {
			video_constraints.width = { ideal: parseInt(video_width, 10) };
			video_constraints.height = { ideal: parseInt(video_height, 10) };
		}

		// Use specific device if selected, otherwise use facing mode
		if (video_device_id) {
			video_constraints.deviceId = { exact: video_device_id };
		} else if (facing_mode) {
			video_constraints.facingMode = facing_mode;
		}

		var constraints = {
			video: mode === 'audio' ? false : video_constraints,
			audio: mode === 'video' || mode === 'audio'
		};

		// Use specific audio device if selected (for video or audio mode)
		if ((mode === 'video' || mode === 'audio') && audio_device_id) {
			constraints.audio = { deviceId: { exact: audio_device_id } };
		}

		self.log('[stream_start_internal] About to call getUserMedia with constraints:', JSON.stringify(constraints));

		navigator.mediaDevices.getUserMedia(constraints)
			.then(function(stream) {
				self.log('[stream_start_internal] getUserMedia SUCCESS, stream:', stream);
				self.log('[stream_start_internal] Stream tracks:', stream.getTracks().length);
				
				// Reset recording control UI states immediately after stream starts
				// This ensures pause/resume buttons are properly disabled
				if (mode === 'video' || mode === 'audio') {
					self.dispatch_ui_state('pause', 'off');
					self.dispatch_ui_state('resume', 'off');
				}
				
				// Only set srcObject for video element if it exists (not in audio mode)
				if (video_element) {
					video_element.srcObject = stream;
					// CRITICAL: Ensure video preview is muted to prevent audio feedback
					video_element.muted = true;
				}

				var preview_container = container.querySelector('.mc-capture');
				if (preview_container) preview_container.style.display = '';

				// Trigger callback if provided
				if (self.config.on_stream_started) {
					self.config.on_stream_started(stream);
				}

				// Auto-start QR scanning in photo mode if enabled
				if (mode === 'photo' && self.config.qr_scan_enabled) {
					self.qr_scan_start();
				}

				if (callback) {
					callback(stream);
				}
			})
			.catch(function(error) {
				self.log('[stream_start_internal] getUserMedia FAILED:', error, 'error');
				self.log('[stream_start_internal] Error name:', error.name, 'error');
				self.log('[stream_start_internal] Error message:', error.message, 'error');
				
				// Trigger error callback if provided
				if (self.config.on_error) {
					self.config.on_error(error);
				}

				if (error_callback) {
					error_callback(error);
				}
			});
	};

	/**
	 * Stop the camera stream
	 * @param {HTMLVideoElement} video_element - The video element (can be null for audio mode)
	 * @private
	 */
	MediaCapture.prototype.stream_stop_internal = function(video_element) {
		var self = this;
		
		self.log('[stream_stop_internal] Called');
		self.log('[stream_stop_internal] video_element:', video_element);
		self.log('[stream_stop_internal] video_element.srcObject:', video_element ? video_element.srcObject : 'N/A');
		
		if (video_element && video_element.srcObject) {
			var tracks = video_element.srcObject.getTracks();
			self.log('[stream_stop_internal] Stopping ' + tracks.length + ' tracks');
			
			tracks.forEach(function(track) {
				self.log('[stream_stop_internal] Stopping track:', track.kind, track.label, 'state:', track.readyState);
				track.stop();
			});
			video_element.srcObject = null;
			
			self.log('[stream_stop_internal] Tracks stopped and srcObject cleared');

			// Stop QR scanning if active
			if (self.qr_scan_active) {
				self.qr_scan_stop();
			}
			
			// Trigger callback if provided
			if (self.config.on_stream_stopped) {
				self.config.on_stream_stopped();
			}
		} else {
			self.log('[stream_stop_internal] No video element or srcObject to stop');
		}
	};

	/**
	 * Countdown timer with visual and audio feedback
	 * @param {HTMLElement} countdown_overlay - The countdown overlay element
	 * @param {number} countdown_duration - Duration in seconds
	 * @param {boolean} sound_enabled - Whether to play beep sounds
	 * @param {number} beep_frequency - Frequency of beep sound in Hz
	 * @param {AudioContext} beep_audio_context - Audio context for beeps
	 * @param {Function} callback - Callback when countdown completes
	 * @private
	 */
	MediaCapture.prototype.countdown = function(countdown_overlay, countdown_duration, sound_enabled, beep_frequency, beep_audio_context, countdown_sound_url, callback) {
		var count = countdown_duration;
		var countdown_number = countdown_overlay.querySelector('.mc-countdown-number');
		var countdown_sr = countdown_overlay.querySelector('.mc-countdown-sr');

		// Number to word mapping for screen readers
		var number_words = {
			1: 'one',
			2: 'two',
			3: 'three',
			4: 'four',
			5: 'five',
			6: 'six',
			7: 'seven',
			8: 'eight',
			9: 'nine',
			10: 'ten'
		};

		// Play beep function
		var play_beep = function() {
			if (!sound_enabled) { return; }

			// If custom countdown sound URL is provided, use it
			if (countdown_sound_url) {
				try {
					var audio = new Audio(countdown_sound_url);
					audio.volume = 0.5; // 50% volume
					audio.play().catch(function(error) {
						// Error playing sound - fail silently
					});
				} catch(e) {
					// Error creating audio - fail silently
				}
				return;
			}

			// Otherwise use synthetic beep
			if (!beep_audio_context) { return; }

			try {
				var oscillator = beep_audio_context.createOscillator();
				var gain_node = beep_audio_context.createGain();

				oscillator.connect(gain_node);
				gain_node.connect(beep_audio_context.destination);

				oscillator.frequency.value = beep_frequency;
				oscillator.type = 'sine';

				gain_node.gain.setValueAtTime(0.3, beep_audio_context.currentTime);
				gain_node.gain.exponentialRampToValueAtTime(0.01, beep_audio_context.currentTime + 0.2);

				oscillator.start(beep_audio_context.currentTime);
				oscillator.stop(beep_audio_context.currentTime + 0.2);
			} catch(e) {
				// Error playing beep - fail silently
			}
		};

		// Initial announcement and beep (but not on the last second)
		if (countdown_number) countdown_number.textContent = count;
		if (countdown_sr) countdown_sr.textContent = number_words[count] || count;
		if (countdown_overlay) countdown_overlay.style.display = '';

		// Play beep for initial count if it's not 1
		if (count > 0) {
			play_beep();
		}

		var countdown_interval = setInterval(function() {
			count--;
			if (count > 0) {
				if (countdown_number) countdown_number.textContent = count;
				if (countdown_sr) countdown_sr.textContent = number_words[count] || count;

				// Play beep only if count is greater than 1 (skip the last second)
				if (count > 0) {
					play_beep();
				}
			} else {
				// Clear visual and announce completion
				if (countdown_number) countdown_number.textContent = '';
				if (countdown_sr) countdown_sr.textContent = 'Capturing now';

				// Clean up after a brief delay to ensure announcement
				setTimeout(function() {
					if (countdown_sr) countdown_sr.textContent = '';
					clearInterval(countdown_interval);
					if (countdown_overlay) countdown_overlay.style.display = 'none';
					if (callback) {
						callback();
					}
				}, 100);
			}
		}, 1000);
	};

	/**
	 * Play camera shutter sound effect
	 * @param {boolean} photo_sound_enabled - Whether sound is enabled
	 * @private
	 */
	MediaCapture.prototype.play_photo_sound = function(photo_sound_enabled) {
		// Only play if enabled
		if (!photo_sound_enabled) { return; }
		
		try {
			// Check if photo_sound_url is configured
			var photo_sound_url = this.config.photo_sound_url || '';
			
			if (photo_sound_url) {
				// Use external MP3 file
				var audio = new Audio(photo_sound_url);
				audio.volume = 0.5; // 50% volume
				audio.play().catch(function(error) {
					// Error playing sound - fail silently
				});
			} else {
				// Fallback: generate synthetic shutter sound using Web Audio API
				var audio_context = new (window.AudioContext || window.webkitAudioContext)();
				
				// Create a short "click" sound (simulates mechanical shutter)
				var oscillator = audio_context.createOscillator();
				var gain_node = audio_context.createGain();
				
				oscillator.connect(gain_node);
				gain_node.connect(audio_context.destination);
				
				// High frequency click
				oscillator.frequency.value = 1200;
				oscillator.type = 'sine';
				
				// Quick envelope for sharp click
				var now = audio_context.currentTime;
				gain_node.gain.setValueAtTime(0.3, now);
				gain_node.gain.exponentialRampToValueAtTime(0.01, now + 0.05);
				
				oscillator.start(now);
				oscillator.stop(now + 0.05);
			}
			
		} catch(e) {
			// Error playing photo sound - fail silently
		}
	};

	/**
	 * Capture a photo from the video stream
	 * @param {HTMLVideoElement} video_element - The video element
	 * @param {HTMLCanvasElement} canvas_element - The canvas element
	 * @param {HTMLImageElement} result_photo - The result image element
	 * @param {HTMLElement} container - The container element
	 * @param {HTMLElement} result_container - The result container element
	 * @param {number} photo_quality - Image quality (0.1 to 1.0)
	 * @param {string} photo_format - Image format ('jpeg', 'png', or 'webp')
	 * @private
	 */
	MediaCapture.prototype.photo_capture_internal = function(video_element, canvas_element, result_photo, container, result_container, photo_quality, photo_format) {
		var self = this;

		// Play photo sound if enabled
		this.play_photo_sound(this.config.photo_sound_enabled);

		// Stop QR scanning if active (video is about to be hidden)
		if (this.qr_scan_active) {
			this.qr_scan_stop();
		}

		canvas_element.width = video_element.videoWidth;
		canvas_element.height = video_element.videoHeight;

		var context = canvas_element.getContext('2d');
		context.drawImage(video_element, 0, 0);

		var mime_type = this.get_supported_photo_mime_type(photo_format);

		// Convert canvas to blob with specified mime type and quality
		canvas_element.toBlob(function(blob) {
			// Create object URL for preview
			var object_url = URL.createObjectURL(blob);
			result_photo.src = object_url;

			// Store blob and filename
			var filename = 'photo-' + Date.now() + '.' + photo_format;
			self.blob = blob;
			self.filename = filename;

			// Create metadata JSON
			var metadata = {
				type: 'photo',
				width: canvas_element.width,
				height: canvas_element.height,
				size: blob.size,
				format: photo_format,
				quality: photo_quality,
				timestamp: Date.now()
			};

			// Trigger callback with blob and filename
			if (self.config.on_photo_captured) {
				self.config.on_photo_captured(blob, filename, metadata);
			}

			// Show download option in settings if enabled
			self.show_download_option(container);

			// Show result container and hide video/controls, show player/result controls
			var preview_container = container.querySelector('.mc-capture');
			var settings_panel = container.querySelector('.mc-settings');
			var video_element_dom = container.querySelector('.mc-video');
			var player_element = container.querySelector('.mc-result');
			var overlay_controls = container.querySelector('.mc-capture-controls');
			
			if (preview_container) preview_container.style.display = '';
			if (settings_panel) settings_panel.style.display = 'none';
			if (video_element_dom) video_element_dom.style.display = 'none';
			if (player_element) player_element.style.display = '';
			if (overlay_controls) overlay_controls.style.display = 'none';

			// Hide flip button when viewing result
			var flip_btn = container.querySelector('.mc-flip-btn');
			if (flip_btn) flip_btn.style.display = 'none';

			// Dispatch UI state event
			self.dispatch_ui_state('flip', 'off');

			// Dispatch UI state events for photo buttons
			self.dispatch_ui_state('capture', 'off');
			self.dispatch_ui_state('retake', 'on');
			self.dispatch_ui_state('cancel', 'off');

			// Dispatch result-show event
			self.dispatch_event('result-show');

			// Trigger callback if provided
			if (self.config.on_result_show) {
				self.config.on_result_show();
			}

			// Trigger flash effect in result container - start at full white
			var flash_overlay = preview_container.querySelector('.mc-flash');
			if (flash_overlay) {
				// Make sure it starts at full opacity immediately
				flash_overlay.classList.add('mc-flash-active');
				// Use a tiny delay to ensure the DOM is updated before starting fade
				setTimeout(function() {
					flash_overlay.classList.remove('mc-flash-active');
				}, 50);
			}

			// Move focus to retake button for keyboard users
			var retake_btn = container.querySelector('.mc-retake-btn');
			if (retake_btn) retake_btn.focus();
			
			// Announce to screen readers
			self.announce_to_screen_reader('Photo captured');

		}, mime_type, photo_quality);
	};

	/**
	 * Retake a photo (internal method)
	 * @param {HTMLElement} container - The container element
	 * @param {HTMLElement} result_container - The result container element
	 * @param {HTMLElement} preview_container - The preview container element
	 * @private
	 */
	MediaCapture.prototype.photo_retake_internal = function(container) {
		var self = this;

		// Backup the current blob and filename before clearing (for cancel functionality)
		self.blob_backup = self.blob;
		self.filename_backup = self.filename;

		self.blob = null;
		self.filename = null;

		// Hide download option
		self.hide_download_option(container);

		// Hide result and show preview container
		var video_element_dom = container.querySelector('.mc-video');
		var player_element = container.querySelector('.mc-result');
		var overlay_controls = container.querySelector('.mc-capture-controls');
		
		if (video_element_dom) video_element_dom.style.display = '';
		if (player_element) player_element.style.display = 'none';
		if (overlay_controls) overlay_controls.style.display = '';

		// Dispatch result-hide event
		self.dispatch_event('result-hide');

		// Trigger callback if provided
		if (self.config.on_result_hide) {
			self.config.on_result_hide();
		}

		// Show cancel button
		var cancel_btn = container.querySelector('.mc-cancel-btn');
		if (cancel_btn) cancel_btn.style.display = '';

		// Show flip button again if multiple cameras available
		if (self.has_multiple_cameras) {
			var flip_btn = container.querySelector('.mc-flip-btn');
			if (flip_btn) flip_btn.style.display = '';
			
			// Dispatch UI state event
			self.dispatch_ui_state('flip', 'on');
		}

		// Dispatch UI state events for photo buttons
		self.dispatch_ui_state('capture', 'on');
		self.dispatch_ui_state('retake', 'off');
		self.dispatch_ui_state('cancel', 'on');

		// Hide settings panel
		var settings_panel = container.querySelector('.mc-settings');
		if (settings_panel) settings_panel.style.display = 'none';

		// Dispatch custom event
		self.dispatch_event('photo-retake');

		// Trigger callback if provided
		if (self.config.on_photo_retake) {
			self.config.on_photo_retake();
		}

		// Resume QR scanning if enabled (stream is still active after retake)
		if (self.config.qr_scan_enabled && !self.qr_scan_active && self.stream && self.stream.active) {
			self.qr_scan_start();
		}
	};

	/**
	 * Cancel photo retake (internal method)
	 * @param {HTMLElement} container - The container element
	 * @private
	 */
	MediaCapture.prototype.photo_cancel_internal = function(container) {
		var self = this;

		// Restore the backed up blob and filename
		self.blob = self.blob_backup;
		self.filename = self.filename_backup;

		// Restore the photo in the result image
		var result_photo = container.querySelector('.mc-photo');
		if (result_photo && self.blob) {
			var object_url = URL.createObjectURL(self.blob);
			result_photo.src = object_url;
		}

		// Show result container, hide preview container
		var preview_container = container.querySelector('.mc-preview');
		if (preview_container) preview_container.style.display = 'none';

		// Dispatch result-show event
		self.dispatch_event('result-show');

		// Trigger callback if provided
		if (self.config.on_result_show) {
			self.config.on_result_show();
		}

		// Hide cancel button
		var cancel_btn = container.querySelector('.mc-cancel-btn');
		if (cancel_btn) cancel_btn.style.display = 'none';

		// Hide flip button when viewing result
		var flip_btn = container.querySelector('.mc-flip-btn');
		if (flip_btn) flip_btn.style.display = 'none';

		// Dispatch UI state event
		self.dispatch_ui_state('flip', 'off');

		// Dispatch UI state events for photo buttons
		self.dispatch_ui_state('capture', 'off');
		self.dispatch_ui_state('retake', 'on');
		self.dispatch_ui_state('cancel', 'off');

		// Clear backup data
		self.blob_backup = null;
		self.filename_backup = null;

		// Dispatch custom event
		self.dispatch_event('photo-cancel');

		// Trigger callback if provided
		if (self.config.on_photo_cancel) {
			self.config.on_photo_cancel();
		}

		// Move focus to retake button for keyboard users
		var retake_btn = container.querySelector('.mc-retake-btn');
		if (retake_btn) retake_btn.focus();
	};

	/**
	 * Start video recording (internal method)
	 * @param {HTMLElement} container - The container element
	 * @param {MediaStream} stream - The media stream
	 * @param {number} max_duration - Maximum video duration in seconds
	 * @private
	 */
	MediaCapture.prototype.video_start_internal = function(container, stream, max_duration) {
		var self = this;
		
		// Mark as recording
		self.is_recording = true;
		self.recorded_chunks = [];

		// Start the recording
		self.start_recording(
			stream,
			container,
			max_duration,
			function(recorder, interval) {
				self.media_recorder = recorder;
				self.timer_interval = interval;
			},
			function(chunk) {
				self.recorded_chunks.push(chunk);
			},
			'video',
			self.config.video_format
		);

		// Update record button to stop state
		var record_btn = container.querySelector('.mc-record-btn');
		if (record_btn) {
			record_btn.innerHTML = self.config.icon_stop;
			record_btn.setAttribute('aria-label', self.config.label_stop);
			record_btn.setAttribute('title', self.config.label_stop);
			record_btn.classList.add('mc-recording');
		}

		// Show pause button
		var pause_btn = container.querySelector('.mc-pause-btn');
		if (pause_btn) pause_btn.style.display = '';

		// Hide flip button during recording
		var flip_btn = container.querySelector('.mc-flip-btn');
		if (flip_btn) flip_btn.style.display = 'none';

		// Dispatch UI state events
		self.dispatch_ui_state('flip', 'off');
		self.dispatch_ui_state('start', 'off');
		self.dispatch_ui_state('stop', 'on');
		self.dispatch_ui_state('pause', 'on');

		// Dispatch custom event
		self.dispatch_event('video-start');

		// Trigger callback if provided
		if (self.config.on_video_started) {
			self.config.on_video_started();
		}
		
		// Announce to screen readers
		self.announce_to_screen_reader('Recording started');
	};

	/**
	 * Stop video recording (internal method)
	 * @param {HTMLElement} container - The container element
	 * @param {HTMLVideoElement} video_element - The video element
	 * @param {Function} callback - Callback function when stop completes
	 * @private
	 */
	MediaCapture.prototype.video_stop_internal = function(container, video_element, callback) {
		var self = this;

		self.stop_recording(
			self.media_recorder,
			self.timer_interval,
			self.recorded_chunks,
			container,
			video_element,
			function() {
				self.media_recorder = null;
				self.timer_interval = null;
				self.is_recording = false;
				self.stream = null;

				// Hide and reset pause button
				var pause_btn = container.querySelector('.mc-pause-btn');
				if (pause_btn) {
					pause_btn.style.display = 'none';
					pause_btn.classList.remove('mc-paused');
					pause_btn.innerHTML = self.config.icon_pause;
					pause_btn.setAttribute('aria-label', self.config.label_pause);
					pause_btn.setAttribute('title', self.config.label_pause);
				}

				// Remove paused class from timer
				var timer = container.querySelector('.mc-timer');
				if (timer) timer.classList.remove('mc-paused');
				
				// Re-enable all settings buttons after recording stops
				var settings_btns = container.querySelectorAll('.mc-settings-btn');
				settings_btns.forEach(function(btn) {
					btn.disabled = false;
				});

				// Dispatch UI state events
				self.dispatch_ui_state('start', 'off');
				self.dispatch_ui_state('stop', 'off');
				self.dispatch_ui_state('pause', 'off');
				self.dispatch_ui_state('resume', 'off');
				self.dispatch_ui_state('retake', 'on');
				self.dispatch_ui_state('cancel', 'off');
				self.dispatch_ui_state('playback-play', 'on');
				self.dispatch_ui_state('playback-pause', 'off');
				self.dispatch_ui_state('playback-toggle', 'on');
				self.dispatch_ui_state('playback-stop', 'off');
				self.dispatch_ui_state('settings-open', 'on');
				self.dispatch_ui_state('settings-close', 'off');

				// Dispatch custom event
				self.dispatch_event('video-stop', {
					blob: self.blob,
					filename: self.filename,
					duration: self.media_recorder ? self.media_recorder.recorded_duration : 0
				});

				// Trigger callback if provided
				if (self.config.on_video_stopped) {
					self.config.on_video_stopped(self.blob, self.filename);
				}
				
				// Announce to screen readers
				self.announce_to_screen_reader('Recording stopped');

				if (callback) {
					callback();
				}
			}
		);
	};

	/**
	 * Pause video recording (internal method)
	 * @param {HTMLElement} container - The container element
	 * @private
	 */
	MediaCapture.prototype.video_pause_internal = function(container) {
		var self = this;
		var pause_btn = container.querySelector('.mc-pause-btn');
		var timer = container.querySelector('.mc-timer');

		// Pause recording
		self.media_recorder.pause();

		// Pause the timer intervals
		if (self.timer_interval) {
			clearInterval(self.timer_interval);
			self.timer_interval = null;
		}
		if (self.media_recorder.duration_interval) {
			clearInterval(self.media_recorder.duration_interval);
			self.media_recorder.duration_interval = null;
		}

		// Store the paused time for resume
		self.media_recorder.paused_time = Date.now();

		// Update button to resume/play state
		if (pause_btn) {
			pause_btn.innerHTML = self.config.icon_play;
			pause_btn.setAttribute('aria-label', self.config.label_resume);
			pause_btn.setAttribute('title', self.config.label_resume);
			pause_btn.classList.add('mc-paused');
		}

		// Add paused class to timer for visual feedback
		if (timer) timer.classList.add('mc-paused');

		// Dispatch UI state events
		self.dispatch_ui_state('pause', 'off');
		self.dispatch_ui_state('resume', 'on');

		// Dispatch custom event
		self.dispatch_event('video-pause');

		// Trigger callback if provided
		if (self.config.on_video_paused) {
			self.config.on_video_paused();
		}
		
		// Announce to screen readers
		self.announce_to_screen_reader('Recording paused');
	};

	/**
	 * Resume video recording (internal method)
	 * @param {HTMLElement} container - The container element
	 * @private
	 */
	MediaCapture.prototype.video_resume_internal = function(container) {
		var self = this;
		var pause_btn = container.querySelector('.mc-pause-btn');
		var timer = container.querySelector('.mc-timer');
		
		var TIMER_UPDATE_INTERVAL = 1000;
		var DURATION_UPDATE_INTERVAL = 100;
		var MILLISECONDS_PER_SECOND = 1000;
		var SELECTORS = { RECORD_BTN: '.mc-record-btn' };

		// Resume recording
		self.media_recorder.resume();

		// Calculate time elapsed during pause and adjust start_time
		var pause_duration = Date.now() - self.media_recorder.paused_time;
		self.media_recorder.start_time += pause_duration;

		// Resume the timer display
		var current_seconds = Math.floor((Date.now() - self.media_recorder.start_time) / 1000);
		var timer_remaining_element = container.querySelector('.mc-timer-remaining');
		var has_remaining_display = timer_remaining_element !== null;
		var max_duration = self.config.max_duration;

		self.timer_interval = setInterval(function() {
			// Safety check: if media_recorder is null, clear interval and stop
			if (!self.media_recorder) {
				clearInterval(self.timer_interval);
				return;
			}

			current_seconds++;
			var mins = Math.floor(current_seconds / 60);
			var secs = current_seconds % 60;

			// Update elapsed time only
			var timer_elapsed = container.querySelector('.mc-timer-elapsed');
			if (timer_elapsed) {
				timer_elapsed.textContent = self.pad_zero(mins) + ':' + self.pad_zero(secs);
			}

			// Update remaining time if element exists
			if (has_remaining_display) {
				var remaining_seconds = max_duration - current_seconds;
				if (remaining_seconds > 0) {
					var remaining_mins = Math.floor(remaining_seconds / 60);
					var remaining_secs = remaining_seconds % 60;
					timer_remaining_element.textContent = self.pad_zero(remaining_mins) + ':' + self.pad_zero(remaining_secs);
				} else {
					timer_remaining_element.textContent = '00:00';
				}
			}

			// Auto-stop at max duration
			if (current_seconds >= max_duration) {
				var record_btn = container.querySelector(SELECTORS.RECORD_BTN);
				if (record_btn) record_btn.click();
			}
		}, TIMER_UPDATE_INTERVAL);

		// Resume duration tracking
		var duration_interval = setInterval(function() {
			// Safety check: if media_recorder is null, clear interval and stop
			if (!self.media_recorder) {
				clearInterval(duration_interval);
				return;
			}

			var elapsed_ms = Date.now() - self.media_recorder.start_time;
			self.media_recorder.recorded_duration = elapsed_ms / MILLISECONDS_PER_SECOND;
		}, DURATION_UPDATE_INTERVAL);

		self.media_recorder.duration_interval = duration_interval;

		// Update button back to pause state
		if (pause_btn) {
			pause_btn.innerHTML = self.config.icon_pause;
			pause_btn.setAttribute('aria-label', self.config.label_pause);
			pause_btn.setAttribute('title', self.config.label_pause);
			pause_btn.classList.remove('mc-paused');
		}

		// Remove paused class from timer
		if (timer) timer.classList.remove('mc-paused');

		// Dispatch UI state events
		self.dispatch_ui_state('pause', 'on');
		self.dispatch_ui_state('resume', 'off');

		// Dispatch custom event
		self.dispatch_event('video-resume');

		// Trigger callback if provided
		if (self.config.on_video_resumed) {
			self.config.on_video_resumed();
		}
		
		// Announce to screen readers
		self.announce_to_screen_reader('Recording resumed');
	};

	/**
	 * Retake video (internal method)
	 * @param {HTMLElement} container - The container element
	 * @param {HTMLElement} result_container - The result container element
	 * @param {HTMLElement} preview_container - The preview container element
	 * @private
	 */
	MediaCapture.prototype.video_retake_internal = function(container, result_container, preview_container) {
		var self = this;
		
		self.log('[video_retake_internal] Starting retake');
		self.log('[video_retake_internal] Current stream:', self.stream);
		self.log('[video_retake_internal] Current media_recorder:', self.media_recorder);
		self.log('[video_retake_internal] is_recording:', self.is_recording);

		// Backup the current blob, filename, and video URL before clearing (for cancel functionality)
		self.blob_backup = self.blob;
		self.filename_backup = self.filename;
		
		// Backup the current video URL
		var video_element = self.video_element;
		self.video_url_backup = video_element.src;

		self.blob = null;
		self.filename = null;

		// Hide result and show preview container
		if (preview_container) preview_container.style.display = 'none';
		if (preview_container) preview_container.style.display = '';

		// Dispatch result-hide event
		self.dispatch_event('result-hide');

		// Trigger callback if provided
		if (self.config.on_result_hide) {
			self.config.on_result_hide();
		}

		// Show cancel button
		var cancel_btn = container.querySelector('.mc-cancel-btn');
		if (cancel_btn) cancel_btn.style.display = '';

		// Reset video element back to live preview mode
		video_element.src = '';
		video_element.srcObject = null;
		video_element.muted = true;
		video_element.autoplay = true;
		video_element.controls = false;

		// Hide playback controls (result controls)
		var playback_controls = container.querySelector('.mc-result-controls');
		if (playback_controls) {
			playback_controls.style.display = 'none';
			playback_controls.classList.remove('mc-controls-visible');
		}

		// Show overlay controls
		var overlay_controls = container.querySelector('.mc-capture-controls');
		if (overlay_controls) {
			overlay_controls.style.display = '';
		}

		// Reset and show record button
		var record_btn = container.querySelector('.mc-record-btn');
		if (record_btn) {
			record_btn.innerHTML = self.config.icon_record;
			record_btn.setAttribute('aria-label', self.config.label_start_recording);
			record_btn.setAttribute('title', self.config.label_start_recording);
			record_btn.classList.remove('mc-recording');
			record_btn.style.display = '';
		}

		self.log('[video_retake_internal] Calling stream_start_internal');

		// Restart the camera stream for re-recording
		self.stream_start_internal(
			container,
			video_element,
			self.config.facing_mode,
			self.video_width,
			self.video_height,
			self.config.mode,
			self.selected_video_device,
			self.selected_audio_device,
			function(stream) {
				self.log('[video_retake_internal] stream_start_internal SUCCESS, stream:', stream);
				self.stream = stream;
				
				// Start mic monitoring for video mode
				self.start_mic_monitoring(container, stream, function(context, analyser_node) {
					// Store references if needed
				});
				
				// Show controls for overlay
				self.show_controls();
				
				// Ensure playback controls remain hidden (show_controls adds the class to all)
				var pb_controls = container.querySelector('.mc-result-controls');
				if (pb_controls) {
					pb_controls.style.display = 'none';
					pb_controls.classList.remove('mc-controls-visible');
				}
				
				// Show flip button again if multiple cameras available
				if (self.has_multiple_cameras) {
					var flip_btn = container.querySelector('.mc-flip-btn');
					if (flip_btn) flip_btn.style.display = '';
					
					// Dispatch UI state event
					self.dispatch_ui_state('flip', 'on');
				}

				// Dispatch UI state events for video buttons
				self.dispatch_ui_state('start', 'on');
				self.dispatch_ui_state('stop', 'off');
				self.dispatch_ui_state('pause', 'off');
				self.dispatch_ui_state('resume', 'off');
				self.dispatch_ui_state('retake', 'off');
				self.dispatch_ui_state('cancel', 'on');
				self.dispatch_ui_state('playback-play', 'off');
				self.dispatch_ui_state('playback-pause', 'off');
				self.dispatch_ui_state('playback-toggle', 'off');
				self.dispatch_ui_state('playback-stop', 'off');
				
				self.log('[video_retake_internal] Retake complete, ready to record');
			},
			function(error) {
				self.log('[video_retake_internal] stream_start_internal FAILED:', error, 'error');
			}
		);

		// Hide settings panel
		var settings_panel = container.querySelector('.mc-settings');
		if (settings_panel) settings_panel.style.display = 'none';

		// Dispatch custom event
		self.dispatch_event('video-retake');

		// Trigger callback if provided
		if (self.config.on_video_retake) {
			self.config.on_video_retake();
		}
	};

	/**
	 * Cancel video retake (internal method)
	 * @param {HTMLElement} container - The container element
	 * @param {HTMLElement} result_container - The result container element
	 * @param {HTMLElement} preview_container - The preview container element
	 * @private
	 */
	MediaCapture.prototype.video_cancel_internal = function(container) {
		var self = this;
		
		self.log('[video_cancel_internal] Starting cancel');
		self.log('[video_cancel_internal] Current stream:', self.stream);
		self.log('[video_cancel_internal] Current media_recorder:', self.media_recorder);
		self.log('[video_cancel_internal] is_recording:', self.is_recording);

		// Stop the camera stream
		self.log('[video_cancel_internal] Stopping stream');
		self.stream_stop_internal(self.video_element);
		self.stream = null;
		
		// CRITICAL: Reset recording state when canceling
		self.is_recording = false;
		self.media_recorder = null;
		self.timer_interval = null;

		// Restore the backed up blob and filename
		self.blob = self.blob_backup;
		self.filename = self.filename_backup;
		
		self.log('[video_cancel_internal] Restored blob:', self.blob);
		self.log('[video_cancel_internal] Restored video URL:', self.video_url_backup);
		self.log('[video_cancel_internal] Reset is_recording to:', self.is_recording);

		// Restore the video in the video element (stays in preview container)
		var video_element = self.video_element;
		if (video_element && self.video_url_backup) {
			video_element.autoplay = false; // Don't autoplay when restoring previous recording
			video_element.src = self.video_url_backup;
			video_element.load();
		}

		// For video mode, keep preview container visible (video element is there)
		// Just switch from overlay controls to playback controls
		var overlay_controls = container.querySelector('.mc-capture-controls');
		var playback_controls = container.querySelector('.mc-result-controls');
		
		if (overlay_controls) overlay_controls.style.display = 'none';
		if (playback_controls) playback_controls.style.display = '';
		
		var preview_container = container.querySelector('.mc-preview');
		if (preview_container) preview_container.style.display = '';

		// Dispatch result-show event
		self.dispatch_event('result-show');

		// Trigger callback if provided
		if (self.config.on_result_show) {
			self.config.on_result_show();
		}

		// Hide cancel button
		var cancel_btn = container.querySelector('.mc-cancel-btn');
		if (cancel_btn) cancel_btn.style.display = 'none';

		// Hide flip button when viewing result
		var flip_btn = container.querySelector('.mc-flip-btn');
		if (flip_btn) flip_btn.style.display = 'none';

		// Dispatch UI state event
		self.dispatch_ui_state('flip', 'off');

		// Dispatch UI state events for video buttons
		self.dispatch_ui_state('start', 'off');
		self.dispatch_ui_state('stop', 'off');
		self.dispatch_ui_state('pause', 'off');
		self.dispatch_ui_state('resume', 'off');
		self.dispatch_ui_state('retake', 'on');
		self.dispatch_ui_state('cancel', 'off');
		self.dispatch_ui_state('playback-play', 'on');
		self.dispatch_ui_state('playback-pause', 'off');
		self.dispatch_ui_state('playback-toggle', 'on');
		self.dispatch_ui_state('playback-stop', 'on');

		// Clear backup data
		self.blob_backup = null;
		self.filename_backup = null;
		self.video_url_backup = null;

		// Dispatch custom event
		self.dispatch_event('video-cancel');

		// Trigger callback if provided
		if (self.config.on_video_cancel) {
			self.config.on_video_cancel();
		}

		// Move focus to retake button for keyboard users
		var retake_btn = container.querySelector('.mc-retake-btn');
		if (retake_btn) retake_btn.focus();
		
		self.log('[video_cancel_internal] Cancel complete');
	};

	/**
	 * Start audio recording (internal method)
	 * @param {HTMLElement} container - The container element
	 * @param {MediaStream} stream - The media stream
	 * @param {number} max_duration - Maximum recording duration
	 * @private
	 */
	MediaCapture.prototype.audio_start_internal = function(container, stream, max_duration) {
		var self = this;
		
		// Mark as recording
		self.is_recording = true;
		self.recorded_chunks = [];

		// Start the recording
		self.start_recording(
			stream,
			container,
			max_duration,
			function(recorder, interval) {
				self.media_recorder = recorder;
				self.timer_interval = interval;
			},
			function(chunk) {
				self.recorded_chunks.push(chunk);
			},
			'audio',
			self.config.audio_format
		);

		// Update record button to stop state
		var record_btn = container.querySelector('.mc-record-btn');
		if (record_btn) {
			record_btn.innerHTML = self.config.icon_stop;
			record_btn.setAttribute('aria-label', self.config.label_stop);
			record_btn.setAttribute('title', self.config.label_stop);
			record_btn.classList.add('mc-recording');
		}

		// Show pause button
		var pause_btn = container.querySelector('.mc-pause-btn');
		if (pause_btn) pause_btn.style.display = '';
		
		// Disable all settings buttons during recording
		self.log('[audio_start_internal] Disabling settings buttons');
		var settings_btns = container.querySelectorAll('.mc-settings-btn');
		self.log('[audio_start_internal] Found', settings_btns.length, 'settings buttons');
		settings_btns.forEach(function(btn, index) {
			self.log('[audio_start_internal] Button', index, '- before:', btn.disabled, 'parent:', btn.parentElement.className);
			btn.disabled = true;
			self.log('[audio_start_internal] Button', index, '- after:', btn.disabled);
		});

		// Dispatch UI state events
		self.dispatch_ui_state('start', 'off');
		self.dispatch_ui_state('stop', 'on');
		self.dispatch_ui_state('pause', 'on');
		self.dispatch_ui_state('settings-open', 'off');
		self.dispatch_ui_state('settings-close', 'off');

		// Dispatch custom event
		self.dispatch_event('audio-start');

		// Trigger callback if provided
		if (self.config.on_audio_started) {
			self.config.on_audio_started();
		}
		
		// Announce to screen readers
		self.announce_to_screen_reader('Recording started');
	};

	/**
	 * Stop audio recording (internal method)
	 * @param {HTMLElement} container - The container element
	 * @param {HTMLAudioElement} audio_element - The audio element
	 * @param {Function} callback - Callback function when stop completes
	 * @private
	 */
	MediaCapture.prototype.audio_stop_internal = function(container, audio_element, callback) {
		var self = this;

		self.stop_recording(
			self.media_recorder,
			self.timer_interval,
			self.recorded_chunks,
			container,
			null, // No video element for audio mode
			function() {
				self.media_recorder = null;
				self.timer_interval = null;
				self.is_recording = false;
				self.stream = null;

				// Hide and reset pause button
				var pause_btn = container.querySelector('.mc-pause-btn');
				if (pause_btn) {
					pause_btn.style.display = 'none';
					pause_btn.classList.remove('mc-paused');
					pause_btn.innerHTML = self.config.icon_pause;
					pause_btn.setAttribute('aria-label', self.config.label_pause);
					pause_btn.setAttribute('title', self.config.label_pause);
				}

				// Remove paused class from timer
				var timer = container.querySelector('.mc-timer');
				if (timer) timer.classList.remove('mc-paused');
				
				// Re-enable all settings buttons after recording stops
				self.log('[audio_stop_internal] Re-enabling settings buttons');
				var settings_btns = container.querySelectorAll('.mc-settings-btn');
				self.log('[audio_stop_internal] Found', settings_btns.length, 'settings buttons');
				settings_btns.forEach(function(btn, index) {
					self.log('[audio_stop_internal] Button', index, '- before:', btn.disabled, 'parent:', btn.parentElement.className);
					btn.disabled = false;
					self.log('[audio_stop_internal] Button', index, '- after:', btn.disabled);
				});

				// Dispatch UI state events
				self.dispatch_ui_state('start', 'off');
				self.dispatch_ui_state('stop', 'off');
				self.dispatch_ui_state('pause', 'off');
				self.dispatch_ui_state('resume', 'off');
				self.dispatch_ui_state('retake', 'on');
				self.dispatch_ui_state('cancel', 'off');
				self.dispatch_ui_state('playback-play', 'on');
				self.dispatch_ui_state('playback-pause', 'off');
				self.dispatch_ui_state('playback-toggle', 'on');
				self.dispatch_ui_state('playback-stop', 'off');
				self.dispatch_ui_state('settings-open', 'on');
				self.dispatch_ui_state('settings-close', 'off');

				// Dispatch custom event
				self.dispatch_event('audio-stop', {
					blob: self.blob,
					filename: self.filename,
					duration: self.media_recorder ? self.media_recorder.recorded_duration : 0
				});

				// Trigger callback if provided
				if (self.config.on_audio_stopped) {
					self.config.on_audio_stopped(self.blob, self.filename);
				}
				
				// Announce to screen readers
				self.announce_to_screen_reader('Recording stopped');

				if (callback) {
					callback();
				}
			}
		);
	};

	/**
	 * Pause audio recording (internal method)
	 * @param {HTMLElement} container - The container element
	 * @private
	 */
	MediaCapture.prototype.audio_pause_internal = function(container) {
		var self = this;
		var pause_btn = container.querySelector('.mc-pause-btn');
		var timer = container.querySelector('.mc-timer');

		// Pause recording
		self.media_recorder.pause();

		// Pause the timer intervals
		if (self.timer_interval) {
			clearInterval(self.timer_interval);
			self.timer_interval = null;
		}
		if (self.media_recorder.duration_interval) {
			clearInterval(self.media_recorder.duration_interval);
			self.media_recorder.duration_interval = null;
		}

		// Store the paused time for resume
		self.media_recorder.paused_time = Date.now();

		// Update button to resume/play state
		if (pause_btn) {
			pause_btn.innerHTML = self.config.icon_play;
			pause_btn.setAttribute('aria-label', self.config.label_resume);
			pause_btn.setAttribute('title', self.config.label_resume);
			pause_btn.classList.add('mc-paused');
		}

		// Add paused class to timer for visual feedback
		if (timer) timer.classList.add('mc-paused');

		// Dispatch UI state events
		self.dispatch_ui_state('pause', 'off');
		self.dispatch_ui_state('resume', 'on');

		// Dispatch custom event
		self.dispatch_event('audio-pause');

		// Trigger callback if provided
		if (self.config.on_audio_paused) {
			self.config.on_audio_paused();
		}
	};

	/**
	 * Resume audio recording (internal method)
	 * @param {HTMLElement} container - The container element
	 * @private
	 */
	MediaCapture.prototype.audio_resume_internal = function(container) {
		var self = this;
		var pause_btn = container.querySelector('.mc-pause-btn');
		var timer = container.querySelector('.mc-timer');
		
		var TIMER_UPDATE_INTERVAL = 1000;
		var DURATION_UPDATE_INTERVAL = 100;
		var MILLISECONDS_PER_SECOND = 1000;
		var SELECTORS = { RECORD_BTN: '.mc-record-btn' };

		// Resume recording
		self.media_recorder.resume();

		// Calculate time elapsed during pause and adjust start_time
		var pause_duration = Date.now() - self.media_recorder.paused_time;
		self.media_recorder.start_time += pause_duration;

		// Resume the timer display
		var current_seconds = Math.floor((Date.now() - self.media_recorder.start_time) / 1000);
		var timer_remaining_element = container.querySelector('.mc-timer-remaining');
		var has_remaining_display = timer_remaining_element !== null;
		var max_duration = self.config.max_duration;

		self.timer_interval = setInterval(function() {
			// Safety check: if media_recorder is null, clear interval and stop
			if (!self.media_recorder) {
				clearInterval(self.timer_interval);
				return;
			}

			current_seconds++;
			var mins = Math.floor(current_seconds / 60);
			var secs = current_seconds % 60;

			// Update elapsed time only
			var timer_elapsed = container.querySelector('.mc-timer-elapsed');
			if (timer_elapsed) {
				timer_elapsed.textContent = self.pad_zero(mins) + ':' + self.pad_zero(secs);
			}

			// Update remaining time if element exists
			if (has_remaining_display) {
				var remaining_seconds = max_duration - current_seconds;
				if (remaining_seconds > 0) {
					var remaining_mins = Math.floor(remaining_seconds / 60);
					var remaining_secs = remaining_seconds % 60;
					timer_remaining_element.textContent = self.pad_zero(remaining_mins) + ':' + self.pad_zero(remaining_secs);
				} else {
					timer_remaining_element.textContent = '00:00';
				}
			}

			// Auto-stop at max duration
			if (current_seconds >= max_duration) {
				var record_btn = container.querySelector(SELECTORS.RECORD_BTN);
				if (record_btn) record_btn.click();
			}
		}, TIMER_UPDATE_INTERVAL);

		// Resume duration tracking
		var duration_interval = setInterval(function() {
			// Safety check: if media_recorder is null, clear interval and stop
			if (!self.media_recorder) {
				clearInterval(duration_interval);
				return;
			}

			var elapsed_ms = Date.now() - self.media_recorder.start_time;
			self.media_recorder.recorded_duration = elapsed_ms / MILLISECONDS_PER_SECOND;
		}, DURATION_UPDATE_INTERVAL);

		self.media_recorder.duration_interval = duration_interval;

		// Update button back to pause state
		if (pause_btn) {
			pause_btn.innerHTML = self.config.icon_pause;
			pause_btn.setAttribute('aria-label', self.config.label_pause);
			pause_btn.setAttribute('title', self.config.label_pause);
			pause_btn.classList.remove('mc-paused');
		}

		// Remove paused class from timer
		if (timer) timer.classList.remove('mc-paused');

		// Dispatch UI state events
		self.dispatch_ui_state('pause', 'on');
		self.dispatch_ui_state('resume', 'off');

		// Dispatch custom event
		self.dispatch_event('audio-resume');

		// Trigger callback if provided
		if (self.config.on_audio_resumed) {
			self.config.on_audio_resumed();
		}
	};

	/**
	 * Retake audio (internal method)
	 * @param {HTMLElement} container - The container element
	 * @private
	 */
	MediaCapture.prototype.audio_retake_internal = function(container) {
		var self = this;

		// Backup the current blob and filename before clearing (for cancel functionality)
		self.blob_backup = self.blob;
		self.filename_backup = self.filename;
		
		// Backup the current audio URL if it exists
		var audio_element = self.audio_element;
		if (audio_element && audio_element.src) {
			self.audio_url_backup = audio_element.src;
		}

		self.blob = null;
		self.filename = null;

		// Hide download option
		self.hide_download_option(container);

		// Hide result and show preview container
		var preview_container = container.querySelector('.mc-capture');
		if (preview_container) preview_container.style.display = 'none';
		if (preview_container) preview_container.style.display = '';

		// Dispatch result-hide event
		self.dispatch_event('result-hide');

		// Trigger callback if provided
		if (self.config.on_result_hide) {
			self.config.on_result_hide();
		}

		// Show cancel button
		var cancel_btn = container.querySelector('.mc-cancel-btn');
		if (cancel_btn) cancel_btn.style.display = '';

		// Hide playback controls
		var playback_controls = container.querySelector('.mc-result-controls');
		if (playback_controls) {
			playback_controls.style.display = 'none';
			playback_controls.classList.remove('mc-controls-visible');
		}

		// Show overlay controls
		var overlay_controls = container.querySelector('.mc-capture-controls');
		if (overlay_controls) {
			overlay_controls.style.display = '';
		}

		// Reset and show record button
		var record_btn = container.querySelector('.mc-record-btn');
		if (record_btn) {
			record_btn.innerHTML = self.config.icon_record;
			record_btn.setAttribute('aria-label', self.config.label_start_recording);
			record_btn.setAttribute('title', self.config.label_start_recording);
			record_btn.classList.remove('mc-recording');
			record_btn.style.display = '';
			record_btn.focus();
		}

		// Restart the microphone stream for re-recording
		self.stream_start_internal(
			container,
			null, // No video element for audio mode
			null, // No facing mode for audio
			null, // No video width
			null, // No video height
			self.config.mode,
			null, // No video device
			self.selected_audio_device,
			function(stream) {
				self.stream = stream;
				
				// Start mic monitoring for audio mode
				self.start_mic_monitoring(container, stream, function(context, analyser_node) {
					// Store references if needed
				});
				
				// Show controls for overlay
				self.show_controls();
				
				// Ensure playback controls remain hidden
				var pb_controls = container.querySelector('.mc-result-controls');
				if (pb_controls) {
					pb_controls.style.display = 'none';
					pb_controls.classList.remove('mc-controls-visible');
				}

				// Dispatch UI state events for audio buttons
				self.dispatch_ui_state('start', 'on');
				self.dispatch_ui_state('stop', 'off');
				self.dispatch_ui_state('pause', 'off');
				self.dispatch_ui_state('resume', 'off');
				self.dispatch_ui_state('retake', 'off');
				self.dispatch_ui_state('cancel', 'on');
				self.dispatch_ui_state('playback-play', 'off');
				self.dispatch_ui_state('playback-pause', 'off');
				self.dispatch_ui_state('playback-toggle', 'off');
				self.dispatch_ui_state('playback-stop', 'off');
			}
		);

		// Hide settings panel
		var settings_panel = container.querySelector('.mc-settings');
		if (settings_panel) settings_panel.style.display = 'none';

		// Dispatch custom event
		self.dispatch_event('audio-retake');

		// Trigger callback if provided
		if (self.config.on_audio_retake) {
			self.config.on_audio_retake();
		}
	};

	/**
	 * Cancel audio retake (internal method)
	 * @param {HTMLElement} container - The container element
	 * @param {HTMLElement} result_container - The result container element
	 * @param {HTMLElement} preview_container - The preview container element
	 * @private
	 */
	MediaCapture.prototype.audio_cancel_internal = function(container) {
		var self = this;

		// Stop the microphone stream (audio mode doesn't use video_element)
		if (self.stream) {
			var tracks = self.stream.getTracks();
			tracks.forEach(function(track) {
				track.stop();
			});
			self.stream = null;
		}
		
		// CRITICAL: Reset recording state when canceling
		self.is_recording = false;
		self.media_recorder = null;
		self.timer_interval = null;

		// Restore the backed up blob and filename
		self.blob = self.blob_backup;
		self.filename = self.filename_backup;

		// Restore the audio if it was backed up
		var audio_element = self.audio_element;
		if (audio_element && self.audio_url_backup) {
			audio_element.src = self.audio_url_backup;
			audio_element.load();
		}

		// Switch from overlay controls to playback controls
		var overlay_controls = container.querySelector('.mc-capture-controls');
		var playback_controls = container.querySelector('.mc-result-controls');
		
		if (overlay_controls) overlay_controls.style.display = 'none';
		if (playback_controls) playback_controls.style.display = '';
		
		var preview_container = container.querySelector('.mc-preview');
		if (preview_container) preview_container.style.display = '';

		// Dispatch result-show event
		self.dispatch_event('result-show');

		// Trigger callback if provided
		if (self.config.on_result_show) {
			self.config.on_result_show();
		}

		// Hide cancel button
		var cancel_btn = container.querySelector('.mc-cancel-btn');
		if (cancel_btn) cancel_btn.style.display = 'none';

		// Dispatch UI state events for audio buttons
		self.dispatch_ui_state('start', 'off');
		self.dispatch_ui_state('stop', 'off');
		self.dispatch_ui_state('pause', 'off');
		self.dispatch_ui_state('resume', 'off');
		self.dispatch_ui_state('retake', 'on');
		self.dispatch_ui_state('cancel', 'off');
		self.dispatch_ui_state('playback-play', 'on');
		self.dispatch_ui_state('playback-pause', 'off');
		self.dispatch_ui_state('playback-toggle', 'on');
		self.dispatch_ui_state('playback-stop', 'on');

		// Clear backup data
		self.blob_backup = null;
		self.filename_backup = null;
		self.audio_url_backup = null;

		// Dispatch custom event
		self.dispatch_event('audio-cancel');

		// Trigger callback if provided
		if (self.config.on_audio_cancel) {
			self.config.on_audio_cancel();
		}

		// Move focus to retake button for keyboard users
		var retake_btn = container.querySelector('.mc-retake-btn');
		if (retake_btn) retake_btn.focus();
	};

	/**
	 * Open settings panel (internal method)
	 * @param {HTMLElement} container - The container element
	 * @param {HTMLElement} settings_panel - The settings panel element
	 * @param {HTMLElement} settings_btn - The settings button element
	 * @private
	 */
	MediaCapture.prototype.settings_open_internal = function(container, settings_panel, settings_btn) {
		var self = this;

		// Position the panel before opening
		self.position_settings_panel(settings_panel, settings_btn);

		self.slide_down(settings_panel, 300, function() {
			// Focus first menu item in main menu
			var first_item = settings_panel.querySelector('[data-menu="main"] .mc-settings-menu-item:not([disabled])');
			if (first_item) {
				first_item.focus();
			}
		});
		
		if (settings_btn) {
			settings_btn.setAttribute('aria-expanded', 'true');
		}

		// Dispatch UI state events
		self.dispatch_ui_state('settings-open', 'off');
		self.dispatch_ui_state('settings-close', 'on');

		// Dispatch custom event
		self.dispatch_event('settings-open');

		// Trigger callback if provided
		if (self.config.on_settings_opened) {
			self.config.on_settings_opened();
		}
	};

	/**
	 * Position settings panel based on available viewport space
	 * @param {HTMLElement} settings_panel - The settings panel element
	 * @param {HTMLElement} settings_btn - The settings button element
	 * @private
	 */
	MediaCapture.prototype.position_settings_panel = function(settings_panel, settings_btn) {
		if (!settings_panel || !settings_btn) {
			return;
		}

		// Get button position in viewport (using fixed positioning like native audio controls)
		var btn_rect = settings_btn.getBoundingClientRect();
		
		// Gap between button and panel
		var gap = 8;
		
		// Get estimated panel height
		var panel_estimated_height = 200;
		
		// Calculate space above and below the button in viewport
		var space_above = btn_rect.top;
		var space_below = window.innerHeight - btn_rect.bottom;
		
		// Minimum space required to show panel
		var min_space_required = panel_estimated_height + 20;
		
		// Remove any existing position classes
		settings_panel.classList.remove('mc-position-above', 'mc-position-below');
		
		// Calculate right position (align with right edge of button)
		var right_position = window.innerWidth - btn_rect.right;
		settings_panel.style.right = right_position + 'px';
		
		// Decide positioning: prefer above if there's enough space, otherwise show below
		if (space_above >= min_space_required) {
			settings_panel.classList.add('mc-position-above');
			// Position above the button with gap
			settings_panel.style.bottom = (window.innerHeight - btn_rect.top + gap) + 'px';
			settings_panel.style.top = 'auto';
		} else if (space_below >= min_space_required) {
			settings_panel.classList.add('mc-position-below');
			// Position below the button with gap
			settings_panel.style.top = (btn_rect.bottom + gap) + 'px';
			settings_panel.style.bottom = 'auto';
		} else {
			// If neither has enough space, use whichever has more
			if (space_above > space_below) {
				settings_panel.classList.add('mc-position-above');
				settings_panel.style.bottom = (window.innerHeight - btn_rect.top + gap) + 'px';
				settings_panel.style.top = 'auto';
			} else {
				settings_panel.classList.add('mc-position-below');
				settings_panel.style.top = (btn_rect.bottom + gap) + 'px';
				settings_panel.style.bottom = 'auto';
			}
		}
	};

	/**
	 * Show settings submenu
	 * @param {HTMLElement} settings_panel - The settings panel element
	 * @param {string} submenu_name - Name of submenu to show ('video-input' or 'audio-input')
	 * @private
	 */
	MediaCapture.prototype.show_settings_submenu = function(settings_panel, submenu_name) {
		// Hide main menu
		var main_menu = settings_panel.querySelector('[data-menu="main"]');
		if (main_menu) {
			main_menu.style.display = 'none';
		}
		
		// Show submenu
		var submenu = settings_panel.querySelector('[data-menu="' + submenu_name + '"]');
		if (submenu) {
			submenu.style.display = 'block';
			
			// Focus first menu item in submenu (back button)
			setTimeout(function() {
				var first_item = submenu.querySelector('.mc-settings-menu-item');
				if (first_item) first_item.focus();
			}, 0);
		}
	};

	/**
	 * Hide settings submenu and show main menu
	 * @param {HTMLElement} settings_panel - The settings panel element
	 * @private
	 */
	MediaCapture.prototype.hide_settings_submenu = function(settings_panel) {
		// Hide all submenus
		var submenus = settings_panel.querySelectorAll('.mc-settings-menu-sub');
		submenus.forEach(function(submenu) {
			submenu.style.display = 'none';
		});
		
		// Show main menu
		var main_menu = settings_panel.querySelector('[data-menu="main"]');
		if (main_menu) {
			main_menu.style.display = 'block';
			
			// Focus first menu item in main menu
			setTimeout(function() {
				var first_item = main_menu.querySelector('.mc-settings-menu-item');
				if (first_item) first_item.focus();
			}, 0);
		}
	};

	/**
	 * Select a device and restart stream
	 * @param {HTMLElement} container - The container element
	 * @param {HTMLVideoElement} video_element - The video element
	 * @param {string} facing_mode - The facing mode
	 * @param {number} video_width - Video width
	 * @param {number} video_height - Video height
	 * @param {string} mode - Capture mode
	 * @param {string} device_type - 'video' or 'audio'
	 * @param {string} device_id - Device ID to select
	 * @param {AudioContext} audio_context - Audio context reference
	 * @param {AnalyserNode} analyser - Analyser node reference
	 * @private
	 */
	MediaCapture.prototype.select_device = function(container, video_element, facing_mode, video_width, 
													video_height, mode, device_type, device_id, 
													audio_context, analyser) {
		var self = this;
		
		// Prevent device switching during recording
		if (self.is_recording) {
			return;
		}
		
		// Update selected device
		if (device_type === 'video') {
			self.selected_video_device = device_id || null;
		} else if (device_type === 'audio') {
			self.selected_audio_device = device_id || null;
		}
		
		// If we're in playback mode (have recorded content), just update the device selection
		// without restarting the stream. The new device will be used on next retake.
		if (self.blob) {
			// Re-enumerate devices to update checkmarks
			self.enumerate_devices(container, mode);
			return;
		}
		
		// Restart stream with new device
		self.stream_stop_internal(video_element);
		self.stream_start_internal(
			container,
			video_element,
			facing_mode,
			video_width,
			video_height,
			mode,
			self.selected_video_device,
			self.selected_audio_device,
			function(stream) {
				self.stream = stream;
				if (mode === 'video' && device_type === 'audio') {
					self.start_mic_monitoring(container, stream, function(context, analyser_node) {
						audio_context = context;
						analyser = analyser_node;
					});
				}
				
				// Re-enumerate devices to update checkmarks
				self.enumerate_devices(container, mode);
			}
		);
	};

	/**
	 * Enable download option in settings menu
	 * @param {HTMLElement} container - The container element
	 * @private
	 */
	MediaCapture.prototype.show_download_option = function(container) {
		if (!this.config.settings_download_enabled) {
			return;
		}
		
		var download_item = container.querySelector('.mc-download-item');
		if (download_item) {
			download_item.disabled = false;
		}
		
		this.dispatch_ui_state('download', 'on');
	};

	/**
	 * Disable download option in settings menu
	 * @param {HTMLElement} container - The container element
	 * @private
	 */
	MediaCapture.prototype.hide_download_option = function(container) {
		if (!this.config.settings_download_enabled) {
			return;
		}
		
		var download_item = container.querySelector('.mc-download-item');
		if (download_item) {
			download_item.disabled = true;
		}
		
		this.dispatch_ui_state('download', 'off');
	};

	/**
	 * Close settings panel (internal method)
	 * @param {HTMLElement} container - The container element
	 * @param {HTMLElement} settings_panel - The settings panel element
	 * @param {HTMLElement} settings_btn - The settings button element
	 * @private
	 */
	MediaCapture.prototype.settings_close_internal = function(container, settings_panel, settings_btn) {
		var self = this;

		// Reset to main menu when closing
		self.hide_settings_submenu(settings_panel);

		self.slide_up(settings_panel, 300);
		
		// Focus settings button after animation completes
		// If settings_btn is not provided or not visible, find the visible one
		if (!settings_btn || settings_btn.style.display === 'none') {
			// Query all settings buttons and find the visible one
			var all_settings_btns = container.querySelectorAll('.mc-settings-btn');
			for (var i = 0; i < all_settings_btns.length; i++) {
				if (all_settings_btns[i].style.display !== 'none' && all_settings_btns[i].offsetParent !== null) {
					settings_btn = all_settings_btns[i];
					break;
				}
			}
		}

		if (settings_btn) {
			settings_btn.setAttribute('aria-expanded', 'false');
			setTimeout(function() {
				settings_btn.focus();
			}, 300);
		}

		// Dispatch UI state events
		self.dispatch_ui_state('settings-open', 'on');
		self.dispatch_ui_state('settings-close', 'off');

		// Dispatch custom event
		self.dispatch_event('settings-close');

		// Trigger callback if provided
		if (self.config.on_settings_closed) {
			self.config.on_settings_closed();
		}
	};

	/**
	 * Flip camera (internal method)
	 * @param {HTMLElement} container - The container element
	 * @param {HTMLVideoElement} video_element - The video element
	 * @param {string} mode - The capture mode
	 * @param {Function} callback - Callback function when flip completes
	 * @private
	 */
	MediaCapture.prototype.stream_flip_internal = function(container, video_element, mode, callback) {
		var self = this;

		// Toggle facing mode
		var new_facing_mode = (self.config.facing_mode === 'user') ? 'environment' : 'user';
		self.config.facing_mode = new_facing_mode;

		// For photo mode, hide result container and show preview container
		if (mode === 'photo') {
			var result_container = preview_container; // Use preview_container
			var preview_container = container.querySelector('.mc-capture');
			if (preview_container) preview_container.style.display = 'none';
			if (preview_container) preview_container.style.display = '';
		}

		// Switch camera
		self.stream_stop_internal(video_element);

		self.stream_start_internal(
			container,
			video_element,
			new_facing_mode,
			self.video_width,
			self.video_height,
			mode,
			null,
			self.selected_audio_device,
			function(new_stream) {
				self.stream = new_stream;
				
				// Dispatch custom event
				self.dispatch_event('stream-flip', { 
					facing_mode: new_facing_mode,
					stream: new_stream
				});

				// Trigger callback if provided
				if (self.config.on_stream_flipped) {
					self.config.on_stream_flipped(new_facing_mode);
				}

				if (callback) {
					callback(new_stream);
				}
			}
		);
	};

	/**
	 * Start the camera/stream
	 * Public method to start the camera stream programmatically
	 * @returns {Promise} Promise that resolves when stream is started
	 */
	MediaCapture.prototype.stream_start = function() {
		var self = this;
		
		self.log('[stream_start] Called');
		
		return new Promise(function(resolve, reject) {
			self.log('[stream_start] Inside promise, is_initialized:', self.is_initialized);
			
			if (!self.is_initialized) {
				self.log('[stream_start] NOT initialized, rejecting');
				reject(new Error('MediaCapture must be initialized before starting stream'));
				return;
			}

			self.log('[stream_start] Initialized, proceeding...');
			
			var container = self.element;
			var video_element = self.video_element;
			
			self.log('[stream_start] video_element:', video_element);
			self.log('[stream_start] mode:', self.config.mode);
			var start_screen = container.querySelector('.mc-start');
			var preview_container = container.querySelector('.mc-capture');

			// Hide start screen, show preview
			if (start_screen) start_screen.style.display = 'none';
			if (preview_container) preview_container.style.display = '';

			self.log('[stream_start] About to call stream_start_internal');
			self.log('[stream_start] Params:', {
				mode: self.config.mode,
				facing_mode: self.config.facing_mode,
				video_device: self.selected_video_device,
				audio_device: self.selected_audio_device
			});

			self.stream_start_internal(
				container,
				video_element,
				self.config.facing_mode,
				self.video_width,
				self.video_height,
				self.config.mode,
				self.selected_video_device,
				self.selected_audio_device,
				function(stream) {
					self.stream = stream;
					
					// Start mic monitoring for video and audio modes
					if (self.config.mode === 'video' || self.config.mode === 'audio') {
						self.start_mic_monitoring(container, stream, function(context, analyser_node) {
							// Store references if needed
						});
					}

					// Check for multiple cameras on mobile (both photo and video modes)
					if (self.is_mobile && !self.has_checked_cameras) {
						self.has_checked_cameras = true;
						self.check_camera_flip_availability(container);
					}

					// Show controls when stream starts
					self.show_controls();

					// Focus on appropriate button based on mode
					if (self.config.mode === 'photo') {
						var capture_btn = container.querySelector('.mc-capture-btn');
						if (capture_btn) capture_btn.focus();
					} else if (self.config.mode === 'video' || self.config.mode === 'audio') {
						var record_btn = container.querySelector('.mc-record-btn');
						if (record_btn) record_btn.focus();
					}

					// Dispatch UI state events based on capture mode
					if (self.config.mode === 'photo') {
						self.dispatch_ui_state('capture', 'on');
						self.dispatch_ui_state('retake', 'off');
						self.dispatch_ui_state('cancel', 'off');
					} else if (self.config.mode === 'video') {
						self.dispatch_ui_state('start', 'on');
						self.dispatch_ui_state('stop', 'off');
						self.dispatch_ui_state('pause', 'off');
						self.dispatch_ui_state('resume', 'off');
						self.dispatch_ui_state('retake', 'off');
						self.dispatch_ui_state('cancel', 'off');
						self.dispatch_ui_state('playback-play', 'off');
						self.dispatch_ui_state('playback-pause', 'off');
						self.dispatch_ui_state('playback-toggle', 'off');
						self.dispatch_ui_state('playback-stop', 'off');
					} else if (self.config.mode === 'audio') {
						self.dispatch_ui_state('start', 'on');
						self.dispatch_ui_state('stop', 'off');
						self.dispatch_ui_state('pause', 'off');
						self.dispatch_ui_state('resume', 'off');
						self.dispatch_ui_state('retake', 'off');
						self.dispatch_ui_state('cancel', 'off');
						self.dispatch_ui_state('playback-play', 'off');
						self.dispatch_ui_state('playback-pause', 'off');
						self.dispatch_ui_state('playback-toggle', 'off');
						self.dispatch_ui_state('playback-stop', 'off');
					}
					
					// Settings button should be enabled when stream is active
					self.dispatch_ui_state('settings-open', 'on');
					self.dispatch_ui_state('settings-close', 'off');

					// Stream control UI states
					self.dispatch_ui_state('stream-start', 'off');
					self.dispatch_ui_state('stream-stop', 'on');

					// Dispatch custom event
					self.dispatch_event('stream-start', { stream: stream });

					resolve(stream);
				},
				function(error) {
					// Show error screen
					if (preview_container) preview_container.style.display = 'none';
					var error_screen = container.querySelector('.mc-error');
					if (error_screen) error_screen.style.display = '';
					
					reject(error);
				}
			);
		});
	};

	/**
	 * Capture a photo
	 * Public method to capture a photo from the video stream programmatically
	 * @returns {Promise} Promise that resolves with {blob, filename, metadata}
	 */
	MediaCapture.prototype.photo_capture = function() {
		var self = this;
		
		self.log('Photo capture initiated');
		
		return new Promise(function(resolve, reject) {
			if (!self.is_initialized) {
				self.log('Photo capture failed: not initialized', 'error');
				reject(new Error('MediaCapture must be initialized before capturing photo'));
				return;
			}

			if (self.config.mode !== 'photo') {
				self.log('Photo capture failed: not in photo mode', 'error');
				reject(new Error('MediaCapture must be in photo mode to capture photos'));
				return;
			}

			if (!self.is_stream_active()) {
				self.log('Photo capture failed: stream not active', 'error');
				reject(new Error('Camera stream must be active before capturing photo'));
				return;
			}

			self.log('Photo capture: capturing image from canvas');

			var container = self.element;
			var video_element = self.video_element;
			var canvas_element = self.canvas_element;
			var result_photo = container.querySelector('.mc-photo');

			// Play photo sound if enabled
			self.play_photo_sound(self.config.photo_sound_enabled);

			canvas_element.width = video_element.videoWidth;
			canvas_element.height = video_element.videoHeight;

			var context = canvas_element.getContext('2d');
			context.drawImage(video_element, 0, 0);

			var mime_type = self.get_supported_photo_mime_type(self.config.photo_format);

			// Convert canvas to blob
			canvas_element.toBlob(function(blob) {
				if (!blob) {
					self.log('Photo capture failed: unable to create blob', 'error');
					reject(new Error('Failed to capture photo'));
					return;
				}

				self.log('Photo captured successfully: ' + blob.size + ' bytes');

				// Create object URL for preview
				var object_url = URL.createObjectURL(blob);
				result_photo.src = object_url;

				// Store blob and filename
				var filename = 'photo-' + Date.now() + '.' + self.config.photo_format;
				self.blob = blob;
				self.filename = filename;

				// Create metadata JSON
				var metadata = {
					type: 'photo',
					width: canvas_element.width,
					height: canvas_element.height,
					size: blob.size,
					format: self.config.photo_format,
					quality: self.config.photo_quality,
					timestamp: Date.now()
				};

				// Show result container and hide video/controls, show player/result controls
				var preview_container = container.querySelector('.mc-capture');
				var settings_panel = container.querySelector('.mc-settings');
				var video_element_dom = container.querySelector('.mc-video');
				var player_element = container.querySelector('.mc-result');
				var overlay_controls = container.querySelector('.mc-capture-controls');
				
				if (preview_container) preview_container.style.display = '';
				if (settings_panel) settings_panel.style.display = 'none';
				if (video_element_dom) video_element_dom.style.display = 'none';
				if (player_element) player_element.style.display = '';
				if (overlay_controls) overlay_controls.style.display = 'none';

				// Hide flip button when viewing result
				var flip_btn = container.querySelector('.mc-flip-btn');
				if (flip_btn) flip_btn.style.display = 'none';

				// Dispatch UI state events
				self.dispatch_ui_state('flip', 'off');
				self.dispatch_ui_state('capture', 'off');
				self.dispatch_ui_state('retake', 'on');
				self.dispatch_ui_state('cancel', 'off');

				// Dispatch result-show event
				self.dispatch_event('result-show');

				// Trigger result-show callback if provided
				if (self.config.on_result_show) {
					self.config.on_result_show();
				}

				// Trigger flash effect in result container
				var flash_overlay = preview_container.querySelector('.mc-flash');
				if (flash_overlay) {
					flash_overlay.classList.add('mc-flash-active');
					setTimeout(function() {
						flash_overlay.classList.remove('mc-flash-active');
					}, 50);
				}

				// Move focus to retake button for keyboard users
				var retake_btn = container.querySelector('.mc-retake-btn');
				if (retake_btn) retake_btn.focus();

				// Trigger callback if provided
				if (self.config.on_photo_captured) {
					self.config.on_photo_captured(blob, filename, metadata);
				}

				// Dispatch custom event
				self.dispatch_event('photo-capture', {
					blob: blob,
					filename: filename,
					metadata: metadata
				});

				resolve({
					blob: blob,
					filename: filename,
					metadata: metadata
				});

			}, mime_type, self.config.photo_quality);
		});
	};

	/**
	 * Stop the camera/stream
	 * Public method to stop the camera stream programmatically
	 */
	MediaCapture.prototype.stream_stop = function() {
		this.log('Stream stop initiated');
		
		if (!this.is_initialized) {
			this.log('Stream stop failed: not initialized', 'error');
			return;
		}

		var container = this.element;
		var video_element = this.video_element;
		var preview_container = container.querySelector('.mc-capture');
		var result_container = preview_container; // Use preview_container
		var start_screen = container.querySelector('.mc-start');

		// Stop the stream
		this.log('Stopping media stream');
		this.stream_stop_internal(video_element);
		this.stream = null;

		// Hide preview and result, show start screen
		if (preview_container) preview_container.style.display = 'none';
		if (preview_container) preview_container.style.display = 'none';
		if (start_screen) start_screen.style.display = '';

		this.log('Resetting all UI states');

		// Dispatch UI state events - all UI elements should be disabled when stream stops
		this.dispatch_ui_state('flip', 'off');
		this.dispatch_ui_state('capture', 'off');
		this.dispatch_ui_state('start', 'off');
		this.dispatch_ui_state('stop', 'off');
		this.dispatch_ui_state('retake', 'off');
		this.dispatch_ui_state('cancel', 'off');
		this.dispatch_ui_state('settings-open', 'off');
		this.dispatch_ui_state('settings-close', 'off');

		// Stream control UI states
		this.dispatch_ui_state('stream-start', 'on');
		this.dispatch_ui_state('stream-stop', 'off');

		// Dispatch custom event
		this.dispatch_event('stream-stop');
		
		this.log('Stream stopped successfully');
	};

	/**
	 * Flip the camera
	 * Public method to flip between front and back cameras programmatically
	 */
	MediaCapture.prototype.stream_flip = function() {
		this.log('Camera flip initiated');
		
		if (!this.is_initialized) {
			this.log('Stream flip failed: not initialized', 'error');
			return;
		}

		// Check if stream is active
		if (!this.stream || !this.stream.active) {
			this.log('stream_flip() can only be called when stream is active', 'warn');
			return;
		}

		// Check if multiple cameras available
		if (!this.has_multiple_cameras) {
			this.log('stream_flip() requires multiple cameras', 'warn');
			return;
		}

		// Check if result is currently showing
		var container = this.element;
		var preview_container = container.querySelector('.mc-capture');
		if (preview_container && preview_container.style.display !== 'none') {
			this.log('stream_flip() cannot be called while result is showing', 'warn');
			return;
		}

		// For video mode, don't allow flipping during recording
		if (this.config.mode === 'video' && this.is_recording) {
			this.log('stream_flip() cannot be called during recording', 'warn');
			return;
		}

		var video_element = this.video_element;
		var self = this;

		this.log('Flipping camera');
		this.stream_flip_internal(container, video_element, this.config.mode, function(new_stream) {
			self.log('Camera flipped successfully');
			self.dispatch_event('stream-flipped');
			self.dispatch_ui_state('flip', 'on');
			
			// For video mode, restart mic monitoring
			if (self.config.mode === 'video') {
				self.start_mic_monitoring(container, new_stream, function(context, analyser_node) {
					// Store references if needed
				});
			}
		});
	};

	/**
	 * Retake a photo
	 * Public method to retake a photo programmatically
	 */
	MediaCapture.prototype.photo_retake = function() {
		if (!this.is_initialized) {
			return;
		}

		if (this.config.mode !== 'photo') {
			self.log('photo_retake() can only be called in photo mode');
			return;
		}

		var container = this.element;
		var result_container = preview_container; // Use preview_container
		var preview_container = container.querySelector('.mc-capture');

		this.photo_retake_internal(container, result_container, preview_container);

		// Move focus to capture button for keyboard users
		var capture_btn = container.querySelector('.mc-capture-btn');
		if (capture_btn) capture_btn.focus();
	};

	/**
	 * Cancel photo retake
	 * Public method to cancel a photo retake and restore the previous photo
	 */
	MediaCapture.prototype.photo_cancel = function() {
		if (!this.is_initialized) {
			return;
		}

		if (this.config.mode !== 'photo') {
			self.log('photo_cancel() can only be called in photo mode');
			return;
		}

		// Only allow cancel if there's a backup
		if (!this.blob_backup) {
			self.log('photo_cancel() can only be called after photo_retake()');
			return;
		}

		var container = this.element;
		var result_container = preview_container; // Use preview_container
		var preview_container = container.querySelector('.mc-capture');

		this.photo_cancel_internal(container, result_container, preview_container);
	};

	/**
	 * Start video recording
	 * Public method to start recording video programmatically
	 * @returns {Promise} Promise that resolves when recording starts
	 */
	MediaCapture.prototype.video_start = function() {
		var self = this;
		
		self.log('Video recording start initiated');
		
		return new Promise(function(resolve, reject) {
			if (!self.is_initialized) {
				self.log('Video start failed: not initialized', 'error');
				reject(new Error('MediaCapture must be initialized before starting video'));
				return;
			}

			if (self.config.mode !== 'video') {
				self.log('Video start failed: not in video mode', 'error');
				reject(new Error('video_start() can only be called in video mode'));
				return;
			}

			// Check if stream is active
			if (!self.stream || !self.stream.active) {
				self.log('Video start failed: stream not active', 'error');
				reject(new Error('Stream must be active before starting video recording'));
				return;
			}

			// Check if already recording
			if (self.is_recording) {
				self.log('Video start failed: already recording', 'error');
				reject(new Error('Video recording is already in progress'));
				return;
			}

			self.log('Video recording: starting MediaRecorder');

			var container = self.element;

			// Call internal method
			self.video_start_internal(container, self.stream, self.config.max_duration);

			self.log('Video recording: started successfully');
			resolve();
		});
	};

	/**
	 * Stop video recording
	 * Public method to stop recording video programmatically
	 * @returns {Promise} Promise that resolves with video data when recording stops
	 */
	MediaCapture.prototype.video_stop = function() {
		var self = this;
		
		self.log('Video recording stop initiated');
		
		return new Promise(function(resolve, reject) {
			if (!self.is_initialized) {
				self.log('Video stop failed: not initialized', 'error');
				reject(new Error('MediaCapture must be initialized before stopping video'));
				return;
			}

			if (self.config.mode !== 'video') {
				self.log('Video stop failed: not in video mode', 'error');
				reject(new Error('video_stop() can only be called in video mode'));
				return;
			}

			// Check if recording
			if (!self.is_recording) {
				self.log('Video stop failed: not recording', 'error');
				reject(new Error('No video recording in progress'));
				return;
			}

			if (!self.media_recorder || self.media_recorder.state === 'inactive') {
				self.log('Video stop failed: MediaRecorder not active', 'error');
				reject(new Error('MediaRecorder is not active'));
				return;
			}

			self.log('Video recording: stopping MediaRecorder');

			var container = self.element;
			var video_element = self.video_element;

			// Call internal method
			self.video_stop_internal(container, video_element, function() {
				self.log('Video recording: stopped successfully');
				resolve({
					blob: self.blob,
					filename: self.filename
				});
			});
		});
	};

	/**
	 * Pause video recording
	 * Public method to pause recording video programmatically
	 */
	MediaCapture.prototype.video_pause = function() {
		var self = this;
		
		if (!self.is_initialized) {
			self.log('MediaCapture must be initialized before pausing video');
			return;
		}

		if (self.config.mode !== 'video') {
			self.log('video_pause() can only be called in video mode');
			return;
		}

		// Check if recording
		if (!self.is_recording) {
			self.log('No video recording in progress to pause');
			return;
		}

		// Check if media recorder exists and is recording
		if (!self.media_recorder || self.media_recorder.state !== 'recording') {
			self.log('MediaRecorder is not in recording state');
			return;
		}

		var container = self.element;

		// Call internal method
		self.video_pause_internal(container);
	};

	/**
	 * Resume video recording
	 * Public method to resume recording video programmatically
	 */
	MediaCapture.prototype.video_resume = function() {
		var self = this;
		
		if (!self.is_initialized) {
			self.log('MediaCapture must be initialized before resuming video');
			return;
		}

		if (self.config.mode !== 'video') {
			self.log('video_resume() can only be called in video mode');
			return;
		}

		// Check if recording
		if (!self.is_recording) {
			self.log('No video recording in progress to resume');
			return;
		}

		// Check if media recorder exists and is paused
		if (!self.media_recorder || self.media_recorder.state !== 'paused') {
			self.log('MediaRecorder is not in paused state');
			return;
		}

		var container = self.element;

		// Call internal method
		self.video_resume_internal(container);
	};

	/**
	 * Retake video
	 * Public method to retake a video programmatically
	 */
	MediaCapture.prototype.video_retake = function() {
		if (!this.is_initialized) {
			return;
		}

		if (this.config.mode !== 'video') {
			self.log('video_retake() can only be called in video mode');
			return;
		}

		var container = this.element;
		var result_container = preview_container; // Use preview_container
		var preview_container = container.querySelector('.mc-capture');

		this.video_retake_internal(container, result_container, preview_container);

		// Move focus to record button for keyboard users
		var record_btn = container.querySelector('.mc-record-btn');
		if (record_btn) record_btn.focus();
	};

	/**
	 * Cancel video retake
	 * Public method to cancel a video retake and restore the previous video
	 */
	MediaCapture.prototype.video_cancel = function() {
		if (!this.is_initialized) {
			return;
		}

		if (this.config.mode !== 'video') {
			self.log('video_cancel() can only be called in video mode');
			return;
		}

		// Only allow cancel if there's a backup
		if (!this.blob_backup) {
			self.log('video_cancel() can only be called after video_retake()');
			return;
		}

		var container = this.element;
		var result_container = preview_container; // Use preview_container
		var preview_container = container.querySelector('.mc-capture');

		this.video_cancel_internal(container, result_container, preview_container);
	};

	/**
	 * Start audio recording
	 * Public method to start recording audio programmatically
	 * @returns {Promise} Promise that resolves when recording starts
	 */
	MediaCapture.prototype.audio_start = function() {
		var self = this;
		
		self.log('Audio recording start initiated');
		
		return new Promise(function(resolve, reject) {
			if (!self.is_initialized) {
				self.log('Audio start failed: not initialized', 'error');
				reject(new Error('MediaCapture must be initialized before starting audio'));
				return;
			}

			if (self.config.mode !== 'audio') {
				self.log('Audio start failed: not in audio mode', 'error');
				reject(new Error('audio_start() can only be called in audio mode'));
				return;
			}

			// Check if stream is active
			if (!self.stream || !self.stream.active) {
				self.log('Audio start failed: stream not active', 'error');
				reject(new Error('Stream must be active before starting audio recording'));
				return;
			}

			// Check if already recording
			if (self.is_recording) {
				self.log('Audio start failed: already recording', 'error');
				reject(new Error('Audio recording is already in progress'));
				return;
			}

			self.log('Audio recording: starting MediaRecorder');

			var container = self.element;

			// Call internal method
			self.audio_start_internal(container, self.stream, self.config.max_duration);

			self.log('Audio recording: started successfully');
			resolve();
		});
	};

	/**
	 * Stop audio recording
	 * Public method to stop recording audio programmatically
	 * @returns {Promise} Promise that resolves with audio data when recording stops
	 */
	MediaCapture.prototype.audio_stop = function() {
		var self = this;
		
		self.log('Audio recording stop initiated');
		
		return new Promise(function(resolve, reject) {
			if (!self.is_initialized) {
				self.log('Audio stop failed: not initialized', 'error');
				reject(new Error('MediaCapture must be initialized before stopping audio'));
				return;
			}

			if (self.config.mode !== 'audio') {
				self.log('Audio stop failed: not in audio mode', 'error');
				reject(new Error('audio_stop() can only be called in audio mode'));
				return;
			}

			// Check if recording
			if (!self.is_recording) {
				self.log('Audio stop failed: not recording', 'error');
				reject(new Error('No audio recording in progress'));
				return;
			}

			if (!self.media_recorder || self.media_recorder.state === 'inactive') {
				self.log('Audio stop failed: MediaRecorder not active', 'error');
				reject(new Error('MediaRecorder is not active'));
				return;
			}

			self.log('Audio recording: stopping MediaRecorder');

			var container = self.element;
			var audio_element = self.audio_element;

			// Call internal method
			self.audio_stop_internal(container, audio_element, function() {
				self.log('Audio recording: stopped successfully');
				resolve({
					blob: self.blob,
					filename: self.filename
				});
			});
		});
	};

	/**
	 * Pause audio recording
	 * Public method to pause recording audio programmatically
	 */
	MediaCapture.prototype.audio_pause = function() {
		var self = this;
		
		if (!self.is_initialized) {
			self.log('MediaCapture must be initialized before pausing audio');
			return;
		}

		if (self.config.mode !== 'audio') {
			self.log('audio_pause() can only be called in audio mode');
			return;
		}

		// Check if recording
		if (!self.is_recording) {
			self.log('No audio recording in progress to pause');
			return;
		}

		// Check if media recorder exists and is recording
		if (!self.media_recorder || self.media_recorder.state !== 'recording') {
			self.log('MediaRecorder is not in recording state');
			return;
		}

		var container = self.element;

		// Call internal method
		self.audio_pause_internal(container);
	};

	/**
	 * Resume audio recording
	 * Public method to resume recording audio programmatically
	 */
	MediaCapture.prototype.audio_resume = function() {
		var self = this;
		
		if (!self.is_initialized) {
			self.log('MediaCapture must be initialized before resuming audio');
			return;
		}

		if (self.config.mode !== 'audio') {
			self.log('audio_resume() can only be called in audio mode');
			return;
		}

		// Check if recording
		if (!self.is_recording) {
			self.log('No audio recording in progress to resume');
			return;
		}

		// Check if media recorder exists and is paused
		if (!self.media_recorder || self.media_recorder.state !== 'paused') {
			self.log('MediaRecorder is not in paused state');
			return;
		}

		var container = self.element;

		// Call internal method
		self.audio_resume_internal(container);
	};

	/**
	 * Retake audio
	 * Public method to retake an audio recording programmatically
	 */
	MediaCapture.prototype.audio_retake = function() {
		if (!this.is_initialized) {
			return;
		}

		if (this.config.mode !== 'audio') {
			self.log('audio_retake() can only be called in audio mode');
			return;
		}

		var container = this.element;
		var result_container = preview_container; // Use preview_container
		var preview_container = container.querySelector('.mc-capture');

		this.audio_retake_internal(container, result_container, preview_container);

		// Move focus to record button for keyboard users
		var record_btn = container.querySelector('.mc-record-btn');
		if (record_btn) record_btn.focus();
	};

	/**
	 * Cancel audio retake
	 * Public method to cancel an audio retake and restore the previous audio
	 */
	MediaCapture.prototype.audio_cancel = function() {
		if (!this.is_initialized) {
			return;
		}

		if (this.config.mode !== 'audio') {
			self.log('audio_cancel() can only be called in audio mode');
			return;
		}

		// Only allow cancel if there's a backup
		if (!this.blob_backup) {
			self.log('audio_cancel() can only be called after audio_retake()');
			return;
		}

		var container = this.element;
		var result_container = preview_container; // Use preview_container
		var preview_container = container.querySelector('.mc-capture');

		this.audio_cancel_internal(container, result_container, preview_container);
	};

	/**
	 * Play recorded video
	 * Public method to play the recorded video playback
	 */
	MediaCapture.prototype.playback_play = function() {
		if (!this.is_initialized) {
			return;
		}

		if (this.config.mode !== 'video' && this.config.mode !== 'audio') {
			self.log('playback_play() can only be called in video or audio mode');
			return;
		}

		var container = this.element;
		var media_element = this.config.mode === 'video' 
			? container.querySelector('.mc-video')
			: container.querySelector('.mc-audio');
		var play_btn = container.querySelector('.mc-play-btn');

		if (!media_element) {
			return;
		}

		// Only play if media is paused and has content
		if (media_element.paused && media_element.src) {
			media_element.play();
			if (play_btn) {
				play_btn.innerHTML = this.config.icon_pause;
				play_btn.setAttribute('aria-label', this.config.label_pause);
			}
			
			// Dispatch UI state events
			this.dispatch_ui_state('playback-play', 'off');
			this.dispatch_ui_state('playback-pause', 'on');
			this.dispatch_ui_state('playback-stop', 'on');
		}
	};

	/**
	 * Pause recorded video or audio
	 * Public method to pause the recorded video or audio playback
	 */
	MediaCapture.prototype.playback_pause = function() {
		if (!this.is_initialized) {
			return;
		}

		if (this.config.mode !== 'video' && this.config.mode !== 'audio') {
			self.log('playback_pause() can only be called in video or audio mode');
			return;
		}

		var container = this.element;
		var media_element = this.config.mode === 'video' 
			? container.querySelector('.mc-video')
			: container.querySelector('.mc-audio');
		var play_btn = container.querySelector('.mc-play-btn');

		if (!media_element) {
			return;
		}

		// Only pause if media is playing
		if (!media_element.paused) {
			media_element.pause();
			if (play_btn) {
				play_btn.innerHTML = this.config.icon_play;
				play_btn.setAttribute('aria-label', this.config.label_play);
			}
			
			// Dispatch UI state events
			this.dispatch_ui_state('playback-play', 'on');
			this.dispatch_ui_state('playback-pause', 'off');
			this.dispatch_ui_state('playback-stop', 'off');
		}
	};

	/**
	 * Toggle play/pause of recorded video or audio
	 * Public method to toggle between play and pause states
	 */
	MediaCapture.prototype.playback_toggle = function() {
		if (!this.is_initialized) {
			return;
		}

		if (this.config.mode !== 'video' && this.config.mode !== 'audio') {
			self.log('playback_toggle() can only be called in video or audio mode');
			return;
		}

		var container = this.element;
		var media_element = this.config.mode === 'video' 
			? container.querySelector('.mc-video')
			: container.querySelector('.mc-audio');

		if (!media_element || !media_element.src) {
			return;
		}

		if (media_element.paused) {
			this.playback_play();
		} else {
			this.playback_pause();
		}
	};

	/**
	 * Stop recorded video or audio playback
	 * Public method to stop the media and reset to beginning
	 */
	MediaCapture.prototype.playback_stop = function() {
		if (!this.is_initialized) {
			return;
		}

		if (this.config.mode !== 'video' && this.config.mode !== 'audio') {
			self.log('playback_stop() can only be called in video or audio mode');
			return;
		}

		var container = this.element;
		var media_element = this.config.mode === 'video' 
			? container.querySelector('.mc-video')
			: container.querySelector('.mc-audio');
		var play_btn = container.querySelector('.mc-play-btn');

		if (!media_element || !media_element.src) {
			return;
		}

		// Pause the media and reset to beginning
		media_element.pause();
		media_element.currentTime = 0;
		
		if (play_btn) {
			play_btn.innerHTML = this.config.icon_play;
			play_btn.setAttribute('aria-label', this.config.label_play);
		}
		
		// Dispatch UI state events
		this.dispatch_ui_state('playback-play', 'on');
		this.dispatch_ui_state('playback-pause', 'off');
		this.dispatch_ui_state('playback-stop', 'off');
	};

	/**
	 * Open settings panel
	 * Public method to open the settings panel programmatically
	 */
	MediaCapture.prototype.settings_open = function() {
		this.log('Opening settings panel');
		
		if (!this.is_initialized) {
			this.log('Settings open failed: not initialized', 'error');
			return;
		}

		var container = this.element;
		var settings_panel = container.querySelector('.mc-settings');
		
		// Find the visible settings button (not the first one, which might be hidden)
		var settings_btns = container.querySelectorAll('.mc-settings-btn');
		var settings_btn = null;
		for (var i = 0; i < settings_btns.length; i++) {
			var btn = settings_btns[i];
			// Check if button is visible (not in a hidden parent)
			if (btn.offsetParent !== null) {
				settings_btn = btn;
				break;
			}
		}

		if (!settings_panel) {
			this.log('Settings panel element not found', 'error');
			return;
		}

		var is_visible = settings_panel.style.display !== 'none' && settings_panel.style.display !== '';
		if (!is_visible) {
			this.log('Settings panel: showing');
			this.settings_open_internal(container, settings_panel, settings_btn);
			this.dispatch_ui_state('settings-open', 'off');
			this.dispatch_ui_state('settings-close', 'on');
		}
	};

	/**
	 * Close settings panel
	 * Public method to close the settings panel programmatically
	 */
	MediaCapture.prototype.settings_close = function() {
		this.log('Closing settings panel');
		
		if (!this.is_initialized) {
			this.log('Settings close failed: not initialized', 'error');
			return;
		}

		var container = this.element;
		var settings_panel = container.querySelector('.mc-settings');
		
		// Find the visible settings button (not the first one, which might be hidden)
		var settings_btns = container.querySelectorAll('.mc-settings-btn');
		var settings_btn = null;
		for (var i = 0; i < settings_btns.length; i++) {
			var btn = settings_btns[i];
			// Check if button is visible (not in a hidden parent)
			if (btn.offsetParent !== null) {
				settings_btn = btn;
				break;
			}
		}

		if (!settings_panel) {
			this.log('Settings panel element not found', 'error');
			return;
		}

		var is_visible = settings_panel.style.display !== 'none' && settings_panel.style.display !== '';
		if (is_visible) {
			this.log('Settings panel: hiding');
			this.settings_close_internal(container, settings_panel, settings_btn);
			this.dispatch_ui_state('settings-open', 'on');
			this.dispatch_ui_state('settings-close', 'off');
		}
	};

	/**
	 * Show controls
	 * Public method to show the overlay controls
	 */
	MediaCapture.prototype.show_controls = function() {
		if (!this.is_initialized) {
			return;
		}

		var container = this.element;
		var overlay_controls = container.querySelector('.mc-capture-controls');
		var playback_controls = container.querySelector('.mc-result-controls');
		var result_controls = container.querySelector('.mc-result-controls');

		if (overlay_controls) overlay_controls.classList.add('mc-controls-visible');
		if (playback_controls) playback_controls.classList.add('mc-controls-visible');
		if (result_controls) result_controls.classList.add('mc-controls-visible');
	};

	/**
	 * Hide controls
	 * Public method to hide the overlay controls
	 */
	MediaCapture.prototype.hide_controls = function() {
		if (!this.is_initialized) {
			return;
		}

		var container = this.element;
		var overlay_controls = container.querySelector('.mc-capture-controls');
		var playback_controls = container.querySelector('.mc-result-controls');
		var result_controls = container.querySelector('.mc-result-controls');

		if (overlay_controls) overlay_controls.classList.remove('mc-controls-visible');
		if (playback_controls) playback_controls.classList.remove('mc-controls-visible');
		if (result_controls) result_controls.classList.remove('mc-controls-visible');
	};

	/**
	 * Get the current media stream
	 * @returns {MediaStream|null} The active media stream
	 */
	MediaCapture.prototype.get_stream = function() {
		return this.stream;
	};

	/**
	 * Check if the stream is currently active
	 * @returns {boolean} True if stream is active
	 */
	MediaCapture.prototype.is_stream_active = function() {
		return this.stream !== null && this.stream.active;
	};

	/**
	 * Update configuration options
	 * @param {Object} new_config - New configuration options to merge
	 */
	MediaCapture.prototype.set_config = function(new_config) {
		this.config = Object.assign({}, this.config, new_config || {});
	};

	/**
	 * Get current configuration
	 * @returns {Object} Current configuration object
	 */
	MediaCapture.prototype.get_config = function() {
		return Object.assign({}, this.config);
	};

	/**
	 * Check if video is currently playing
	 * @returns {boolean} True if video is playing
	 */
	MediaCapture.prototype.is_playing = function() {
		if (!this.is_initialized) {
			return false;
		}

		if (this.config.mode === 'video') {
			var video_element = this.element.querySelector('.mc-video');
			return video_element && !video_element.paused && video_element.src && video_element.currentTime > 0;
		} else if (this.config.mode === 'audio') {
			var audio_element = this.element.querySelector('.mc-audio');
			return audio_element && !audio_element.paused && audio_element.src && audio_element.currentTime > 0;
		}

		return false;
	};

	/**
	 * Check if a recorded video is available
	 * @returns {boolean} True if a recorded video exists
	 */
	MediaCapture.prototype.has_video = function() {
		if (!this.is_initialized || this.config.mode !== 'video') {
			return false;
		}

		return this.blob !== null;
	};

	/**
	 * Check if a captured photo is available
	 * @returns {boolean} True if a captured photo exists
	 */
	MediaCapture.prototype.has_photo = function() {
		if (!this.is_initialized || this.config.mode !== 'photo') {
			return false;
		}

		return this.blob !== null;
	};

	/**
	 * Check if audio has been captured
	 * @returns {boolean} True if audio has been captured, false otherwise
	 * @public
	 */
	MediaCapture.prototype.has_audio = function() {
		if (!this.is_initialized || this.config.mode !== 'audio') {
			return false;
		}

		return this.blob !== null;
	};

	/**
	 * Get the captured media as a Blob
	 * @returns {Blob|null} The captured photo or video blob, or null if none exists
	 */
	MediaCapture.prototype.get_blob = function() {
		return this.blob || null;
	};

	/**
	 * Get the captured media as a data URL
	 * @param {Function} callback - Callback function that receives the data URL
	 */
	MediaCapture.prototype.get_data_url = function(callback) {
		if (!this.blob) {
			callback(null);
			return;
		}
		
		var reader = new FileReader();
		reader.onloadend = function() {
			callback(reader.result);
		};
		reader.readAsDataURL(this.blob);
	};

	/**
	 * Get the captured media as an Object URL
	 * @returns {string|null} Object URL for the blob, or null if none exists
	 */
	MediaCapture.prototype.get_object_url = function() {
		if (!this.blob) {
			return null;
		}
		return URL.createObjectURL(this.blob);
	};

	/**
	 * Get the MIME type of the captured media
	 * @returns {string|null} MIME type of the blob, or null if none exists
	 */
	MediaCapture.prototype.get_mime_type = function() {
		if (!this.blob) {
			return null;
		}
		return this.blob.type;
	};

	/**
	 * Get the filename that would be used for the captured media
	 * @returns {string|null} Generated filename, or null if no media captured
	 */
	MediaCapture.prototype.get_filename = function() {
		if (!this.blob) {
			return null;
		}
		
		var timestamp = new Date().toISOString().replace(/[:.]/g, '-');
		var extension = this.config.mode === 'photo' 
			? this.config.photo_format 
			: this.config.video_format;
		
		return this.config.mode + '_' + timestamp + '.' + extension;
	};

	/**
	 * Download the captured media
	 * @param {string} filename - Optional custom filename
	 */
	MediaCapture.prototype.download = function(filename) {
		if (!this.blob) {
			return;
		}
		
		var link = document.createElement('a');
		link.href = URL.createObjectURL(this.blob);
		link.download = filename || this.get_filename();
		link.click();
		URL.revokeObjectURL(link.href);
	};

	/**
	 * Get metadata about the captured media
	 * @returns {Object|null} Object containing metadata, or null if no media captured
	 */
	MediaCapture.prototype.get_meta = function() {
		if (!this.blob) {
			return null;
		}
		
		var meta = {
			type: this.config.mode, // 'photo', 'video', or 'audio'
			size: this.blob.size, // size in bytes
			mime_type: this.blob.type,
			timestamp: new Date().toISOString(),
			filename: this.get_filename()
		};
		
		// Add mode-specific metadata
		if (this.config.mode === 'photo') {
			meta.format = this.config.photo_format;
			meta.quality = this.config.photo_quality;
		} else if (this.config.mode === 'video') {
			meta.format = this.config.video_format;
			
			// Try to get duration from video element if available
			var video_element = this.element.querySelector('.mc-video');
			if (video_element && video_element.duration) {
				meta.duration = video_element.duration; // in seconds
			}
		} else if (this.config.mode === 'audio') {
			meta.format = this.config.audio_format;
			
			// Try to get duration from audio element if available
			var audio_element = this.element.querySelector('.mc-audio');
			if (audio_element && audio_element.duration) {
				meta.duration = audio_element.duration; // in seconds
			}
		}
		
		// Add resolution if it was set
		if (this.config.resolution) {
			meta.resolution = this.config.resolution;
		}
		
		// Add facing mode that was used
		meta.facing_mode = this.config.facing_mode;
		
		return meta;
	};

	/**
	 * Get supported audio and video formats for the current browser
	 * @returns {Object} Object with supported audio and video formats
	 * @public
	 */
	MediaCapture.prototype.get_supported_formats = function() {
		var audio_formats = {
			webm: false,
			mp4: false,
			ogg: false,
			wav: false
		};
		
		var video_formats = {
			webm: false,
			mp4: false
		};
		
		// Check audio format support
		var audio_mimes = {
			webm: ['audio/webm;codecs=opus', 'audio/webm'],
			mp4: ['audio/mp4', 'audio/mp4;codecs=mp4a.40.2'],
			ogg: ['audio/ogg;codecs=opus', 'audio/ogg'],
			wav: ['audio/wav']
		};
		
		for (var audio_format in audio_mimes) {
			for (var i = 0; i < audio_mimes[audio_format].length; i++) {
				if (MediaRecorder.isTypeSupported(audio_mimes[audio_format][i])) {
					audio_formats[audio_format] = true;
					break;
				}
			}
		}
		
		// Check video format support
		var video_mimes = {
			webm: ['video/webm;codecs=vp9,opus', 'video/webm;codecs=vp8,opus', 'video/webm'],
			mp4: ['video/mp4;codecs=h264,aac', 'video/mp4']
		};
		
		for (var video_format in video_mimes) {
			for (var j = 0; j < video_mimes[video_format].length; j++) {
				if (MediaRecorder.isTypeSupported(video_mimes[video_format][j])) {
					video_formats[video_format] = true;
					break;
				}
			}
		}
		
		return {
			audio: audio_formats,
			video: video_formats
		};
	};

	/**
	 * Check if QR code scanning is supported in the current browser
	 * Uses the native BarcodeDetector API
	 * @returns {Promise<boolean>} Promise resolving to true if QR scanning is supported
	 * @public
	 */
	MediaCapture.prototype.is_qr_supported = function() {
		if (!('BarcodeDetector' in window)) {
			return Promise.resolve(false);
		}
		return BarcodeDetector.getSupportedFormats()
			.then(function(formats) {
				return formats.indexOf('qr_code') !== -1;
			})
			.catch(function() {
				return false;
			});
	};

	/**
	 * Start QR code scanning against the live video stream
	 * Only valid in photo mode with qr_scan_enabled = true
	 * Fires on_qr_detected callback and 'qr-detected' custom event when a code is found
	 * @public
	 */
	MediaCapture.prototype.qr_scan_start = function() {
		var self = this;

		if (!this.is_initialized) {
			this.log('qr_scan_start() failed: not initialized', 'warn');
			return;
		}

		if (this.config.mode !== 'photo') {
			this.log('qr_scan_start() is only available in photo mode', 'warn');
			return;
		}

		if (this.qr_scan_active) {
			this.log('qr_scan_start() already scanning', 'warn');
			return;
		}

		if (!('BarcodeDetector' in window)) {
			this.log('qr_scan_start() BarcodeDetector API not available in this browser', 'warn');
			return;
		}

		var video_element = this.video_element;
		if (!video_element) {
			this.log('qr_scan_start() no video element found', 'warn');
			return;
		}

		// Instantiate BarcodeDetector once and reuse it
		try {
			this.qr_barcode_detector = new BarcodeDetector({ formats: ['qr_code'] });
		} catch(e) {
			this.log('qr_scan_start() BarcodeDetector instantiation failed: ' + e.message, 'error');
			return;
		}

		this.qr_scan_active = true;
		this.log('QR scanning started');

		// Show overlay if present
		var container = this.element;
		var qr_overlay = container.querySelector('.mc-qr-overlay');
		if (qr_overlay) qr_overlay.style.display = '';

		// Trigger callback
		if (typeof this.config.on_qr_scan_start === 'function') {
			this.config.on_qr_scan_start();
		}

		// Dispatch event
		this.dispatch_event('qr-scan-start');

		// Screen reader announcement
		var qr_sr = container.querySelector('.mc-qr-sr');
		if (qr_sr) qr_sr.textContent = this.config.label_qr_scanning;

		// Scanning loop
		var scan_interval = this.config.qr_scan_interval;
		var video_el = this.video_element;

		this.qr_scan_interval_id = setInterval(function() {
			self.qr_scan_frame(video_el);
		}, scan_interval);
	};

	/**
	 * Stop QR code scanning
	 * @public
	 */
	MediaCapture.prototype.qr_scan_stop = function() {
		if (!this.qr_scan_active) {
			return;
		}

		if (this.qr_scan_interval_id) {
			clearInterval(this.qr_scan_interval_id);
			this.qr_scan_interval_id = null;
		}

		this.qr_scan_active = false;
		this.qr_barcode_detector = null;

		// Hide overlay if present
		var container = this.element;
		var qr_overlay = container.querySelector('.mc-qr-overlay');
		if (qr_overlay) qr_overlay.style.display = 'none';

		// Clear screen reader announcement
		var qr_sr = container.querySelector('.mc-qr-sr');
		if (qr_sr) qr_sr.textContent = '';

		this.log('QR scanning stopped');

		// Trigger callback
		if (typeof this.config.on_qr_scan_stop === 'function') {
			this.config.on_qr_scan_stop();
		}

		// Dispatch event
		this.dispatch_event('qr-scan-stop');
	};

	/**
	 * Process a single video frame for QR codes
	 * Called on interval by qr_scan_start - not intended for direct use
	 * @param {HTMLVideoElement} video_element - The live video element to scan
	 * @private
	 */
	MediaCapture.prototype.qr_scan_frame = function(video_element) {
		var self = this;

		// Don't scan if video isn't ready
		if (!video_element || video_element.readyState < 2 || !this.qr_barcode_detector) {
			return;
		}

		// Don't scan if stream is gone
		if (!this.stream || !this.stream.active) {
			return;
		}

		this.qr_barcode_detector.detect(video_element)
			.then(function(barcodes) {
				if (!barcodes || barcodes.length === 0) {
					return;
				}

				var result = barcodes[0];
				var raw_value = result.rawValue;

				self.log('QR code detected: ' + raw_value);

				// Play beep if enabled
				if (self.config.qr_scan_beep) {
					self.play_qr_beep();
				}

				// Screen reader announcement
				var container = self.element;
				var qr_sr = container.querySelector('.mc-qr-sr');
				if (qr_sr) qr_sr.textContent = raw_value;

				// Pause scanning if configured
				if (self.config.qr_scan_pause_on_detect) {
					self.qr_scan_stop();
				}

				// Auto-capture if configured
				if (self.config.qr_scan_auto) {
					self.photo_capture();
				}

				// Trigger callback
				if (typeof self.config.on_qr_detected === 'function') {
					self.config.on_qr_detected({
						raw_value: raw_value,
						bounding_box: result.boundingBox,
						corner_points: result.cornerPoints,
						format: result.format
					});
				}

				// Dispatch event
				self.dispatch_event('qr-detected', {
					raw_value: raw_value,
					bounding_box: result.boundingBox,
					corner_points: result.cornerPoints,
					format: result.format
				});
			})
			.catch(function(error) {
				// Detection errors are expected (e.g. frame not ready) - fail silently
				self.log('QR detect error: ' + error.message);
			});
	};

	/**
	 * Play a short beep sound on QR detection
	 * @private
	 */
	MediaCapture.prototype.play_qr_beep = function() {
		try {
			var audio_context = new (window.AudioContext || window.webkitAudioContext)();
			var oscillator = audio_context.createOscillator();
			var gain_node = audio_context.createGain();

			oscillator.connect(gain_node);
			gain_node.connect(audio_context.destination);

			oscillator.frequency.value = 1800;
			oscillator.type = 'sine';

			var now = audio_context.currentTime;
			gain_node.gain.setValueAtTime(0.2, now);
			gain_node.gain.exponentialRampToValueAtTime(0.01, now + 0.1);

			oscillator.start(now);
			oscillator.stop(now + 0.1);
		} catch(e) {
			// Fail silently
		}
	};

	// Expose MediaCapture to global scope
	window.MediaCapture = MediaCapture;

})(window);