Usage
Media Services allows camera capture, gallery selection, and image saving on mobile devices
Essential Kit wraps native iOS and Android media APIs into a single Unity interface. All media operations automatically handle permissions - the first call triggers the system permission dialog if needed.
Table of Contents
Understanding Core Concepts
Media Content Types
Media Services supports three content types through unified APIs:
Images: PNG, JPEG photos from gallery or camera
Videos: MP4, MOV videos from gallery or camera
Documents: PDFs and other file types (gallery selection only)
Permission Modes
Gallery access has different permission levels:
Read: Select existing media from gallery
Write: Save new media to gallery
Add: Add media without full gallery access (iOS 14+ limited library)
IMediaContent Interface
All media operations return IMediaContent instances that can be converted to:
Texture2D: For displaying images in UI
File Path: For saving to disk with custom location
Raw Data: For uploading to servers or custom processing
Import Namespaces
using System;
using VoxelBusters.EssentialKit;
using VoxelBusters.CoreLibrary;How Permissions Work
Just call the media operation directly - no permission checks needed. Essential Kit automatically shows the system permission dialog on first use. If permission is granted, the operation executes. If denied, the error callback explains why.
UX Best Practice: Show a custom explanation screen before calling media operations to improve opt-in rates. For example, display "Choose a profile picture to personalize your experience" before calling SelectMediaContent().
Handle permission issues in the error callback:
void OnMediaOperationComplete(IMediaContent[] contents, Error error)
{
if (error != null)
{
if (error.Code == (int)MediaServicesErrorCode.PermissionNotAvailable)
{
Debug.Log("Gallery access denied - guide user to settings");
Utilities.OpenApplicationSettings();
}
return;
}
Debug.Log($"Selected {contents.Length} media items");
}Optional: Check Permission Status
Use GetGalleryAccessStatus() or GetCameraAccessStatus() only when you need to inspect the exact state before calling the main operation or to customize UI messaging.
var galleryStatus = MediaServices.GetGalleryAccessStatus(GalleryAccessMode.Read);
// NotDetermined | Restricted | Denied | Authorized | Limited
var cameraStatus = MediaServices.GetCameraAccessStatus();
// NotDetermined | Restricted | Denied | AuthorizedSelecting Media from Gallery
Why Gallery Selection is Needed
Let players choose existing media from their device for avatars, backgrounds, or user-generated content features without requiring them to take new photos.
This ensures:
Faster user onboarding (use existing photos)
Privacy-friendly selection (Photo Picker on modern platforms)
Multiple file type support (images, videos, documents)
Basic Image Selection
void SelectAvatarImage()
{
var options = MediaContentSelectOptions.CreateForImage();
options.AllowsMultipleSelection = false;
MediaServices.SelectMediaContent(options, OnImageSelected);
}
void OnImageSelected(IMediaContent[] contents, Error error)
{
if (error != null)
{
Debug.LogError($"Image selection failed: {error.Description}");
return;
}
if (contents == null || contents.Length == 0)
{
Debug.Log("User cancelled selection");
return;
}
// Convert to Texture2D for display
contents[0].AsTexture2D((texture, conversionError) =>
{
if (conversionError != null)
{
Debug.LogError($"Failed to load image: {conversionError.Description}");
return;
}
Debug.Log($"Loaded avatar image: {texture.width}x{texture.height}");
Debug.Log("Assign the texture to your avatar UI.");
});
}Multiple Selection
void SelectMultiplePhotos()
{
var options = MediaContentSelectOptions.CreateForImage();
options.AllowsMultipleSelection = true;
MediaServices.SelectMediaContent(options, (contents, error) =>
{
if (error == null && contents != null)
{
Debug.Log($"Selected {contents.Length} photos");
foreach (var content in contents)
{
// Process each photo
}
}
});
}Selecting Videos
void SelectVideo()
{
var options = MediaContentSelectOptions.CreateForVideo();
MediaServices.SelectMediaContent(options, (contents, error) =>
{
if (error == null && contents != null && contents.Length > 0)
{
// Videos must be saved to file for playback
contents[0].AsFilePath(Application.persistentDataPath, "selected_video",
(filePath, fileError) =>
{
if (fileError == null)
{
Debug.Log($"Video saved to: {filePath}");
Debug.Log("Play the saved video using your preferred player.");
}
});
}
});
}Custom MIME Type Selection
void SelectDocument()
{
var options = new MediaContentSelectOptions(
title: "Select Document",
allowedMimeType: MimeType.kPDFDocument,
maxAllowed: 1
);
MediaServices.SelectMediaContent(options, OnDocumentSelected);
}Capturing Media with Camera
Why Camera Capture is Needed
Let players create new content directly from your app - profile photos, in-game screenshots with real backgrounds, or user-generated content.
This ensures:
Fresh, context-appropriate content creation
Immediate photo/video capture without leaving the app
Native camera interface with platform-optimized controls
Capturing a Photo
void CaptureProfilePhoto()
{
var options = MediaContentCaptureOptions.CreateForImage();
options.Title = "Capture Profile Photo";
options.FileName = "profile_photo";
MediaServices.CaptureMediaContent(options, OnPhotoCaptured);
}
void OnPhotoCaptured(IMediaContent content, Error error)
{
if (error != null)
{
Debug.LogError($"Camera capture failed: {error.Description}");
return;
}
if (content == null)
{
Debug.Log("User cancelled camera capture");
return;
}
content.AsTexture2D((texture, conversionError) =>
{
if (conversionError == null)
{
Debug.Log($"Captured photo: {texture.width}x{texture.height}");
Debug.Log("Apply the captured texture to the profile UI.");
}
});
}Capturing a Video
void CaptureVideo()
{
var options = MediaContentCaptureOptions.CreateForVideo();
options.FileName = "captured_video";
MediaServices.CaptureMediaContent(options, (content, error) =>
{
if (error == null && content != null)
{
// Save video to file
content.AsFilePath(Application.persistentDataPath, "videos",
(filePath, fileError) =>
{
if (fileError == null)
{
Debug.Log($"Video captured at: {filePath}");
}
});
}
});
}Advanced Capture Options
void CustomCapture()
{
var options = new MediaContentCaptureOptions(
title: "Capture Image",
fileName: "game_photo",
captureType: MediaContentCaptureType.Image
);
MediaServices.CaptureMediaContent(options, OnPhotoCaptured);
}Saving Media to Gallery
Why Saving Media is Needed
Let players save game screenshots, generated images, or downloaded content to their device for sharing and keeping.
This ensures:
Players can share achievements on social media
Generated content persists outside the app
Screenshots can be accessed from native photo apps
Saving a Screenshot
void SaveGameScreenshot()
{
// Capture screenshot
Texture2D screenshot = ScreenCapture.CaptureScreenshotAsTexture();
byte[] imageData = screenshot.EncodeToPNG();
var saveOptions = new MediaContentSaveOptions(
directoryName: "MyGame Screenshots",
fileName: $"mygame_screenshot_{DateTime.UtcNow:yyyyMMdd_HHmmss}");
MediaServices.SaveMediaContent(
data: imageData,
mimeType: MimeType.kPNGImage,
options: saveOptions,
callback: OnScreenshotSaved
);
// Clean up temporary texture
UnityEngine.Object.Destroy(screenshot);
}
void OnScreenshotSaved(bool success, Error error)
{
if (success)
{
Debug.Log("Screenshot saved to gallery");
Debug.Log("Show share success feedback to the player.");
}
else if (error != null)
{
Debug.LogError($"Failed to save screenshot: {error.Description}");
}
}Saving Without Custom Album
void SaveToDefaultGallery()
{
Texture2D image = GetGeneratedImage();
byte[] imageData = image.EncodeToJPG(quality: 90);
// Pass null for directoryName to save to default location
var options = new MediaContentSaveOptions(
directoryName: null, // No custom album
fileName: "generated_image"
);
MediaServices.SaveMediaContent(imageData, MimeType.kJPEGImage, options,
(success, error) =>
{
Debug.Log(success ? "Saved successfully" : $"Save failed: {error}");
});
}Custom Albums on iOS: Creating custom albums requires additional permissions. If "Saves Files to Custom Directories" is disabled in settings, you must pass null for directoryName to avoid permission errors.
Working with IMediaContent
IMediaContent is the universal interface for all media returned by Essential Kit. It supports three conversion methods:
Convert to Texture2D
void LoadImageTexture(IMediaContent content)
{
content.AsTexture2D((texture, error) =>
{
if (error == null)
{
Debug.Log($"Display texture ({texture.width}x{texture.height}) in your UI.");
}
});
}Convert to File Path
void SaveToFile(IMediaContent content)
{
string saveDirectory = Application.persistentDataPath;
string fileName = "media_file";
content.AsFilePath(saveDirectory, fileName, (filePath, error) =>
{
if (error == null)
{
Debug.Log($"Media saved to: {filePath}");
}
});
}Get Raw Media Data
void GetRawBytes(IMediaContent content)
{
content.GetRawMediaData((rawData, error) =>
{
if (error == null)
{
byte[] mediaBytes = rawData.Data;
string mimeType = rawData.MimeType;
Debug.Log($"Got {mediaBytes.Length} bytes of {mimeType}");
// Upload to server, process, etc.
}
});
}Data Properties
IMediaContent
Interface
Provides asynchronous helpers (AsTexture2D, AsFilePath, AsRawMediaData) so you can retrieve the captured or selected asset in the format your UI or backend expects.
RawMediaData.Bytes
byte[]
Raw payload returned by AsRawMediaData; combine with RawMediaData.Mime to upload files or persist binary data accurately.
RawMediaData.Mime
string
MIME type that Essential Kit detected for the media item (image/png, video/mp4, etc.); use it when saving or posting to remote services.
MediaServicesErrorCode
Enum
Values surfaced through Error.Code inside callbacks. Use it to branch on permission issues, cancellations, or platform errors before retrying.
Core APIs Reference
MediaServices.SelectMediaContent(options, callback)
Open gallery to select media
IMediaContent[] via callback
MediaServices.CaptureMediaContent(options, callback)
Open camera to capture photo/video
IMediaContent via callback
MediaServices.SaveMediaContent(data, mimeType, options, callback)
Save media to device gallery
bool success via callback
MediaContentSelectOptions.CreateForImage()
Create selection options for images
MediaContentSelectOptions
MediaContentSelectOptions.CreateForVideo()
Create selection options for videos
MediaContentSelectOptions
MediaContentCaptureOptions.CreateForImage()
Create capture options for photos
MediaContentCaptureOptions
MediaContentCaptureOptions.CreateForVideo()
Create capture options for videos
MediaContentCaptureOptions
MediaServices.GetGalleryAccessStatus(mode)
Optional: Check gallery permission
GalleryAccessStatus enum
MediaServices.GetCameraAccessStatus()
Optional: Check camera permission
CameraAccessStatus enum
Error Handling
PermissionNotAvailable
User declined camera/gallery access
Show rationale + link to Utilities.OpenApplicationSettings()
UserCancelled
User closed picker/camera without selecting
Log for analytics, no error message needed
Unknown
Platform error during operation
Retry or log for support investigation
DataNotAvailable
Missing required data in the underlying service
Validate options before calling API
void HandleMediaError(Error error)
{
if (error == null) return;
switch (error.Code)
{
case (int)MediaServicesErrorCode.PermissionNotAvailable:
Debug.LogWarning("Explain why permission is needed and direct players to Settings.");
break;
case (int)MediaServicesErrorCode.UserCancelled:
Debug.Log("User cancelled operation");
break;
default:
Debug.LogError($"Media operation failed: {error.Description}");
break;
}
}Advanced: Manual Initialization
Manual initialization is only needed for specific runtime scenarios. For most games, Essential Kit's automatic initialization handles everything. Skip this section unless you need runtime settings or server-driven configuration.
Understanding Manual Initialization
Default Behavior: Essential Kit automatically initializes Media Services using settings from the ScriptableObject configured in the Unity Editor.
Advanced Usage: Override default settings at runtime when you need:
Feature flags based on user subscription level
A/B testing different camera quality settings
Server-driven feature configuration
Dynamic album names based on game state
Implementation
Override settings at runtime before using Media Services:
void Awake()
{
var customSettings = new MediaServicesUnitySettings(
isEnabled: true,
usesCameraForImageCapture: true,
usesCameraForVideoCapture: true,
savesFilesToGallery: true,
savesFilesToCustomAlbums: false); // Disable custom albums
MediaServices.Initialize(customSettings);
}Related Guides
Demo scene:
Assets/Plugins/VoxelBusters/EssentialKit/Examples/Scenes/MediaServicesDemo.unityPair with Sharing Services to share selected images via native share sheet
Use with Utilities.OpenApplicationSettings() for permission recovery flows
See Testing Guide for editor simulation and device validation
Last updated
Was this helpful?