📱 Capacitor MediaStore Plugin

Media access, file watching & native media session for Android & iOS

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
  • Internal & SD card storage
  • All media types supported
🍎

iOS Devices

Coming Soon

  • iPhone iOS 12+
  • iPad iPadOS 13+
  • Photos & Videos access
  • Music library integration
  • Expected Q2 2025
🌐

Web Platform

Development Only

  • Chrome, Firefox, Safari
  • Mock data for testing
  • No real file access
  • Development fallback
  • Not for production
📱 iOS Support Coming Soon! We're actively working on iOS support with full Photos framework integration.

🏆 Android Version Compatibility

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

💝 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 iOS support and new features 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

Get access to all media files on Android devices - including music, photos, videos, and files stored on SD cards. Perfect for building music players, photo galleries, and file managers.

📚 Quick Links:
⭐ View on GitHub 📦 npm Package npm install @odion-cloud/capacitor-mediastore
🎵

Music & Audio Files

Access all music files with rich metadata like artist, album, duration, and album art.

📸

Photos & Videos

Browse device photos and videos with dimensions, file sizes, and creation dates.

💾

SD Card Support

Access files stored on external SD cards and removable storage devices.

Fast & Reliable

Uses Android's native MediaStore API for fast, indexed access to media files.

👁️

File Change Watcher

Real-time detection of file additions, deletions, and modifications via native observers.

🔔

Native Media Session

Full playback notification with custom buttons (like, shuffle, repeat), progress bar, and app logo.

Works with all Android versions! From Android 6.0 to the latest versions, with automatic permission handling.
📱 iOS Support Coming Soon! We're actively working on iOS support with full Photos framework integration.

Quick Example

// Get all music files from device (including SD card)
const music = await CapacitorMediaStore.getMediasByType({
  mediaType: 'audio',
  includeExternal: true
});

console.log(`Found ${music.totalCount} songs!`);
music.media.forEach(song => {
  console.log(`${song.title} by ${song.artist}`);
});

👁️ File Change Watcher

Detect when files are added, deleted, or modified on the device in real-time. Uses Android ContentObserver and iOS PHPhotoLibraryChangeObserver.

Added

Detects when new files are downloaded, saved, or transferred to the device.

🗑️

Deleted

Detects when files are removed from the device or SD card.

✏️

Modified

Detects when file metadata or content is changed (e.g., edited tags).

Start Watching

import { CapacitorMediaStore } from '@odion-cloud/capacitor-mediastore';

// Listen for file changes
CapacitorMediaStore.addListener('mediaChanged', (event) => {
  console.log(`File ${event.type}:`, event.file);
  // event.type: 'added' | 'deleted' | 'modified'
  // event.file: { id, displayName, uri, mimeType, mediaType, ... }
});

// Start watching all media types
await CapacitorMediaStore.startWatching();

// Or watch specific types only
await CapacitorMediaStore.startWatching({
  mediaTypes: ['audio', 'image']
});

Stop Watching

await CapacitorMediaStore.stopWatching();

Example: Auto-refresh Music Library

async function setupAutoRefresh() {
  CapacitorMediaStore.addListener('mediaChanged', (event) => {
    if (event.file.mediaType === 'audio') {
      if (event.type === 'added') {
        console.log('New song downloaded:', event.file.displayName);
        refreshMusicLibrary();
      } else if (event.type === 'deleted') {
        console.log('Song removed:', event.file.displayName);
        removeFromLibrary(event.file.id);
      }
    }
  });
  
  await CapacitorMediaStore.startWatching({ mediaTypes: ['audio'] });
}
📱 Platform: Uses ContentObserver on Android (API 1+) and PHPhotoLibraryChangeObserver on iOS (iOS 8+). Works across all supported versions.

🎵 Native Media Session

Create persistent media playback notifications with lock screen & notification controls — just like Spotify, Apple Music, or YouTube Music.

🎛️

Transport Controls

Previous, Play/Pause, Next always visible in compact notification view.

❤️

Custom Buttons

Add Like, Shuffle, Repeat buttons with position: 'left' or 'right'.

🖼️

Artwork & Branding

Album art + your app logo. Background auto-tints with artwork color.

📊

Progress Bar

Native seek bar showing playback position and duration.

🎨 Icon Setup (Android)

Place icon drawables in android/app/src/main/res/drawable/.

Format: Vector XML (24×24dp) or PNG (48×48px mdpi / 96×96px xhdpi). Use monochrome white-on-transparent for the notification icon.

File Name Purpose
ic_notification.xml Small notification icon (your app logo)
ic_play.xml Play button
ic_pause.xml Pause button
ic_skip_previous.xml Previous track
ic_skip_next.xml Next track
ic_shuffle.xml Shuffle off
ic_shuffle_on.xml Shuffle on
ic_favorite_border.xml Not liked (outline heart)
ic_favorite.xml Liked (filled heart)
ic_repeat.xml Repeat off/normal
ic_repeat_one.xml Repeat one
ic_repeat_all.xml Repeat all (optional)
Tip: In Android Studio: Right-click res/drawable → New → Vector Asset → search Material Icons. The plugin tries to resolve icon names as: exact name → ic_<name>ms_<name>.

Create a Media Session

await CapacitorMediaStore.setMediaSession({
  title: 'Bohemian Rhapsody',
  artist: 'Queen',
  album: 'A Night at the Opera',
  artworkUrl: 'https://example.com/album-art.jpg',
  duration: 354,  // seconds
  smallIconName: 'ic_notification',  // 24dp monochrome white-on-transparent
  buttons: [
    // position: 'left' = displayed BEFORE the Previous button
    { action: 'shuffle', displayName: 'Shuffle', iconName: 'ic_shuffle',
      enabled: true, position: 'left' },
    // position: 'right' = displayed AFTER the Next button (default)
    { action: 'like', displayName: 'Like', iconName: 'ic_favorite_border',
      enabled: true, position: 'right' },
    { action: 'repeat', displayName: 'Repeat', iconName: 'ic_repeat',
      enabled: true, position: 'right' }
  ]
});

await CapacitorMediaStore.updatePlaybackState({ state: 'playing' });
🎨 Colorized Background: The notification background automatically tints with the dominant color from the album artwork (like YouTube Music / Spotify on Android 13+).

Handle Button Events

CapacitorMediaStore.addListener('mediaSessionAction', (event) => {
  switch (event.action) {
    case 'play': player.play(); break;
    case 'pause': player.pause(); break;
    case 'nexttrack': player.next(); break;
    case 'previoustrack': player.previous(); break;
    case 'like': toggleLike(); break;
    case 'shuffle': toggleShuffle(); break;
    case 'repeat': toggleRepeat(); break;
    case 'seekto': player.seekTo(event.extras.seekTime); break;
  }
});

Update Progress Bar

// Update position every second
setInterval(async () => {
  await CapacitorMediaStore.updatePositionState({
    position: player.getCurrentTime(),
    duration: player.getDuration(),
    playbackRate: 1.0
  });
}, 1000);

Dynamically Update Buttons

// Toggle like icon (outline → filled)
await CapacitorMediaStore.updateMediaSessionButtons({
  buttons: [
    { action: 'like', displayName: 'Unlike', iconName: 'ic_favorite',
      enabled: true, position: 'right' }
  ]
});

// Cycle repeat modes
// Mode 1: Repeat Off
await CapacitorMediaStore.updateMediaSessionButtons({
  buttons: [{ action: 'repeat', displayName: 'Repeat Off',
    iconName: 'ic_repeat', position: 'right' }]
});
// Mode 2: Repeat All
await CapacitorMediaStore.updateMediaSessionButtons({
  buttons: [{ action: 'repeat', displayName: 'Repeat All',
    iconName: 'ic_repeat_all', position: 'right' }]
});
// Mode 3: Repeat One
await CapacitorMediaStore.updateMediaSessionButtons({
  buttons: [{ action: 'repeat', displayName: 'Repeat One',
    iconName: 'ic_repeat_one', position: 'right' }]
});

Destroy Session

await CapacitorMediaStore.destroyMediaSession();
Android Icons: All iconName values must match a file in android/app/src/main/res/drawable/. Without them, the plugin falls back to a generic Play icon and logs a warning to help you debug.
iOS: Uses MPNowPlayingInfoCenter + MPRemoteCommandCenter (iOS 7.1+). Lock screen controls work out of the box. Enable Background Audio in Xcode.

Getting Started

1Install the Plugin

npm install @odion-cloud/capacitor-mediastore
npx cap sync

2Add Android Permissions

Add these permissions to android/app/src/main/AndroidManifest.xml:

<!-- For Android 6-12 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" 
                 android:maxSdkVersion="32" />

<!-- For Android 13+ -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />

3iOS Setup (Optional)

If you plan to support iOS, add to ios/App/App/Info.plist:

<key>NSPhotoLibraryUsageDescription</key>
<string>This app needs access to your photos and media</string>
That's it! The plugin will automatically handle permission requests when you use it. No additional setup needed.

How to Use

1. Import the Plugin

import { CapacitorMediaStore } from '@odion-cloud/capacitor-mediastore';

2. Request Permissions

// Request all permissions
const permissions = await CapacitorMediaStore.requestPermissions();

// Or request specific types only
const audioPermission = await CapacitorMediaStore.requestPermissions({
  types: ['audio']  // Only ask for music access
});

3. Get Media Files

Get All Media

const allMedia = await CapacitorMediaStore.getMedias({
  limit: 100,              // Get first 100 files
  includeExternal: true    // Include SD card files
});

console.log(`Total files: ${allMedia.totalCount}`);

Get Specific Types

// Get all music files
const music = await CapacitorMediaStore.getMediasByType({
  mediaType: 'audio',
  sortBy: 'TITLE',
  sortOrder: 'ASC'
});

// Get all photos  
const photos = await CapacitorMediaStore.getMediasByType({
  mediaType: 'image',
  limit: 50
});

// Get all videos
const videos = await CapacitorMediaStore.getMediasByType({
  mediaType: 'video'
});

// Get documents (PDF, DOC, TXT, etc.)
const documents = await CapacitorMediaStore.getMediasByType({
  mediaType: 'document'
});

4. Access File Information

// Each file includes detailed information
music.media.forEach(song => {
  console.log('Title:', song.title);
  console.log('Artist:', song.artist);
  console.log('Album:', song.album);
  console.log('Duration:', song.duration + 'ms');
  console.log('File size:', song.size + ' bytes');
  console.log('SD Card?:', song.isExternal);
});
Remember: Always check permissions before accessing media files. The plugin will show permission dialogs automatically when needed.

Real-World Examples

🎵 Music Player App

async function createMusicLibrary() {
  // Ask for music access
  await CapacitorMediaStore.requestPermissions({ types: ['audio'] });
  
  // Get all songs (including from SD card)
  const result = await CapacitorMediaStore.getMediasByType({
    mediaType: 'audio',
    sortBy: 'TITLE',
    includeExternal: true
  });
  
  // Create playlist
  const playlist = result.media.map(song => ({
    id: song.id,
    title: song.title || 'Unknown Song',
    artist: song.artist || 'Unknown Artist',
    album: song.album || 'Unknown Album',
    duration: song.duration || 0,
    filePath: song.uri,
    isOnSDCard: song.isExternal
  }));
  
  console.log(`Created playlist with ${playlist.length} songs`);
  return playlist;
}
Output:
Created playlist with 847 songs
Song: "Bohemian Rhapsody" by Queen (SD Card: true)
Song: "Hotel California" by Eagles (SD Card: false)

📸 Photo Gallery App

async function createPhotoGallery() {
  // Ask for photo access
  await CapacitorMediaStore.requestPermissions({ types: ['images'] });
  
  // Get recent photos
  const photos = await CapacitorMediaStore.getMediasByType({
    mediaType: 'image',
    sortBy: 'DATE_ADDED',
    sortOrder: 'DESC',
    limit: 100
  });
  
  // Create gallery
  const gallery = photos.media.map(photo => ({
    id: photo.id,
    name: photo.displayName,
    size: `${photo.width}×${photo.height}`,
    fileSize: Math.round(photo.size / 1024) + ' KB',
    dateTaken: new Date(photo.dateAdded),
    thumbnailPath: photo.uri
  }));
  
  return gallery;
}

🔍 Search Music by Artist

async function findSongsByArtist(artistName) {
  const songs = await CapacitorMediaStore.getMediasByType({
    mediaType: 'audio',
    artistName: artistName,  // Filter by specific artist
    sortBy: 'TITLE'
  });
  
  console.log(`Found ${songs.totalCount} songs by ${artistName}`);
  return songs.media;
}

// Usage
const beatlesSongs = await findSongsByArtist('The Beatles');
const taylorSwiftSongs = await findSongsByArtist('Taylor Swift');

💿 Get Music Albums

async function getMusicAlbums() {
   await CapacitorMediaStore.requestPermissions({ types: ['audio'] });
   
   const albums = await CapacitorMediaStore.getAlbums();
   
   albums.albums.forEach(album => {
     console.log(`${album.name} by ${album.artist}`);
     console.log(`${album.trackCount} tracks`);
   });
   
   return albums.albums;
 }

💾 Save Media File

async function saveAudioFile(audioData, fileName) {
   await CapacitorMediaStore.requestPermissions({ types: ['audio'] });
   
   const result = await CapacitorMediaStore.saveMedia({
     data: audioData,           // Base64 encoded audio data
     fileName: fileName,        // e.g., 'my-song.mp3'
     mediaType: 'audio',
     albumName: 'My Custom Album',
     relativePath: 'MyApp/Audio'  // Optional custom folder
   });
   
   if (result.success) {
     console.log('File saved successfully at:', result.uri);
     return result.uri;
   } else {
     console.error('Save failed:', result.error);
   }
 }

🔍 Get Detailed Metadata

async function getDetailedSongInfo(fileUri) {
   const metadata = await CapacitorMediaStore.getMediaMetadata({
     filePath: fileUri
   });
   
   const song = metadata.media;
   
   console.log('🎵 Song Details:');
   console.log(`Title: ${song.title}`);
   console.log(`Artist: ${song.artist}`);
   console.log(`Album: ${song.album}`);
   console.log(`Duration: ${song.duration}ms`);
   console.log(`Bitrate: ${song.bitrate} kbps`);
   console.log(`Sample Rate: ${song.sampleRate} Hz`);
   console.log(`Channels: ${song.channels === 1 ? 'Mono' : 'Stereo'}`);
   console.log(`Genre: ${song.genre}`);
   console.log(`Year: ${song.year}`);
   
   return song;
 }

Complete API Reference

getMedias(options?)

Get all media files from the device (photos, music, videos).

getMedias({
  limit?: number;           // How many files to return
  offset?: number;          // Skip first N files (for pagination)
  sortBy?: string;          // 'DATE_ADDED', 'TITLE', 'SIZE'
  sortOrder?: 'ASC' | 'DESC'; // Sort direction
  includeExternal?: boolean;  // Include SD card files (default: true)
})
getMediasByType(options)

Get specific types of media files with filtering options.

getMediasByType({
  mediaType: 'audio' | 'image' | 'video' | 'document';
  limit?: number;
  offset?: number;
  sortBy?: string;
  sortOrder?: 'ASC' | 'DESC';
  includeExternal?: boolean;
  albumName?: string;       // Filter by album (music only)
  artistName?: string;      // Filter by artist (music only)
})
getAlbums()

Get all music albums from the device.

getAlbums(): Promise<{
  albums: {
    id: string;
    name: string;
    artist: string;
    trackCount: number;
    albumArtUri?: string;
  }[];
  totalCount: number;
}>
requestPermissions(options?)

Request permissions to access media files.

requestPermissions({
  types?: ('audio' | 'images' | 'video')[];  // Specific permission types
})
saveMedia(options)

Save a media file to device storage with proper metadata.

saveMedia({
   data: string;             // Base64 data or file URI
   fileName: string;         // Name for the file
   mediaType: 'audio' | 'image' | 'video';
   albumName?: string;       // Album to save in (optional)
   relativePath?: string;    // Custom folder path (optional)
 })
getMediaMetadata(options)

Get detailed metadata for a specific media file including audio tags.

getMediaMetadata({
   filePath: string;         // URI of the media file
 }): Promise<{
   media: MediaFile;         // File with complete metadata
 }>
checkPermissions()

Check current permission status without requesting.

checkPermissions(): Promise<PermissionStatus>

Media File Properties

Every media file returned includes these properties:

Property Type Description
id string Unique file identifier
uri string File path/URI
displayName string File name
size number File size in bytes
mimeType string File type (e.g., 'audio/mp3')
dateAdded number When file was added (timestamp)
mediaType string 'audio', 'image', 'video', etc.
isExternal boolean true if file is on SD card

Music Files Also Include:

Basic Info

  • title - Song title
  • artist - Artist name
  • album - Album name
  • duration - Length in milliseconds
  • albumArtist - Album artist
  • composer - Song composer

Extended Info

  • genre - Music genre
  • year - Release year
  • track - Track number
  • albumArtUri - Album cover image
  • bitrate - Audio bitrate
  • sampleRate - Sample rate
  • channels - Audio channels (mono/stereo)

Images & Videos Also Include:

Tip: Use includeExternal: true to access files stored on SD cards and external storage devices.