Advanced volume control with native Android and iOS implementations
Get Started Now โFull Support
Coming Soon
Development Only
| Android Version | API Level | Support Level | Features |
|---|---|---|---|
| Android 14+ | API 34+ | โ Full | All features, visual media permissions |
| Android 13 | API 33 | โ Full | Granular media permissions |
| Android 10-12 | API 29-32 | โ Full | Scoped storage, external volumes |
| Android 6-9 | API 23-28 | โ Full | Runtime permissions, SD card access |
| Android 5 | API 21-22 | โ ๏ธ Basic | Limited external storage access |
Help me improve this plugin and build better tools for the community!
Support through GitHub's official sponsorship program:
Support via crypto donations across multiple networks:
bc1q2k0ftm2fgst22kzj683e8gpau3spfa23ttkg260xd6f4d8733c8C23e7bEC8Aeba37F4b3D2e93172d10xd6f4d8733c8C23e7bEC8Aeba37F4b3D2e93172d1TXVy781mQ2tCuQ1BrattXWueUHp1wB5fwtGZ8jmSUUzc4dQF7Cthj2atomvpBZWqccR81N9DL4o1BeUQAthXSNIlauj3SrzpDAU4VYxgEVV3niOSmeTPCtMBKGfEAEGive us a star on GitHub to show your support!
Help improve the plugin by reporting bugs and suggesting features.
Contribute to documentation, examples, and tutorials.
Share the plugin with other developers who might find it useful.
Advanced volume control for Capacitor apps with native Android and iOS implementations. Perfect for building music players, audio apps, and any application that needs precise volume management.
Get and set volume levels for different audio streams with precise 0.0-1.0 range control.
Watch for volume changes in real-time with hardware button detection and callbacks.
Android: Suppress volume indicator. iOS: Disable system volume handler.
Uses native Android AudioManager and iOS AVAudioSession for optimal performance.
// Get current volume and set it to 50%
const currentVolume = await VolumeControl.getVolumeLevel();
console.log('Current volume:', currentVolume.value);
await VolumeControl.setVolumeLevel({ value: 0.5 });
// Watch for volume changes
await VolumeControl.watchVolume({
suppressVolumeIndicator: true
}, (event) => {
console.log('Volume changed:', event.direction);
});
npm install @odion-cloud/capacitor-volume-control npx cap sync
For advanced volume control features, add to android/app/src/main/AndroidManifest.xml:
<!-- For volume control features --> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
For iOS volume control features, add to ios/App/App/Info.plist:
<key>NSMicrophoneUsageDescription</key> <string>This app needs access to control audio volume</string>
import { VolumeControl } from '@odion-cloud/capacitor-volume-control';
// Get current volume level (0.0 to 1.0)
const currentVolume = await VolumeControl.getVolumeLevel();
console.log('Current volume:', currentVolume.value);
// Get specific volume type
const musicVolume = await VolumeControl.getVolumeLevel({
type: 'music'
});
// Set volume to 50%
await VolumeControl.setVolumeLevel({ value: 0.5 });
// Set specific volume type
await VolumeControl.setVolumeLevel({
value: 0.8,
type: 'system'
});
// Method 1: Using watchVolume with callback
await VolumeControl.watchVolume({
suppressVolumeIndicator: true, // Android: hide volume UI
disableSystemVolumeHandler: true // iOS: disable system UI
}, (event) => {
console.log('Volume changed:', event.direction);
});
// Method 2: Using event listeners (recommended for frameworks)
const listener = await VolumeControl.addListener('volumeChanged', (event) => {
console.log('Volume changed:', event.direction);
});
// Start watching (required for both methods)
await VolumeControl.watchVolume({
disableSystemVolumeHandler: true,
suppressVolumeIndicator: true
}, () => {}); // Empty callback if using event listeners
// Stop watching
await VolumeControl.clearWatch();
// Remove specific listener
listener.remove();
// Or remove all listeners
await VolumeControl.removeAllListeners();
// Check if watching
const isWatching = await VolumeControl.isWatching();
console.log('Is watching:', isWatching.value);
class MusicPlayer {
constructor() {
this.isWatching = false;
}
async initialize() {
// Get current volume
const volume = await VolumeControl.getVolumeLevel();
this.currentVolume = volume.value;
// Start watching volume changes
await this.startVolumeWatching();
}
async startVolumeWatching() {
if (this.isWatching) return;
await VolumeControl.watchVolume({
suppressVolumeIndicator: true
}, (event) => {
this.handleVolumeChange(event);
});
this.isWatching = true;
}
handleVolumeChange(event) {
this.currentVolume = event.level;
console.log(`Volume ${event.direction}: ${event.level}`);
// Update UI or trigger other actions
this.updateVolumeDisplay(event.level);
}
async setVolume(level) {
await VolumeControl.setVolumeLevel({ value: level });
this.currentVolume = level;
}
}
class AudioMixer {
constructor() {
this.volumeLevels = {
music: 0.5,
notification: 0.3,
system: 0.7
};
}
async setMusicVolume(level) {
await VolumeControl.setVolumeLevel({
value: level,
type: 'music'
});
this.volumeLevels.music = level;
}
async setNotificationVolume(level) {
await VolumeControl.setVolumeLevel({
value: level,
type: 'notification'
});
this.volumeLevels.notification = level;
}
async getCurrentLevels() {
const music = await VolumeControl.getVolumeLevel({ type: 'music' });
const notification = await VolumeControl.getVolumeLevel({ type: 'notification' });
const system = await VolumeControl.getVolumeLevel({ type: 'system' });
return {
music: music.value,
notification: notification.value,
system: system.value
};
}
}
class GameVolumeManager {
constructor() {
this.isMuted = false;
this.previousVolume = 0.5;
}
async initialize() {
// Get current volume
const volume = await VolumeControl.getVolumeLevel();
this.previousVolume = volume.value;
// Start watching for volume changes
await VolumeControl.watchVolume({
disableSystemVolumeHandler: true
}, (event) => {
this.handleGameVolumeChange(event);
});
}
handleGameVolumeChange(event) {
if (this.isMuted) {
// If muted, restore previous volume
VolumeControl.setVolumeLevel({ value: this.previousVolume });
} else {
// Update previous volume
this.previousVolume = event.level;
}
}
async mute() {
this.isMuted = true;
await VolumeControl.setVolumeLevel({ value: 0 });
}
async unmute() {
this.isMuted = false;
await VolumeControl.setVolumeLevel({ value: this.previousVolume });
}
}
import { useEffect, useState } from 'react';
import { VolumeControl } from '@odion-cloud/capacitor-volume-control';
export function useVolumeControl() {
const [volume, setVolume] = useState(0.5);
const [isWatching, setIsWatching] = useState(false);
useEffect(() => {
// Get initial volume
VolumeControl.getVolumeLevel().then(result => {
setVolume(result.value);
});
// Cleanup on unmount
return () => {
VolumeControl.clearWatch();
VolumeControl.removeAllListeners();
};
}, []);
const startWatching = async () => {
if (isWatching) return;
try {
// Add event listener first
await VolumeControl.addListener('volumeChanged', (event) => {
setVolume(event.level);
});
// Start watching with empty callback since we're using event listeners
await VolumeControl.watchVolume({
disableSystemVolumeHandler: true,
suppressVolumeIndicator: true
}, () => {});
setIsWatching(true);
} catch (error) {
console.error('Failed to start watching:', error);
}
};
const stopWatching = async () => {
try {
await VolumeControl.clearWatch();
await VolumeControl.removeAllListeners();
setIsWatching(false);
} catch (error) {
console.error('Failed to stop watching:', error);
}
};
const setVolumeLevel = async (value) => {
try {
await VolumeControl.setVolumeLevel({ value });
setVolume(value);
} catch (error) {
console.error('Failed to set volume:', error);
}
};
return {
volume,
isWatching,
startWatching,
stopWatching,
setVolumeLevel
};
}
import { ref, onMounted, onUnmounted } from 'vue';
import { VolumeControl } from '@odion-cloud/capacitor-volume-control';
export function useVolumeControl() {
const volume = ref(0.5);
const isWatching = ref(false);
let volumeListener = null;
onMounted(async () => {
// Get initial volume
try {
const result = await VolumeControl.getVolumeLevel();
volume.value = result.value;
} catch (error) {
console.error('Failed to get initial volume:', error);
}
});
onUnmounted(async () => {
await stopWatching();
});
const startWatching = async () => {
if (isWatching.value) return;
try {
// Add event listener
volumeListener = await VolumeControl.addListener('volumeChanged', (event) => {
volume.value = event.level;
console.log('Volume changed:', event.direction);
});
// Start watching
await VolumeControl.watchVolume({
disableSystemVolumeHandler: true,
suppressVolumeIndicator: true
}, () => {});
isWatching.value = true;
} catch (error) {
console.error('Failed to start watching:', error);
}
};
const stopWatching = async () => {
try {
await VolumeControl.clearWatch();
if (volumeListener) {
volumeListener.remove();
volumeListener = null;
}
await VolumeControl.removeAllListeners();
isWatching.value = false;
} catch (error) {
console.error('Failed to stop watching:', error);
}
};
const setVolumeLevel = async (value) => {
try {
await VolumeControl.setVolumeLevel({ value });
volume.value = value;
} catch (error) {
console.error('Failed to set volume:', error);
}
};
return {
volume,
isWatching,
startWatching,
stopWatching,
setVolumeLevel
};
}
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { VolumeControl } from '@odion-cloud/capacitor-volume-control';
@Injectable({
providedIn: 'root'
})
export class VolumeService {
private volumeSubject = new BehaviorSubject<number>(0.5);
private isWatchingSubject = new BehaviorSubject<boolean>(false);
private volumeListener = null;
public volume$ = this.volumeSubject.asObservable();
public isWatching$ = this.isWatchingSubject.asObservable();
constructor() {
this.initializeVolume();
}
private async initializeVolume() {
try {
const result = await VolumeControl.getVolumeLevel();
this.volumeSubject.next(result.value);
} catch (error) {
console.error('Failed to get initial volume:', error);
}
}
async startWatching(): Promise<void> {
if (this.isWatchingSubject.value) return;
try {
// Add event listener
this.volumeListener = await VolumeControl.addListener('volumeChanged', (event) => {
this.volumeSubject.next(event.level);
console.log('Volume changed:', event.direction);
});
// Start watching
await VolumeControl.watchVolume({
disableSystemVolumeHandler: true,
suppressVolumeIndicator: true
}, () => {});
this.isWatchingSubject.next(true);
} catch (error) {
console.error('Failed to start watching:', error);
throw error;
}
}
async stopWatching(): Promise<void> {
try {
await VolumeControl.clearWatch();
if (this.volumeListener) {
this.volumeListener.remove();
this.volumeListener = null;
}
await VolumeControl.removeAllListeners();
this.isWatchingSubject.next(false);
} catch (error) {
console.error('Failed to stop watching:', error);
throw error;
}
}
async setVolumeLevel(value: number): Promise<void> {
try {
await VolumeControl.setVolumeLevel({ value });
this.volumeSubject.next(value);
} catch (error) {
console.error('Failed to set volume:', error);
throw error;
}
}
}
Get the current volume level for a specific audio stream.
getVolumeLevel({
type?: VolumeType; // Volume type to get (default: 'music')
}): Promise<VolumeResult>
// Returns: { value: number } (0.0 to 1.0)
Set the volume level for a specific audio stream.
setVolumeLevel({
value: number; // Volume level (0.0 to 1.0)
type?: VolumeType; // Volume type to set (default: 'music')
}): Promise<VolumeResult>
// Returns: { value: number } (the new volume level)
Start watching for volume changes with hardware button detection.
watchVolume({
disableSystemVolumeHandler?: boolean; // iOS: disable system UI
suppressVolumeIndicator?: boolean; // Android: hide volume UI
}, callback: VolumeChangeCallback): Promise<void>
// Callback receives: { direction: 'up' | 'down' }
Add listener for volume change events (recommended for frameworks).
addListener(eventName: 'volumeChanged', callback: VolumeChangeCallback): Promise<PluginListenerHandle> // Returns: PluginListenerHandle with remove() method
Remove all listeners for an event.
removeAllListeners(eventName?: string): Promise<void>
Stop watching for volume changes.
clearWatch(): Promise<void>
Check if volume watching is currently active.
isWatching(): Promise<WatchStatusResult>
// Returns: { value: boolean }
Supported volume types for Android and iOS:
| Volume Type | Description | Platform |
|---|---|---|
VolumeType.MUSIC |
Music, videos, games, and other media | Android, iOS |
VolumeType.SYSTEM |
System sounds and notifications | Android |
VolumeType.RING |
Phone ringtone volume | Android |
VolumeType.NOTIFICATION |
Notification sounds | Android |
VolumeType.ALARM |
Alarm clock volume | Android |
VolumeType.VOICE_CALL |
Voice call volume | Android, iOS |
VolumeType.DTMF |
DTMF tones | Android |
suppressVolumeIndicator - Hide system volume UItype - Control specific volume streamsdisableSystemVolumeHandler - Disable system UItype - Control voice call volumevalue - Volume level (0.0-1.0)type - Volume type selectiondirection - 'up' or 'down'level - New volume levelVolume value must be between 0.0 and 1.0 - Invalid volume levelVolume watching is already active - Multiple watch callsVolume slider not available - iOS setup issueFailed to get volume level - Permission or system errorVolume observer registration failed - Android system issueAudio session setup failed - iOS audio session issueAlways remove event listeners when components unmount to prevent memory leaks.
// In React useEffect cleanup
return () => {
VolumeControl.clearWatch();
VolumeControl.removeAllListeners();
};
Prefer addListener() over callback in watchVolume() for better framework integration.
// Recommended for frameworks
await VolumeControl.addListener('volumeChanged', callback);
await VolumeControl.watchVolume({}, () => {});
Wrap volume operations in try-catch blocks and handle common error scenarios.
try {
await VolumeControl.setVolumeLevel({ value: 0.8 });
} catch (error) {
if (error.message.includes('between 0.0 and 1.0')) {
console.error('Invalid volume value');
}
}
Use isWatching() to avoid duplicate watch calls and manage state properly.
const status = await VolumeControl.isWatching();
if (!status.value) {
await VolumeControl.watchVolume({}, callback);
}
Volume watching and hardware button detection require physical devices for proper testing.
// Run on device for testing npx cap run android npx cap run ios
Avoid frequent volume changes and batch operations when possible.
// Debounce volume changes
const debouncedSetVolume = debounce(
(value) => VolumeControl.setVolumeLevel({ value }),
100
);