Media access, file watching & native media session for Android & iOS
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.
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.
Access all music files with rich metadata like artist, album, duration, and album art.
Browse device photos and videos with dimensions, file sizes, and creation dates.
Access files stored on external SD cards and removable storage devices.
Uses Android's native MediaStore API for fast, indexed access to media files.
Real-time detection of file additions, deletions, and modifications via native observers.
Full playback notification with custom buttons (like, shuffle, repeat), progress bar, and app logo.
// 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}`);
});
Detect when files are added, deleted, or modified on the device in real-time. Uses Android
ContentObserver and iOS PHPhotoLibraryChangeObserver.
Detects when new files are downloaded, saved, or transferred to the device.
Detects when files are removed from the device or SD card.
Detects when file metadata or content is changed (e.g., edited tags).
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']
});
await CapacitorMediaStore.stopWatching();
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'] });
}
ContentObserver on Android (API 1+) and
PHPhotoLibraryChangeObserver on iOS (iOS 8+). Works across all supported versions.
Create persistent media playback notifications with lock screen & notification controls — just like Spotify, Apple Music, or YouTube Music.
Previous, Play/Pause, Next always visible in compact notification view.
Add Like, Shuffle, Repeat buttons with position: 'left' or 'right'.
Album art + your app logo. Background auto-tints with artwork color.
Native seek bar showing playback position and duration.
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) |
res/drawable → New → Vector Asset →
search Material Icons. The plugin tries to resolve icon names as: exact name →
ic_<name> → ms_<name>.
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' });
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 position every second
setInterval(async () => {
await CapacitorMediaStore.updatePositionState({
position: player.getCurrentTime(),
duration: player.getDuration(),
playbackRate: 1.0
});
}, 1000);
// 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' }]
});
await CapacitorMediaStore.destroyMediaSession();
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.
MPNowPlayingInfoCenter + MPRemoteCommandCenter (iOS
7.1+). Lock screen controls work out of the box. Enable Background Audio in Xcode.
npm install @odion-cloud/capacitor-mediastore npx cap sync
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" />
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>
import { CapacitorMediaStore } from '@odion-cloud/capacitor-mediastore';
// 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
});
const allMedia = await CapacitorMediaStore.getMedias({
limit: 100, // Get first 100 files
includeExternal: true // Include SD card files
});
console.log(`Total files: ${allMedia.totalCount}`);
// 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'
});
// 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);
});
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;
}
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;
}
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');
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;
}
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);
}
}
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;
}
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)
})
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)
})
Get all music albums from the device.
getAlbums(): Promise<{
albums: {
id: string;
name: string;
artist: string;
trackCount: number;
albumArtUri?: string;
}[];
totalCount: number;
}>
Request permissions to access media files.
requestPermissions({
types?: ('audio' | 'images' | 'video')[]; // Specific permission types
})
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)
})
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
}>
Check current permission status without requesting.
checkPermissions(): Promise<PermissionStatus>
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 |
title - Song titleartist - Artist namealbum - Album nameduration - Length in millisecondsalbumArtist - Album artistcomposer - Song composergenre - Music genreyear - Release yeartrack - Track numberalbumArtUri - Album cover imagebitrate - Audio bitratesampleRate - Sample ratechannels - Audio channels (mono/stereo)width - Image/video width in pixelsheight - Image/video height in pixelsduration - Video length in milliseconds (videos only)includeExternal: true to access files stored on SD cards and
external storage devices.