Advanced volume control with native Android and iOS implementations
Get Started Now โFull Support
Full Support
Development Only
npm install @odion-cloud/capacitor-volume-control and npx cap sync.
| Android Version | API Level | Support Level | Features |
|---|---|---|---|
| Android 14+ | API 34+ | โ Full | All volume features, MediaSession button detection |
| Android 13 | API 33 | โ Full | Full volume control & button watching |
| Android 10-12 | API 29-32 | โ Full | AudioManager, volume streams, button detection |
| Android 6-9 | API 23-28 | โ Full | Runtime permissions, all volume types |
| Android 5 | API 21-22 | โ ๏ธ Basic | Basic volume control, limited button support |
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 using addListener('volumeButtonPressed', callback) then watchVolume(options). No callback on watchVolume() (v2.0+).
Android: Suppress volume indicator. iOS: Disable system volume handler.
Uses native Android AudioManager and iOS AVAudioSession for optimal performance.
watchVolume() no longer accepts a callback. Use addListener('volumeButtonPressed', callback) before calling watchVolume(options). See migration in CHANGELOG.
// 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 hardware volume button presses (v2.0+ pattern)
await VolumeControl.addListener('volumeButtonPressed', (event) => {
console.log('Volume button:', event.direction);
});
await VolumeControl.watchVolume({ suppressVolumeIndicator: true });
npm install @odion-cloud/capacitor-volume-control npx cap sync
@capacitor-community/volume-buttons (MediaSession). Buttons work automatically on Android and iOS.
For 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'
});
watchVolume() no longer accepts a callback. Add a listener first, then start watching.
// 1. Add listener for hardware volume button presses
const listener = await VolumeControl.addListener('volumeButtonPressed', (event) => {
console.log('Volume button:', event.direction); // 'up' | 'down'
if (event.value !== undefined) console.log('Level:', event.value);
});
// 2. Start watching (no callback parameter)
await VolumeControl.watchVolume({
suppressVolumeIndicator: true, // Android: hide volume UI
disableSystemVolumeHandler: true // iOS: disable system UI
});
// Optional: also listen for volume level changes (e.g. from setVolumeLevel)
await VolumeControl.addListener('volumeLevelChanged', (event) => {
console.log('Level changed:', event.value, event.direction);
});
// 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.addListener('volumeButtonPressed', (event) => {
this.handleVolumeChange(event);
});
await VolumeControl.watchVolume({ suppressVolumeIndicator: true });
this.isWatching = true;
}
handleVolumeChange(event) {
if (event.value !== undefined) this.currentVolume = event.value;
console.log(`Volume ${event.direction}: ${event.value ?? this.currentVolume}`);
// Update UI or trigger other actions
this.updateVolumeDisplay(event.value ?? this.currentVolume);
}
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 button presses
await VolumeControl.addListener('volumeButtonPressed', (event) => {
this.handleGameVolumeChange(event);
});
await VolumeControl.watchVolume({ disableSystemVolumeHandler: true });
}
handleGameVolumeChange(event) {
if (this.isMuted) {
VolumeControl.setVolumeLevel({ value: this.previousVolume });
} else if (event.value !== undefined) {
this.previousVolume = event.value;
}
}
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 {
await VolumeControl.addListener('volumeButtonPressed', (event) => {
if (event.value !== undefined) setVolume(event.value);
});
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 {
volumeListener = await VolumeControl.addListener('volumeButtonPressed', (event) => {
if (event.value !== undefined) volume.value = event.value;
console.log('Volume button:', event.direction);
});
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 {
this.volumeListener = await VolumeControl.addListener('volumeButtonPressed', (event) => {
if (event.value !== undefined) this.volumeSubject.next(event.value);
console.log('Volume button:', event.direction);
});
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 hardware volume button presses. Does not accept a callback (v2.0+). Use addListener('volumeButtonPressed', callback) before calling this.
watchVolume({
disableSystemVolumeHandler?: boolean; // iOS: disable system UI
suppressVolumeIndicator?: boolean; // Android: hide volume UI
}): Promise<void>
Add listener for volume events. Call this before watchVolume().
// Hardware volume button presses
addListener('volumeButtonPressed', (event: VolumeButtonPressedEvent) => void): Promise<PluginListenerHandle>
// event: { direction: 'up' | 'down', value?: number }
// Volume level changes (hardware + setVolumeLevel)
addListener('volumeLevelChanged', (event: VolumeLevelChangedEvent) => void): Promise<PluginListenerHandle>
// event: { value: number, direction?: 'up' | 'down', type?: VolumeType }
// 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 selectionvolumeButtonPressed - direction, value?volumeLevelChanged - value, direction?, type?watchVolume()Volume 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();
};
watchVolume() no longer accepts a callback. Always use addListener() first, then watchVolume().
// Required pattern in v2.0+
await VolumeControl.addListener('volumeButtonPressed', 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.addListener('volumeButtonPressed', callback);
await VolumeControl.watchVolume({});
}
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
);