๐Ÿ”Š Capacitor Volume Control Plugin

Advanced volume control with native Android and iOS implementations

โญ GitHub ๐Ÿ“ฆ npm
Get Started Now โ†’

Device Support & Platform Compatibility

๐Ÿ“ฑ Supported Devices

๐Ÿค–

Android Devices

Full Support

  • Android 6.0+ (API 23+)
  • All phone manufacturers
  • Tablets and foldables
  • Volume get/set and hardware button detection
  • No MainActivity integration required (v2.0+)
๐ŸŽ

iOS Devices

Full Support

  • iPhone iOS 13+
  • iPad iPadOS 13+
  • Volume get/set and hardware buttons
  • Works out of the box, no extra setup
  • AVAudioSession & MPVolumeView
๐ŸŒ

Web Platform

Development Only

  • Chrome, Firefox, Safari
  • Mock implementation for testing
  • No real volume control
  • Development fallback
  • Not for production
๐Ÿ”Š Capacitor Version: This plugin supports Capacitor 5, 6, 7, and 8. Install in any of these projects with npm install @odion-cloud/capacitor-volume-control and npx cap sync.

๐Ÿ† Android Version Compatibility

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

๐Ÿ’ Support This Project

Help me improve this plugin and build better tools for the community!

๐Ÿค

GitHub Sponsors

Support through GitHub's official sponsorship program:

๐Ÿ’ก Recommended: GitHub Sponsors offers the most transparent and developer-friendly way to support open source projects.
โ‚ฟ

Cryptocurrency Support

Support via crypto donations across multiple networks:

  • Bitcoin (BTC):
    bc1q2k0ftm2fgst22kzj683e8gpau3spfa23ttkg26
  • USDT (Ethereum):
    0xd6f4d8733c8C23e7bEC8Aeba37F4b3D2e93172d1
  • USDT (BNB Chain):
    0xd6f4d8733c8C23e7bEC8Aeba37F4b3D2e93172d1
  • USDT (TRON/TRC20):
    TXVy781mQ2tCuQ1BrattXWueUHp1wB5fwt
  • USDT (Solana):
    GZ8jmSUUzc4dQF7Cthj2atomvpBZWqccR81N9DL4o1Be
  • USDT (TON):
    UQAthXSNIlauj3SrzpDAU4VYxgEVV3niOSmeTPCtMBKGfEAE
๐Ÿ’ป Why Support? Your contributions help me:
  • Upgrade to better development hardware
  • Improve my workspace and productivity
  • Dedicate more time to open source projects
  • Add new features and Capacitor 8 support faster
  • Provide better documentation and examples

๐Ÿค Other Ways to Help

โญ Star the Project

Give us a star on GitHub to show your support!

๐Ÿ› Report Issues

Help improve the plugin by reporting bugs and suggesting features.

๐Ÿ“– Improve Docs

Contribute to documentation, examples, and tutorials.

๐Ÿ’ฌ Spread the Word

Share the plugin with other developers who might find it useful.

What This Plugin Does

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.

๐Ÿ“š Quick Links:
โญ View on GitHub ๐Ÿ“ฆ npm Package โ€ข npm install @odion-cloud/capacitor-volume-control
๐Ÿ”Š

Volume Level Control

Get and set volume levels for different audio streams with precise 0.0-1.0 range control.

๐Ÿ‘‚

Volume Change Monitoring

Watch for volume changes in real-time using addListener('volumeButtonPressed', callback) then watchVolume(options). No callback on watchVolume() (v2.0+).

๐Ÿ“ฑ

Platform-Specific Features

Android: Suppress volume indicator. iOS: Disable system volume handler.

โšก

Fast & Reliable

Uses native Android AudioManager and iOS AVAudioSession for optimal performance.

Works with all platforms! Full support for Android 6.0+ and iOS 13.0+ with native implementations.
๐Ÿ”Š Real-time Volume Control! Monitor volume changes and control audio levels with hardware button support.
โš ๏ธ Breaking change in v2.0: watchVolume() no longer accepts a callback. Use addListener('volumeButtonPressed', callback) before calling watchVolume(options). See migration in CHANGELOG.

Quick Example

// 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 });

Getting Started

1Install the Plugin

npm install @odion-cloud/capacitor-volume-control
npx cap sync
No MainActivity changes required! As of v2.0, volume button watching uses @capacitor-community/volume-buttons (MediaSession). Buttons work automatically on Android and iOS.

2Android Setup (Optional)

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" />

3iOS Setup (Optional)

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>
That's it! The plugin works out of the box. Permissions are optional and only needed for advanced features.

How to Use

1. Import the Plugin

import { VolumeControl } from '@odion-cloud/capacitor-volume-control';

2. Get Current Volume

// 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'
});

3. Set Volume Level

// Set volume to 50%
await VolumeControl.setVolumeLevel({ value: 0.5 });

// Set specific volume type
await VolumeControl.setVolumeLevel({
  value: 0.8,
  type: 'system'
});

4. Watch Volume Button Presses (v2.0+)

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);
Remember: Volume values must be between 0.0 and 1.0. The plugin will throw an error for invalid values.

Real-World Examples

๐ŸŽต Music Player App

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;
  }
}
Output:
Volume up: 0.75
Volume down: 0.6
Current volume: 0.6

๐Ÿ”Š Audio Mixer App

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
    };
  }
}
Output:
Music volume: 0.5
Notification volume: 0.3
System volume: 0.7

๐ŸŽฎ Game Volume Control

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 });
  }
}
Output:
Game muted: Volume set to 0
Game unmuted: Volume restored to 0.5
Volume change detected: 0.6

๐Ÿ“ฑ React Hook Example

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
  };
}
Output:
Initial volume: 0.5
Volume watching started
Volume changed: 0.7
Volume set to: 0.8

๐ŸŽฏ Vue Composition API Example

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
  };
}
Output:
Volume initialized: 0.5
Volume watching started
Volume changed: up 0.6
Volume set to: 0.8

๐Ÿ”ง Angular Service Example

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;
    }
  }
}
Output:
Service initialized: volume 0.5
Volume subscription active
Volume changed: down 0.4
Volume set via service: 0.7

Complete API Reference

getVolumeLevel(options?)

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)
setVolumeLevel(options)

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)
watchVolume(options)

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>
addListener(eventName, callback)

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
removeAllListeners(eventName?)

Remove all listeners for an event.

removeAllListeners(eventName?: string): Promise<void>
clearWatch()

Stop watching for volume changes.

clearWatch(): Promise<void>
isWatching()

Check if volume watching is currently active.

isWatching(): Promise<WatchStatusResult>

// Returns: { value: boolean }

Volume Types

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

Configuration Options

Android Options

  • suppressVolumeIndicator - Hide system volume UI
  • type - Control specific volume streams
  • Hardware button detection
  • Real-time volume monitoring

iOS Options

  • disableSystemVolumeHandler - Disable system UI
  • type - Control voice call volume
  • Volume button interception
  • Audio session management

Cross-Platform

  • value - Volume level (0.0-1.0)
  • type - Volume type selection
  • Error handling
  • TypeScript support

Event Handling

  • volumeButtonPressed - direction, value?
  • volumeLevelChanged - value, direction?, type?
  • Add listener before watchVolume()
  • Multiple listeners supported

Error Handling

Common Errors:
  • Volume value must be between 0.0 and 1.0 - Invalid volume level
  • Volume watching is already active - Multiple watch calls
  • Volume slider not available - iOS setup issue
  • Failed to get volume level - Permission or system error
  • Volume observer registration failed - Android system issue
  • Audio session setup failed - iOS audio session issue

Best Practices

๐Ÿงน Clean Up Listeners

Always remove event listeners when components unmount to prevent memory leaks.

// In React useEffect cleanup
return () => {
  VolumeControl.clearWatch();
  VolumeControl.removeAllListeners();
};

๐ŸŽฏ Use Event Listeners (v2.0+)

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({});

๐Ÿ›ก๏ธ Handle Errors Gracefully

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');
  }
}

๐Ÿ” Check Watch Status

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({});
}

๐Ÿ“ฑ Test on Real Devices

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

โšก Performance Tips

Avoid frequent volume changes and batch operations when possible.

// Debounce volume changes
const debouncedSetVolume = debounce(
  (value) => VolumeControl.setVolumeLevel({ value }), 
  100
);