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.

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 | Authorized

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

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

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

Item
Type
Notes

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

API
Purpose
Returns

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

Error Code
Trigger
Recommended Action

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

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

Call Initialize() before any media operations. Most games should use the standard setup configured in Essential Kit Settings instead.

  • Demo scene: Assets/Plugins/VoxelBusters/EssentialKit/Examples/Scenes/MediaServicesDemo.unity

  • Pair 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?