Add detailed documentation for camera switching functionality across web and mobile platforms: - Add camera management interfaces to QRScannerService - Document MLKit Barcode Scanner configuration for Capacitor - Add platform-specific implementations for iOS and Android - Include camera state management and error handling - Add performance optimization guidelines - Document testing requirements and scenarios Key additions: - Camera switching implementation for both platforms - Platform-specific considerations (iOS/Android) - Battery and memory optimization strategies - Comprehensive testing guidelines - Error handling and state management - Security and permission considerations This update provides a complete reference for implementing robust camera switching functionality in the QR code scanner.
21 KiB
QR Code Implementation Guide
Overview
This document describes the QR code scanning and generation implementation in the TimeSafari application. The system uses a platform-agnostic design with specific implementations for web and mobile platforms.
Architecture
Directory Structure
src/
├── services/
│ └── QRScanner/
│ ├── types.ts # Core interfaces and types
│ ├── QRScannerFactory.ts # Factory for creating scanner instances
│ ├── CapacitorQRScanner.ts # Mobile implementation using MLKit
│ ├── WebInlineQRScanner.ts # Web implementation using MediaDevices API
│ └── interfaces.ts # Additional interfaces
├── components/
│ └── QRScanner/
│ └── QRScannerDialog.vue # Shared UI component
└── views/
├── ContactQRScanView.vue # Dedicated scanning view
└── ContactQRScanShowView.vue # Combined QR display and scanning view
Core Components
-
Factory Pattern
QRScannerFactory- Creates appropriate scanner instance based on platform- Common interface
QRScannerServiceimplemented by all scanners - Platform detection via Capacitor and build flags
-
Platform-Specific Implementations
CapacitorQRScanner- Native mobile implementation using MLKitWebInlineQRScanner- Web browser implementation using MediaDevices APIQRScannerDialog.vue- Shared UI component
-
View Components
ContactQRScanView- Dedicated view for scanning QR codesContactQRScanShowView- Combined view for displaying and scanning QR codes
Implementation Details
Core Interfaces
interface QRScannerService {
checkPermissions(): Promise<boolean>;
requestPermissions(): Promise<boolean>;
isSupported(): Promise<boolean>;
startScan(options?: QRScannerOptions): Promise<void>;
stopScan(): Promise<void>;
addListener(listener: ScanListener): void;
onStream(callback: (stream: MediaStream | null) => void): void;
cleanup(): Promise<void>;
getAvailableCameras(): Promise<MediaDeviceInfo[]>;
switchCamera(deviceId: string): Promise<void>;
getCurrentCamera(): Promise<MediaDeviceInfo | null>;
}
interface ScanListener {
onScan: (result: string) => void;
onError?: (error: Error) => void;
}
interface QRScannerOptions {
camera?: "front" | "back";
showPreview?: boolean;
playSound?: boolean;
}
Platform-Specific Implementations
Mobile (Capacitor)
- Uses
@capacitor-mlkit/barcode-scanning - Native camera access through platform APIs
- Optimized for mobile performance
- Supports both iOS and Android
- Real-time QR code detection
- Back camera preferred for scanning
Configuration:
// capacitor.config.ts
const config: CapacitorConfig = {
plugins: {
MLKitBarcodeScanner: {
formats: ['QR_CODE'],
detectorSize: 1.0,
lensFacing: 'back',
googleBarcodeScannerModuleInstallState: true,
// Additional camera options
cameraOptions: {
quality: 0.8,
allowEditing: false,
resultType: 'uri',
sourceType: 'CAMERA',
saveToGallery: false
}
}
}
};
Web
- Uses browser's MediaDevices API
- Vue.js components for UI
- EventEmitter for stream management
- Browser-based camera access
- Inline camera preview
- Responsive design
- Cross-browser compatibility
View Components
ContactQRScanView
- Dedicated view for scanning QR codes
- Full-screen camera interface
- Simple UI focused on scanning
- Used primarily on native platforms
- Streamlined scanning experience
ContactQRScanShowView
- Combined view for QR code display and scanning
- Shows user's own QR code
- Handles user registration status
- Provides options to copy contact information
- Platform-specific scanning implementation:
- Native: Button to navigate to ContactQRScanView
- Web: Built-in scanning functionality
QR Code Workflow
-
Initiation
- User selects "Scan QR Code" option
- Platform-specific scanner is initialized
- Camera permissions are verified
- Appropriate scanner component is loaded
-
Platform-Specific Implementation
- Web: Uses
qrcode-streamfor real-time scanning - Native: Uses
@capacitor-mlkit/barcode-scanning
- Web: Uses
-
Scanning Process
- Camera stream initialization
- Real-time frame analysis
- QR code detection and decoding
- Validation of QR code format
- Processing of contact information
-
Contact Processing
- Decryption of contact data
- Validation of user information
- Verification of timestamp
- Check for duplicate contacts
- Processing of shared data
Build Configuration
Common Vite Configuration
// vite.config.common.mts
export async function createBuildConfig(mode: string) {
const isCapacitor = mode === "capacitor";
return defineConfig({
define: {
'process.env.VITE_PLATFORM': JSON.stringify(mode),
'process.env.VITE_PWA_ENABLED': JSON.stringify(!isNative),
__IS_MOBILE__: JSON.stringify(isCapacitor),
__USE_QR_READER__: JSON.stringify(!isCapacitor)
},
optimizeDeps: {
include: [
'@capacitor-mlkit/barcode-scanning',
'vue-qrcode-reader'
]
}
});
}
Platform-Specific Builds
{
"scripts": {
"build:web": "vite build --config vite.config.web.mts",
"build:capacitor": "vite build --config vite.config.capacitor.mts",
"build:all": "npm run build:web && npm run build:capacitor"
}
}
Error Handling
Common Error Scenarios
- No camera found
- Permission denied
- Camera in use by another application
- HTTPS required
- Browser compatibility issues
- Invalid QR code format
- Expired QR codes
- Duplicate contact attempts
- Network connectivity issues
Error Response
- User-friendly error messages
- Troubleshooting tips
- Clear instructions for resolution
- Platform-specific guidance
Security Considerations
QR Code Security
- Encryption of contact data
- Timestamp validation
- Version checking
- User verification
- Rate limiting for scans
Data Protection
- Secure transmission of contact data
- Validation of QR code authenticity
- Prevention of duplicate scans
- Protection against malicious codes
- Secure storage of contact information
Best Practices
Camera Access
- Always check for camera availability
- Request permissions explicitly
- Handle all error conditions
- Provide clear user feedback
- Implement proper cleanup
Performance
- Optimize camera resolution
- Implement proper resource cleanup
- Handle camera switching efficiently
- Manage memory usage
- Battery usage optimization
User Experience
- Clear visual feedback
- Camera preview
- Scanning status indicators
- Error messages
- Success confirmations
- Intuitive camera controls
- Smooth camera switching
- Responsive UI feedback
Testing
Test Scenarios
- Permission handling
- Camera switching
- Error conditions
- Platform compatibility
- Performance metrics
- QR code detection
- Contact processing
- Security validation
Test Environment
- Multiple browsers
- iOS and Android devices
- Various network conditions
- Different camera configurations
Dependencies
Key Packages
@capacitor-mlkit/barcode-scanningqrcode-streamvue-qrcode-reader- Platform-specific camera APIs
Maintenance
Regular Updates
- Keep dependencies updated
- Monitor platform changes
- Update documentation
- Review security patches
Performance Monitoring
- Track memory usage
- Monitor camera performance
- Check error rates
- Analyze user feedback
Camera Handling
Camera Switching Implementation
The QR scanner supports camera switching on both mobile and desktop platforms through a unified interface.
Platform-Specific Implementations
-
Mobile (Capacitor)
- Uses
@capacitor-mlkit/barcode-scanning - Supports front/back camera switching
- Native camera access through platform APIs
- Optimized for mobile performance
// CapacitorQRScanner.ts async startScan(options?: QRScannerOptions): Promise<void> { const scanOptions: StartScanOptions = { formats: [BarcodeFormat.QrCode], lensFacing: options?.camera === "front" ? LensFacing.Front : LensFacing.Back }; await BarcodeScanner.startScan(scanOptions); } - Uses
-
Web (Desktop)
- Uses browser's MediaDevices API
- Supports multiple camera devices
- Dynamic camera enumeration
- Real-time camera switching
// WebInlineQRScanner.ts async getAvailableCameras(): Promise<MediaDeviceInfo[]> { const devices = await navigator.mediaDevices.enumerateDevices(); return devices.filter(device => device.kind === 'videoinput'); } async switchCamera(deviceId: string): Promise<void> { // Stop current stream await this.stopScan(); // Start new stream with selected camera this.stream = await navigator.mediaDevices.getUserMedia({ video: { deviceId: { exact: deviceId }, width: { ideal: 1280 }, height: { ideal: 720 } } }); // Update video and restart scanning if (this.video) { this.video.srcObject = this.stream; await this.video.play(); } this.scanQRCode(); }
Core Interfaces
interface QRScannerService {
// ... existing methods ...
/** Get available cameras */
getAvailableCameras(): Promise<MediaDeviceInfo[]>;
/** Switch to a specific camera */
switchCamera(deviceId: string): Promise<void>;
/** Get current camera info */
getCurrentCamera(): Promise<MediaDeviceInfo | null>;
}
interface QRScannerOptions {
/** Camera to use ('front' or 'back' for mobile) */
camera?: "front" | "back";
/** Whether to show a preview of the camera feed */
showPreview?: boolean;
/** Whether to play a sound on successful scan */
playSound?: boolean;
}
UI Components
The camera switching UI adapts to the platform:
-
Mobile Interface
- Simple toggle button for front/back cameras
- Positioned in bottom-right corner
- Clear visual feedback during switching
- Native camera controls
<button v-if="isNativePlatform" @click="toggleMobileCamera" class="camera-switch-btn" > <font-awesome icon="camera-rotate" /> Switch Camera </button> -
Desktop Interface
- Dropdown menu with all available cameras
- Camera labels and device IDs
- Real-time camera switching
- Responsive design
<select v-model="selectedCameraId" @change="onCameraChange" class="camera-select-dropdown" > <option v-for="camera in availableCameras" :key="camera.deviceId" :value="camera.deviceId" > {{ camera.label || `Camera ${camera.deviceId.slice(0, 4)}` }} </option> </select>
Error Handling
The camera switching implementation includes comprehensive error handling:
-
Common Error Scenarios
- Camera in use by another application
- Permission denied during switch
- Device not available
- Stream initialization failure
- Camera switch timeout
-
Error Response
private async handleCameraSwitch(deviceId: string): Promise<void> { try { this.updateCameraState("initializing", "Switching camera..."); await this.switchCamera(deviceId); this.updateCameraState("active", "Camera switched successfully"); } catch (error) { this.updateCameraState("error", "Failed to switch camera"); throw error; } } -
User Feedback
- Visual indicators during switching
- Error notifications
- Camera state updates
- Permission request dialogs
State Management
The camera system maintains several states:
-
Camera States
type CameraState = | "initializing" // Camera is being initialized | "ready" // Camera is ready to use | "active" // Camera is actively streaming | "in_use" // Camera is in use by another application | "permission_denied" // Camera permission was denied | "not_found" // No camera found on device | "error" // Generic error state | "off"; // Camera is off -
State Transitions
- Initialization → Ready
- Ready → Active
- Active → Switching
- Switching → Active/Error
- Any state → Off (on cleanup)
Best Practices
-
Camera Access
- Always check permissions before switching
- Handle camera busy states
- Implement proper cleanup
- Monitor camera state changes
-
Performance
- Optimize camera resolution
- Handle stream switching efficiently
- Manage memory usage
- Implement proper cleanup
-
User Experience
- Clear visual feedback
- Smooth camera transitions
- Intuitive camera controls
- Responsive UI updates
- Accessible camera selection
-
Security
- Secure camera access
- Permission management
- Device validation
- Stream security
Testing
-
Test Scenarios
- Camera switching on both platforms
- Permission handling
- Error conditions
- Multiple camera devices
- Camera busy states
- Stream initialization
- UI responsiveness
-
Test Environment
- Multiple mobile devices
- Various desktop browsers
- Different camera configurations
- Network conditions
- Permission states
Capacitor Implementation Details
MLKit Barcode Scanner Configuration
-
Plugin Setup
// capacitor.config.ts const config: CapacitorConfig = { plugins: { MLKitBarcodeScanner: { formats: ['QR_CODE'], detectorSize: 1.0, lensFacing: 'back', googleBarcodeScannerModuleInstallState: true, // Additional camera options cameraOptions: { quality: 0.8, allowEditing: false, resultType: 'uri', sourceType: 'CAMERA', saveToGallery: false } } } }; -
Camera Management
// CapacitorQRScanner.ts export class CapacitorQRScanner implements QRScannerService { private currentLensFacing: LensFacing = LensFacing.Back; async getAvailableCameras(): Promise<MediaDeviceInfo[]> { // On mobile, we have two fixed cameras return [ { deviceId: 'back', label: 'Back Camera', kind: 'videoinput' }, { deviceId: 'front', label: 'Front Camera', kind: 'videoinput' } ] as MediaDeviceInfo[]; } async switchCamera(deviceId: string): Promise<void> { if (!this.isScanning) return; const newLensFacing = deviceId === 'front' ? LensFacing.Front : LensFacing.Back; // Stop current scan await this.stopScan(); // Update lens facing this.currentLensFacing = newLensFacing; // Restart scan with new camera await this.startScan({ camera: deviceId as 'front' | 'back' }); } async getCurrentCamera(): Promise<MediaDeviceInfo | null> { return { deviceId: this.currentLensFacing === LensFacing.Front ? 'front' : 'back', label: this.currentLensFacing === LensFacing.Front ? 'Front Camera' : 'Back Camera', kind: 'videoinput' } as MediaDeviceInfo; } } -
Camera State Management
// CapacitorQRScanner.ts private async handleCameraState(): Promise<void> { try { // Check if camera is available const { camera } = await BarcodeScanner.checkPermissions(); if (camera === 'denied') { this.updateCameraState('permission_denied'); return; } // Check if camera is in use const isInUse = await this.isCameraInUse(); if (isInUse) { this.updateCameraState('in_use'); return; } this.updateCameraState('ready'); } catch (error) { this.updateCameraState('error', error.message); } } private async isCameraInUse(): Promise<boolean> { try { // Try to start a test scan await BarcodeScanner.startScan({ formats: [BarcodeFormat.QrCode], lensFacing: this.currentLensFacing }); // If successful, stop it immediately await BarcodeScanner.stopScan(); return false; } catch (error) { return error.message.includes('camera in use'); } } -
Error Handling
// CapacitorQRScanner.ts private async handleCameraError(error: Error): Promise<void> { switch (error.name) { case 'CameraPermissionDenied': this.updateCameraState('permission_denied'); break; case 'CameraInUse': this.updateCameraState('in_use'); break; case 'CameraUnavailable': this.updateCameraState('not_found'); break; default: this.updateCameraState('error', error.message); } }
Platform-Specific Considerations
-
iOS Implementation
- Camera permissions in Info.plist
- Privacy descriptions
- Camera usage description
- Background camera access
<!-- ios/App/App/Info.plist --> <key>NSCameraUsageDescription</key> <string>We need access to your camera to scan QR codes</string> <key>NSPhotoLibraryUsageDescription</key> <string>We need access to save scanned QR codes</string> -
Android Implementation
- Camera permissions in AndroidManifest.xml
- Runtime permission handling
- Camera features declaration
- Hardware feature requirements
<!-- android/app/src/main/AndroidManifest.xml --> <uses-permission android:name="android.permission.CAMERA" /> <uses-feature android:name="android.hardware.camera" /> <uses-feature android:name="android.hardware.camera.autofocus" /> -
Platform-Specific Features
- iOS: Camera orientation handling
- Android: Camera resolution optimization
- Both: Battery usage optimization
- Both: Memory management
// Platform-specific optimizations private getPlatformSpecificOptions(): StartScanOptions { const baseOptions: StartScanOptions = { formats: [BarcodeFormat.QrCode], lensFacing: this.currentLensFacing }; if (Capacitor.getPlatform() === 'ios') { return { ...baseOptions, // iOS-specific options cameraOptions: { quality: 0.7, // Lower quality for better performance allowEditing: false, resultType: 'uri' } }; } else if (Capacitor.getPlatform() === 'android') { return { ...baseOptions, // Android-specific options cameraOptions: { quality: 0.8, allowEditing: false, resultType: 'uri', saveToGallery: false } }; } return baseOptions; }
Performance Optimization
-
Battery Usage
// CapacitorQRScanner.ts private optimizeBatteryUsage(): void { // Reduce scan frequency when battery is low if (this.isLowBattery()) { this.scanInterval = 2000; // 2 seconds between scans } else { this.scanInterval = 1000; // 1 second between scans } } private isLowBattery(): boolean { // Check battery level if available if (Capacitor.isPluginAvailable('Battery')) { const { level } = await Battery.getBatteryLevel(); return level < 0.2; // 20% or lower } return false; } -
Memory Management
// CapacitorQRScanner.ts private async cleanupResources(): Promise<void> { // Stop scanning await this.stopScan(); // Clear any stored camera data this.currentLensFacing = LensFacing.Back; // Remove listeners this.listenerHandles.forEach(handle => handle()); this.listenerHandles = []; // Reset state this.isScanning = false; this.updateCameraState('off'); }
Testing on Capacitor
-
Device Testing
- Test on multiple iOS devices
- Test on multiple Android devices
- Test different camera configurations
- Test with different screen sizes
- Test with different OS versions
-
Camera Testing
- Test front camera switching
- Test back camera switching
- Test camera permissions
- Test camera in use scenarios
- Test low light conditions
- Test different QR code sizes
- Test different QR code distances
-
Performance Testing
- Battery usage monitoring
- Memory usage monitoring
- Camera switching speed
- QR code detection speed
- App responsiveness
- Background/foreground transitions