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.
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
-
Always check for API support
if ('getBattery' in navigator) { // Use Battery API } else { // Provide fallback }
-
Handle Promise rejections
navigator .getBattery() .then((battery) => { // Use battery }) .catch((error) => { // Handle error gracefully });
-
Don't rely solely on Battery API
// Provide manual power mode selection const powerModeSelector = createPowerModeSelector();
-
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!