Web APIs

JavaScript Battery API: Complete Power Management Guide

Master the Battery API in JavaScript for monitoring device battery status. Learn power-aware features and energy-efficient application design.

By JavaScriptDoc Team
batterypowerdevicemonitoringjavascript

JavaScript Battery API: Complete Power Management Guide

The Battery API provides information about the system's battery charge level and charging status, enabling you to create power-aware applications that adapt their behavior based on battery conditions.

Understanding the Battery API

The Battery API allows web applications to access battery information and respond to changes in battery status, helping optimize performance and user experience.

// Check Battery API support
async function checkBatterySupport() {
  if ('getBattery' in navigator) {
    console.log('Battery API is supported');

    try {
      const battery = await navigator.getBattery();
      console.log('Battery object:', battery);
      return true;
    } catch (error) {
      console.error('Battery API error:', error);
      return false;
    }
  } else {
    console.log('Battery API is not supported');
    return false;
  }
}

// Basic battery information
async function getBatteryInfo() {
  try {
    const battery = await navigator.getBattery();

    console.log('Battery level:', battery.level * 100 + '%');
    console.log('Charging:', battery.charging);
    console.log('Charging time:', battery.chargingTime);
    console.log('Discharging time:', battery.dischargingTime);

    return {
      level: battery.level,
      charging: battery.charging,
      chargingTime: battery.chargingTime,
      dischargingTime: battery.dischargingTime,
    };
  } catch (error) {
    console.error('Failed to get battery info:', error);
    return null;
  }
}

Battery Status Monitoring

Comprehensive Battery Manager

class BatteryManager {
  constructor() {
    this.battery = null;
    this.callbacks = new Map();
    this.thresholds = {
      critical: 0.05, // 5%
      low: 0.15, // 15%
      medium: 0.5, // 50%
      high: 0.8, // 80%
    };
    this.currentLevel = 'unknown';
    this.isInitialized = false;
  }

  // Initialize battery monitoring
  async init() {
    if (!('getBattery' in navigator)) {
      throw new Error('Battery API is not supported');
    }

    try {
      this.battery = await navigator.getBattery();
      this.setupEventListeners();
      this.updateBatteryStatus();
      this.isInitialized = true;

      return this.getStatus();
    } catch (error) {
      console.error('Failed to initialize battery manager:', error);
      throw error;
    }
  }

  // Setup event listeners
  setupEventListeners() {
    if (!this.battery) return;

    // Battery level change
    this.battery.addEventListener('levelchange', () => {
      this.handleLevelChange();
    });

    // Charging status change
    this.battery.addEventListener('chargingchange', () => {
      this.handleChargingChange();
    });

    // Charging time change
    this.battery.addEventListener('chargingtimechange', () => {
      this.handleChargingTimeChange();
    });

    // Discharging time change
    this.battery.addEventListener('dischargingtimechange', () => {
      this.handleDischargingTimeChange();
    });
  }

  // Get current battery status
  getStatus() {
    if (!this.battery) return null;

    return {
      level: this.battery.level,
      percentage: Math.round(this.battery.level * 100),
      charging: this.battery.charging,
      chargingTime: this.formatTime(this.battery.chargingTime),
      dischargingTime: this.formatTime(this.battery.dischargingTime),
      levelCategory: this.getLevelCategory(this.battery.level),
      isLow: this.battery.level <= this.thresholds.low,
      isCritical: this.battery.level <= this.thresholds.critical,
    };
  }

  // Get battery level category
  getLevelCategory(level) {
    if (level <= this.thresholds.critical) return 'critical';
    if (level <= this.thresholds.low) return 'low';
    if (level <= this.thresholds.medium) return 'medium';
    if (level <= this.thresholds.high) return 'high';
    return 'full';
  }

  // Format time in seconds to readable format
  formatTime(seconds) {
    if (seconds === Infinity) return 'Unknown';
    if (seconds < 60) return `${Math.round(seconds)}s`;

    const hours = Math.floor(seconds / 3600);
    const minutes = Math.floor((seconds % 3600) / 60);

    if (hours > 0) {
      return `${hours}h ${minutes}m`;
    }
    return `${minutes}m`;
  }

  // Handle battery level change
  handleLevelChange() {
    const status = this.getStatus();
    const previousLevel = this.currentLevel;
    this.currentLevel = status.levelCategory;

    // Notify level change
    this.notify('levelchange', status);

    // Check for threshold crossings
    if (previousLevel !== this.currentLevel) {
      this.notify('levelcategory', {
        previous: previousLevel,
        current: this.currentLevel,
        status,
      });
    }

    // Critical battery warning
    if (status.isCritical && previousLevel !== 'critical') {
      this.notify('critical', status);
    }

    // Low battery warning
    if (status.isLow && !status.isCritical && previousLevel !== 'low') {
      this.notify('low', status);
    }
  }

  // Handle charging status change
  handleChargingChange() {
    const status = this.getStatus();
    this.notify('chargingchange', status);

    if (status.charging) {
      this.notify('chargingstart', status);
    } else {
      this.notify('chargingstop', status);
    }
  }

  // Handle charging time change
  handleChargingTimeChange() {
    const status = this.getStatus();
    this.notify('chargingtimechange', status);
  }

  // Handle discharging time change
  handleDischargingTimeChange() {
    const status = this.getStatus();
    this.notify('dischargingtimechange', status);

    // Warn if battery won't last long
    if (this.battery.dischargingTime < 1800) {
      // Less than 30 minutes
      this.notify('lowruntime', {
        ...status,
        remainingMinutes: Math.floor(this.battery.dischargingTime / 60),
      });
    }
  }

  // Update battery status
  updateBatteryStatus() {
    this.currentLevel = this.getLevelCategory(this.battery.level);
  }

  // Subscribe to battery events
  on(event, callback) {
    if (!this.callbacks.has(event)) {
      this.callbacks.set(event, []);
    }

    this.callbacks.get(event).push(callback);

    return () => {
      const callbacks = this.callbacks.get(event);
      const index = callbacks.indexOf(callback);
      if (index > -1) {
        callbacks.splice(index, 1);
      }
    };
  }

  // Notify subscribers
  notify(event, data) {
    const callbacks = this.callbacks.get(event) || [];
    callbacks.forEach((callback) => callback(data));
  }

  // Monitor battery with specific interval
  startMonitoring(interval = 60000) {
    // Default: 1 minute
    this.stopMonitoring();

    this.monitoringInterval = setInterval(() => {
      const status = this.getStatus();
      this.notify('update', status);
    }, interval);
  }

  // Stop monitoring
  stopMonitoring() {
    if (this.monitoringInterval) {
      clearInterval(this.monitoringInterval);
      this.monitoringInterval = null;
    }
  }

  // Get battery health estimation
  getBatteryHealth() {
    if (!this.battery) return null;

    const status = this.getStatus();

    // Estimate based on charging/discharging times
    let health = 'good';

    if (this.battery.dischargingTime < 3600) {
      // Less than 1 hour
      health = 'poor';
    } else if (this.battery.dischargingTime < 7200) {
      // Less than 2 hours
      health = 'fair';
    }

    return {
      health,
      estimatedCapacity: this.estimateCapacity(),
      cycleCount: 'unknown', // Not available in API
      temperature: 'unknown', // Not available in API
    };
  }

  // Estimate battery capacity (simplified)
  estimateCapacity() {
    // This is a rough estimation based on discharge time
    if (this.battery.dischargingTime === Infinity) return 'unknown';

    const typicalDischargeTime = 4 * 3600; // 4 hours typical
    const ratio = this.battery.dischargingTime / typicalDischargeTime;

    return Math.min(100, Math.round(ratio * 100)) + '%';
  }
}

// Usage
const batteryManager = new BatteryManager();

// Initialize and monitor
batteryManager.init().then((status) => {
  console.log('Initial battery status:', status);

  // Subscribe to events
  batteryManager.on('levelchange', (status) => {
    console.log('Battery level changed:', status.percentage + '%');
  });

  batteryManager.on('critical', (status) => {
    console.warn('Critical battery level!', status.percentage + '%');
    // Show critical warning to user
  });

  batteryManager.on('chargingstart', (status) => {
    console.log('Charging started');
  });

  // Start periodic monitoring
  batteryManager.startMonitoring(30000); // Every 30 seconds
});

Power-Aware Features

Adaptive Application Behavior

class PowerAwareApp {
  constructor() {
    this.batteryManager = new BatteryManager();
    this.powerMode = 'normal';
    this.features = new Map();
    this.init();
  }

  // Initialize power-aware features
  async init() {
    try {
      await this.batteryManager.init();
      this.setupPowerModes();
      this.applyPowerMode();

      // Monitor battery changes
      this.batteryManager.on('levelcategory', ({ current }) => {
        this.adjustPowerMode(current);
      });

      this.batteryManager.on('chargingchange', (status) => {
        if (status.charging) {
          this.setPowerMode('normal');
        } else {
          this.evaluatePowerMode();
        }
      });
    } catch (error) {
      console.log('Running without battery awareness:', error);
      // Fallback to normal mode
    }
  }

  // Setup power modes
  setupPowerModes() {
    // Define feature configurations for each mode
    this.powerModes = {
      normal: {
        animations: true,
        highQualityImages: true,
        backgroundSync: true,
        autoplay: true,
        prefetch: true,
        analyticsInterval: 60000,
        updateInterval: 30000,
        videoQuality: 'high',
      },
      balanced: {
        animations: true,
        highQualityImages: false,
        backgroundSync: true,
        autoplay: false,
        prefetch: false,
        analyticsInterval: 300000,
        updateInterval: 60000,
        videoQuality: 'medium',
      },
      powersaver: {
        animations: false,
        highQualityImages: false,
        backgroundSync: false,
        autoplay: false,
        prefetch: false,
        analyticsInterval: 600000,
        updateInterval: 300000,
        videoQuality: 'low',
      },
    };
  }

  // Set power mode
  setPowerMode(mode) {
    if (!this.powerModes[mode]) {
      console.error('Invalid power mode:', mode);
      return;
    }

    const previousMode = this.powerMode;
    this.powerMode = mode;

    if (previousMode !== mode) {
      console.log(`Power mode changed: ${previousMode} -> ${mode}`);
      this.applyPowerMode();
      this.notifyPowerModeChange(mode, previousMode);
    }
  }

  // Evaluate and set appropriate power mode
  evaluatePowerMode() {
    const status = this.batteryManager.getStatus();

    if (status.charging) {
      this.setPowerMode('normal');
    } else if (status.isCritical) {
      this.setPowerMode('powersaver');
    } else if (status.isLow) {
      this.setPowerMode('balanced');
    } else {
      this.setPowerMode('normal');
    }
  }

  // Adjust power mode based on battery level
  adjustPowerMode(levelCategory) {
    if (this.batteryManager.battery.charging) return;

    switch (levelCategory) {
      case 'critical':
      case 'low':
        this.setPowerMode('powersaver');
        break;
      case 'medium':
        this.setPowerMode('balanced');
        break;
      case 'high':
      case 'full':
        this.setPowerMode('normal');
        break;
    }
  }

  // Apply power mode settings
  applyPowerMode() {
    const settings = this.powerModes[this.powerMode];

    // Apply animation settings
    this.setAnimations(settings.animations);

    // Apply image quality settings
    this.setImageQuality(settings.highQualityImages);

    // Apply background sync
    this.setBackgroundSync(settings.backgroundSync);

    // Apply autoplay settings
    this.setAutoplay(settings.autoplay);

    // Apply prefetch settings
    this.setPrefetch(settings.prefetch);

    // Apply update intervals
    this.setUpdateIntervals(settings);

    // Apply video quality
    this.setVideoQuality(settings.videoQuality);
  }

  // Toggle animations
  setAnimations(enabled) {
    if (enabled) {
      document.body.classList.remove('reduce-motion');
      // Enable CSS animations
      this.updateCSS(
        `
        * {
          animation-play-state: running !important;
          transition-duration: inherit !important;
        }
      `,
        'animations'
      );
    } else {
      document.body.classList.add('reduce-motion');
      // Disable CSS animations
      this.updateCSS(
        `
        * {
          animation: none !important;
          transition: none !important;
        }
      `,
        'animations'
      );
    }
  }

  // Set image quality
  setImageQuality(highQuality) {
    const images = document.querySelectorAll('img[data-src-low]');

    images.forEach((img) => {
      if (highQuality && img.dataset.srcHigh) {
        img.src = img.dataset.srcHigh;
      } else if (img.dataset.srcLow) {
        img.src = img.dataset.srcLow;
      }
    });

    // Update future image loads
    this.features.set('imageQuality', highQuality ? 'high' : 'low');
  }

  // Set background sync
  setBackgroundSync(enabled) {
    if ('serviceWorker' in navigator && navigator.serviceWorker.controller) {
      navigator.serviceWorker.controller.postMessage({
        type: 'SET_BACKGROUND_SYNC',
        enabled: enabled,
      });
    }

    this.features.set('backgroundSync', enabled);
  }

  // Set autoplay
  setAutoplay(enabled) {
    const videos = document.querySelectorAll('video[autoplay]');

    videos.forEach((video) => {
      if (enabled) {
        video.play().catch(() => {});
      } else {
        video.pause();
        video.removeAttribute('autoplay');
      }
    });

    this.features.set('autoplay', enabled);
  }

  // Set prefetch
  setPrefetch(enabled) {
    const prefetchLinks = document.querySelectorAll('link[rel="prefetch"]');

    prefetchLinks.forEach((link) => {
      if (enabled) {
        link.disabled = false;
      } else {
        link.disabled = true;
      }
    });

    this.features.set('prefetch', enabled);
  }

  // Set update intervals
  setUpdateIntervals(settings) {
    // Notify components about new intervals
    window.dispatchEvent(
      new CustomEvent('powerModeChange', {
        detail: {
          analyticsInterval: settings.analyticsInterval,
          updateInterval: settings.updateInterval,
        },
      })
    );
  }

  // Set video quality
  setVideoQuality(quality) {
    const qualityMap = {
      low: '360p',
      medium: '720p',
      high: '1080p',
    };

    // Update video players
    const videos = document.querySelectorAll('video');
    videos.forEach((video) => {
      if (video.dataset.qualities) {
        const qualities = JSON.parse(video.dataset.qualities);
        const selectedQuality =
          qualities[qualityMap[quality]] || qualities.default;

        if (selectedQuality && video.src !== selectedQuality) {
          const currentTime = video.currentTime;
          video.src = selectedQuality;
          video.currentTime = currentTime;
        }
      }
    });

    this.features.set('videoQuality', quality);
  }

  // Update CSS dynamically
  updateCSS(css, id) {
    let style = document.getElementById(`power-aware-${id}`);

    if (!style) {
      style = document.createElement('style');
      style.id = `power-aware-${id}`;
      document.head.appendChild(style);
    }

    style.textContent = css;
  }

  // Notify about power mode change
  notifyPowerModeChange(newMode, oldMode) {
    window.dispatchEvent(
      new CustomEvent('powerModeChanged', {
        detail: {
          oldMode,
          newMode,
          settings: this.powerModes[newMode],
        },
      })
    );
  }

  // Get current feature status
  getFeatureStatus(feature) {
    return this.features.get(feature);
  }

  // Get power mode
  getPowerMode() {
    return this.powerMode;
  }

  // Get power mode settings
  getPowerModeSettings() {
    return this.powerModes[this.powerMode];
  }
}

// Usage
const powerAwareApp = new PowerAwareApp();

// Listen for power mode changes
window.addEventListener('powerModeChanged', (event) => {
  console.log('Power mode changed:', event.detail);

  // Adjust application behavior
  if (event.detail.newMode === 'powersaver') {
    // Reduce functionality
    disableNonEssentialFeatures();
  } else {
    // Restore functionality
    enableAllFeatures();
  }
});

// Check feature availability
function shouldLoadHighResImages() {
  return powerAwareApp.getFeatureStatus('imageQuality') === 'high';
}

// Adaptive image loading
class AdaptiveImageLoader {
  constructor() {
    this.powerAwareApp = powerAwareApp;
  }

  loadImage(lowResSrc, highResSrc) {
    const img = new Image();

    const shouldUseHighRes =
      this.powerAwareApp.getFeatureStatus('imageQuality') === 'high';

    img.src = shouldUseHighRes ? highResSrc : lowResSrc;
    img.dataset.srcLow = lowResSrc;
    img.dataset.srcHigh = highResSrc;

    return img;
  }
}

Battery Status UI

Visual Battery Indicator

class BatteryIndicator {
  constructor(container) {
    this.container = container;
    this.batteryManager = new BatteryManager();
    this.init();
  }

  // Initialize indicator
  async init() {
    this.createUI();

    try {
      await this.batteryManager.init();
      this.updateUI(this.batteryManager.getStatus());

      // Subscribe to battery updates
      this.batteryManager.on('update', (status) => {
        this.updateUI(status);
      });

      this.batteryManager.on('levelchange', (status) => {
        this.updateUI(status);
        this.animateLevelChange();
      });

      this.batteryManager.on('chargingchange', (status) => {
        this.updateUI(status);
        this.animateChargingChange(status.charging);
      });

      // Start monitoring
      this.batteryManager.startMonitoring(5000);
    } catch (error) {
      this.showError('Battery API not supported');
    }
  }

  // Create UI elements
  createUI() {
    this.container.innerHTML = `
      <div class="battery-widget">
        <div class="battery-icon">
          <div class="battery-body">
            <div class="battery-fill"></div>
            <div class="battery-percentage">--</div>
          </div>
          <div class="battery-cap"></div>
          <svg class="charging-icon" viewBox="0 0 24 24" style="display: none;">
            <path d="M11 20l-5-9h3.5v-7l5 9H11v7z" fill="currentColor"/>
          </svg>
        </div>
        
        <div class="battery-info">
          <div class="battery-status">Unknown</div>
          <div class="battery-time">--</div>
        </div>
        
        <div class="battery-details" style="display: none;">
          <div class="detail-row">
            <span class="label">Level:</span>
            <span class="value level-value">--</span>
          </div>
          <div class="detail-row">
            <span class="label">Status:</span>
            <span class="value status-value">--</span>
          </div>
          <div class="detail-row">
            <span class="label">Time:</span>
            <span class="value time-value">--</span>
          </div>
        </div>
      </div>
    `;

    this.addStyles();
    this.attachEventListeners();
  }

  // Add CSS styles
  addStyles() {
    const style = document.createElement('style');
    style.textContent = `
      .battery-widget {
        display: inline-flex;
        align-items: center;
        gap: 15px;
        padding: 10px;
        background: #f5f5f5;
        border-radius: 8px;
        font-family: system-ui, -apple-system, sans-serif;
        position: relative;
      }

      .battery-icon {
        position: relative;
        width: 50px;
        height: 24px;
      }

      .battery-body {
        width: 44px;
        height: 24px;
        border: 2px solid #333;
        border-radius: 4px;
        position: relative;
        background: #fff;
        overflow: hidden;
      }

      .battery-cap {
        position: absolute;
        right: -4px;
        top: 50%;
        transform: translateY(-50%);
        width: 4px;
        height: 10px;
        background: #333;
        border-radius: 0 2px 2px 0;
      }

      .battery-fill {
        position: absolute;
        left: 0;
        top: 0;
        height: 100%;
        background: #4CAF50;
        transition: width 0.3s ease, background-color 0.3s ease;
        width: 0%;
      }

      .battery-fill.critical {
        background: #f44336;
        animation: pulse 1s infinite;
      }

      .battery-fill.low {
        background: #ff9800;
      }

      .battery-fill.medium {
        background: #ffeb3b;
      }

      .battery-fill.high {
        background: #8bc34a;
      }

      .battery-fill.full {
        background: #4caf50;
      }

      @keyframes pulse {
        0%, 100% { opacity: 1; }
        50% { opacity: 0.5; }
      }

      .battery-percentage {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        font-size: 10px;
        font-weight: bold;
        color: #333;
        z-index: 1;
      }

      .charging-icon {
        position: absolute;
        width: 16px;
        height: 16px;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        color: #fff;
        z-index: 2;
        filter: drop-shadow(0 0 2px rgba(0,0,0,0.5));
      }

      .charging-icon.animating {
        animation: charging-pulse 1.5s ease-in-out infinite;
      }

      @keyframes charging-pulse {
        0%, 100% { transform: translate(-50%, -50%) scale(1); }
        50% { transform: translate(-50%, -50%) scale(1.2); }
      }

      .battery-info {
        display: flex;
        flex-direction: column;
        gap: 4px;
      }

      .battery-status {
        font-weight: 600;
        font-size: 14px;
      }

      .battery-time {
        font-size: 12px;
        color: #666;
      }

      .battery-details {
        position: absolute;
        top: 100%;
        left: 0;
        right: 0;
        margin-top: 10px;
        background: white;
        border: 1px solid #ddd;
        border-radius: 4px;
        padding: 10px;
        box-shadow: 0 2px 8px rgba(0,0,0,0.1);
        z-index: 10;
      }

      .detail-row {
        display: flex;
        justify-content: space-between;
        padding: 4px 0;
        font-size: 12px;
      }

      .detail-row .label {
        color: #666;
      }

      .detail-row .value {
        font-weight: 500;
      }

      .battery-widget.expanded .battery-details {
        display: block !important;
      }

      /* Responsive */
      @media (max-width: 480px) {
        .battery-widget {
          padding: 8px;
          gap: 10px;
        }

        .battery-icon {
          width: 40px;
          height: 20px;
        }

        .battery-body {
          width: 36px;
          height: 20px;
        }
      }
    `;

    document.head.appendChild(style);
  }

  // Attach event listeners
  attachEventListeners() {
    this.container.addEventListener('click', () => {
      this.toggleDetails();
    });
  }

  // Update UI with battery status
  updateUI(status) {
    if (!status) return;

    // Update fill level
    const fill = this.container.querySelector('.battery-fill');
    fill.style.width = `${status.percentage}%`;
    fill.className = `battery-fill ${status.levelCategory}`;

    // Update percentage text
    const percentage = this.container.querySelector('.battery-percentage');
    percentage.textContent = `${status.percentage}%`;

    // Update charging icon
    const chargingIcon = this.container.querySelector('.charging-icon');
    chargingIcon.style.display = status.charging ? 'block' : 'none';

    // Update status text
    const statusText = this.container.querySelector('.battery-status');
    statusText.textContent = this.getStatusText(status);

    // Update time text
    const timeText = this.container.querySelector('.battery-time');
    timeText.textContent = this.getTimeText(status);

    // Update details
    this.updateDetails(status);
  }

  // Get status text
  getStatusText(status) {
    if (status.charging) {
      return 'Charging';
    }

    switch (status.levelCategory) {
      case 'critical':
        return 'Critical Battery';
      case 'low':
        return 'Low Battery';
      case 'medium':
        return 'Battery OK';
      case 'high':
      case 'full':
        return 'Battery Good';
      default:
        return 'Unknown';
    }
  }

  // Get time text
  getTimeText(status) {
    if (status.charging) {
      return status.chargingTime === 'Unknown'
        ? 'Calculating...'
        : `Full in ${status.chargingTime}`;
    } else {
      return status.dischargingTime === 'Unknown'
        ? 'Calculating...'
        : `${status.dischargingTime} remaining`;
    }
  }

  // Update details panel
  updateDetails(status) {
    this.container.querySelector('.level-value').textContent =
      `${status.percentage}%`;

    this.container.querySelector('.status-value').textContent = status.charging
      ? 'Charging'
      : 'Discharging';

    this.container.querySelector('.time-value').textContent = status.charging
      ? status.chargingTime
      : status.dischargingTime;
  }

  // Toggle details view
  toggleDetails() {
    const widget = this.container.querySelector('.battery-widget');
    widget.classList.toggle('expanded');
  }

  // Animate level change
  animateLevelChange() {
    const fill = this.container.querySelector('.battery-fill');
    fill.style.transition = 'width 0.5s ease, background-color 0.5s ease';
  }

  // Animate charging state change
  animateChargingChange(isCharging) {
    const icon = this.container.querySelector('.charging-icon');

    if (isCharging) {
      icon.classList.add('animating');
    } else {
      icon.classList.remove('animating');
    }
  }

  // Show error message
  showError(message) {
    this.container.innerHTML = `
      <div class="battery-widget error">
        <span class="error-message">${message}</span>
      </div>
    `;
  }
}

// Usage
const batteryContainer = document.getElementById('battery-indicator');
const batteryIndicator = new BatteryIndicator(batteryContainer);

// Create minimal battery indicator
class MinimalBatteryIndicator {
  constructor() {
    this.create();
  }

  create() {
    const indicator = document.createElement('div');
    indicator.className = 'minimal-battery';
    indicator.style.cssText = `
      position: fixed;
      top: 10px;
      right: 10px;
      padding: 5px 10px;
      background: rgba(0,0,0,0.8);
      color: white;
      border-radius: 4px;
      font-size: 12px;
      z-index: 9999;
    `;

    document.body.appendChild(indicator);

    // Update battery status
    navigator.getBattery().then((battery) => {
      const update = () => {
        const percentage = Math.round(battery.level * 100);
        const charging = battery.charging ? '⚡' : '';
        indicator.textContent = `${charging}${percentage}%`;

        // Color based on level
        if (percentage <= 20) {
          indicator.style.background = 'rgba(244,67,54,0.9)';
        } else if (percentage <= 50) {
          indicator.style.background = 'rgba(255,152,0,0.9)';
        } else {
          indicator.style.background = 'rgba(0,0,0,0.8)';
        }
      };

      update();
      battery.addEventListener('levelchange', update);
      battery.addEventListener('chargingchange', update);
    });
  }
}

// Create minimal indicator
if (window.location.search.includes('battery=minimal')) {
  new MinimalBatteryIndicator();
}

Battery-Aware Media

Adaptive Media Playback

class BatteryAwareMediaPlayer {
  constructor(videoElement) {
    this.video = videoElement;
    this.batteryManager = new BatteryManager();
    this.qualityLevels = ['auto', '1080p', '720p', '480p', '360p'];
    this.currentQuality = 'auto';
    this.init();
  }

  async init() {
    try {
      await this.batteryManager.init();
      this.setupQualityControl();
      this.monitorBattery();
    } catch (error) {
      console.log('Battery-aware features disabled:', error);
    }
  }

  setupQualityControl() {
    // Create quality selector
    const controls = document.createElement('div');
    controls.className = 'battery-aware-controls';
    controls.innerHTML = `
      <select class="quality-selector">
        ${this.qualityLevels
          .map((q) => `<option value="${q}">${q}</option>`)
          .join('')}
      </select>
      <span class="battery-mode">Auto</span>
    `;

    this.video.parentElement.appendChild(controls);

    // Handle manual quality selection
    controls
      .querySelector('.quality-selector')
      .addEventListener('change', (e) => {
        this.setQuality(e.target.value);
      });
  }

  monitorBattery() {
    // Adjust quality based on battery
    this.batteryManager.on('levelcategory', ({ current, status }) => {
      if (this.currentQuality === 'auto') {
        this.autoAdjustQuality(current, status);
      }
    });

    // Pause on critical battery
    this.batteryManager.on('critical', () => {
      if (!this.video.paused) {
        this.video.pause();
        this.showBatteryWarning();
      }
    });
  }

  autoAdjustQuality(level, status) {
    let quality;

    if (status.charging) {
      quality = '1080p';
    } else {
      switch (level) {
        case 'critical':
          quality = '360p';
          break;
        case 'low':
          quality = '480p';
          break;
        case 'medium':
          quality = '720p';
          break;
        default:
          quality = '1080p';
      }
    }

    this.applyQuality(quality);
    this.updateModeIndicator(quality, level);
  }

  setQuality(quality) {
    this.currentQuality = quality;

    if (quality === 'auto') {
      const status = this.batteryManager.getStatus();
      this.autoAdjustQuality(status.levelCategory, status);
    } else {
      this.applyQuality(quality);
    }
  }

  applyQuality(quality) {
    // Implementation depends on video source
    console.log('Setting video quality to:', quality);

    // For HLS/DASH streams
    if (this.video.hlsPlayer) {
      const levelIndex = this.getQualityIndex(quality);
      this.video.hlsPlayer.currentLevel = levelIndex;
    }

    // For multiple source elements
    const sources = this.video.querySelectorAll('source');
    sources.forEach((source) => {
      if (source.dataset.quality === quality) {
        const currentTime = this.video.currentTime;
        this.video.src = source.src;
        this.video.currentTime = currentTime;
        this.video.play();
      }
    });
  }

  getQualityIndex(quality) {
    const qualityMap = {
      '360p': 0,
      '480p': 1,
      '720p': 2,
      '1080p': 3,
    };
    return qualityMap[quality] || -1; // -1 for auto
  }

  updateModeIndicator(quality, batteryLevel) {
    const indicator = this.video.parentElement.querySelector('.battery-mode');
    indicator.textContent = `${quality} (Battery: ${batteryLevel})`;
  }

  showBatteryWarning() {
    const warning = document.createElement('div');
    warning.className = 'battery-warning';
    warning.innerHTML = `
      <p>Video paused due to critical battery level</p>
      <button onclick="this.parentElement.remove()">Dismiss</button>
    `;
    warning.style.cssText = `
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      background: rgba(244,67,54,0.9);
      color: white;
      padding: 20px;
      border-radius: 8px;
      text-align: center;
      z-index: 100;
    `;

    this.video.parentElement.appendChild(warning);
  }
}

// Usage
const video = document.querySelector('video');
const batteryAwarePlayer = new BatteryAwareMediaPlayer(video);

Best Practices

  1. Always check for API support

    if ('getBattery' in navigator) {
      // Use Battery API
    } else {
      // Provide fallback
    }
    
  2. Handle Promise rejections

    navigator
      .getBattery()
      .then((battery) => {
        // Use battery
      })
      .catch((error) => {
        // Handle error gracefully
      });
    
  3. Don't rely solely on Battery API

    // Provide manual power mode selection
    const powerModeSelector = createPowerModeSelector();
    
  4. Respect user preferences

    // Allow users to override automatic adjustments
    if (userPreferences.ignoreBattery) {
      return;
    }
    

Browser Compatibility

The Battery API has limited support and has been deprecated in many browsers due to privacy concerns. Always provide fallbacks:

class BatteryAPIFallback {
  constructor() {
    this.level = 1;
    this.charging = true;
    this.chargingTime = Infinity;
    this.dischargingTime = Infinity;
  }

  async getBattery() {
    // Return mock battery object
    return {
      level: this.level,
      charging: this.charging,
      chargingTime: this.chargingTime,
      dischargingTime: this.dischargingTime,
      addEventListener: () => {},
      removeEventListener: () => {},
    };
  }
}

// Polyfill
if (!('getBattery' in navigator)) {
  navigator.getBattery = new BatteryAPIFallback().getBattery;
}

Conclusion

The Battery API enables power-aware web applications that:

  • Adapt performance based on battery level
  • Save energy with intelligent feature management
  • Improve UX with battery status indicators
  • Prevent data loss with low battery warnings
  • Optimize media playback quality
  • Extend battery life with power-saving modes

Key takeaways:

  • Always provide fallbacks for unsupported browsers
  • Allow users to override automatic adjustments
  • Test power-saving features thoroughly
  • Consider privacy implications
  • Monitor browser compatibility changes
  • Design for progressive enhancement

Build considerate applications that respect device resources and user battery life!