IMediaPicker — работа с фото и видео в Android

Еще одной достаточно распространенной функцией приложений Android является съемка фото- и видеоматериалов. С использованием интерфейса IMediaPicker мы можем выбирать различные медиафайлы, а также запускать фото- и видеокамеры для начала процесса съемки.

Определение необходимых разрешений

Для использования всех возможностей IMediaPicker нам потребуются следующие разрешения:

  • CAMERA — для доступа к камере устройства.

Также, в зависимости от версии Android нам потребуются следующие разрешения:

  1. Если приложение предназначено для Android 12 или более поздней версий, необходимо запросить разрешения WRITE_EXTERNAL_STORAGE и READ_EXTERNAL_STORAGE.
  2. Если приложение предназначено для Android 13 или более поздней версии и нуждается в доступе к файлам мультимедиа, созданным другими приложениями, необходимо запросить одно или несколько следующих разрешений вместо READ_EXTERNAL_STORAGE разрешения:
    1. READ_MEDIA_IMAGES
    2. READ_MEDIA_VIDEO
    3. READ_MEDIA_AUDIO

Все эти разрешения относятся к опасным, то есть должны явно запрашиваться у пользователя. Если целевая версии Android проекта имеет значение Android 11 (R API 30) или более поздней, необходимо обновить манифест Android и добавить в файл Platform/Android/AndroidManifest.xml следующие queries/intent узлы

<queries> 
   <intent> 
      <action android:name="android.media.action.IMAGE_CAPTURE" /> 
   </intent> 
</queries>

Итак, создадим новое приложение Blazor Hybrid и сразу изменим файл Platforms/Android/MainApplication.cs следующим образом:

using Android.App;
using Android.Runtime;

[assembly: UsesPermission(Android.Manifest.Permission.ReadExternalStorage, MaxSdkVersion = 32)]
[assembly: UsesPermission(Android.Manifest.Permission.ReadMediaAudio)]
[assembly: UsesPermission(Android.Manifest.Permission.ReadMediaImages)]
[assembly: UsesPermission(Android.Manifest.Permission.ReadMediaVideo)]


[assembly: UsesPermission(Android.Manifest.Permission.Camera)]
[assembly: UsesPermission(Android.Manifest.Permission.WriteExternalStorage, MaxSdkVersion = 32)]

[assembly: UsesFeature("android.hardware.camera", Required = true)]
[assembly: UsesFeature("android.hardware.camera.autofocus", Required = true)]
[assembly: UsesFeature("android.hardware.camera.autofocus", Required = true)]

namespace BlazorCamera
{
    [Application]
    public class MainApplication : MauiApplication
    {
        public MainApplication(IntPtr handle, JniHandleOwnership ownership)
            : base(handle, ownership)
        {

а также изменим файл Platforms/Android/AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application android:allowBackup="true" android:icon="@mipmap/appicon" android:roundIcon="@mipmap/appicon_round" android:supportsRtl="true"></application>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />

    <queries>
        <intent>
            <action android:name="android.media.action.IMAGE_CAPTURE" />
        </intent>
    </queries>
    
</manifest>

Интерфейс IMediaPicker и класс MediaPicker

Интерфейсе IMediaPicker предоставляет нам следующие методы:

Метод Тип Описание
CapturePhotoAsync(MediaPickerOptions) Task<FileResult> Открывает камеру для съемки.
CaptureVideoAsync(MediaPickerOptions) Task<FileResult> Открывает камеру для съемки видео.
PickPhotoAsync(MediaPickerOptions) Task<FileResult> Открывает браузер мультимедиа для выбора фотографии.
PickVideoAsync(MediaPickerOptions) Task<FileResult> Открывает браузер мультимедиа для выбора видео.

а также одно свойство:

public bool IsCaptureSupported { get; }

которое возвращает значение, указывающее, поддерживается ли запись мультимедиа на этом устройстве.

Класс FileResult представляет файл после его выбора пользователем и содержит следующие полезные для нас свойства:

Свойство Тип Описание
ContentType string Тип контента файла в качестве типа MIME (например: image/png).
FileName string Имя файла
FullPath string Полный путь, включая имя файла.

Доступ к реализации IMediaPicker  по умолчанию осуществляется через свойство Default класса MediaPicker. Теперь рассмотрим основные моменты работы с IMediaPicker на примере нашего приложения.

Как выбрать и открыть фотографию в приложении Blazor Hybrid

Выбор мультимедиа на устройстве Android осуществляется с использованием метода CapturePhotoAsync(). Всё, что сделает этот метод — это вернет нам объект типа FileResult. Наша задача, помимо выбора файла, может также заключаться в том, чтобы каким-либо образом, отобразить файл в приложении. Один из вариантов решения такой задачи — это вызвать JS внутри нашего компонента. Поэтому, прежде, чем мы задействуем в работе приложения IMediaPicker, добавим в разметку компонента Home следующий скрипт:

<script>
    window.setSource = async (elementId, stream, contentType, title) => {
      const arrayBuffer = await stream.arrayBuffer();
      let blobOptions = {};
      if (contentType) {
        blobOptions['type'] = contentType;
      }
      const blob = new Blob([arrayBuffer], blobOptions);
      const url = URL.createObjectURL(blob);
      const element = document.getElementById(elementId);
      element.title = title;
      element.onload = () => {
        URL.revokeObjectURL(url);
      }
      element.src = url;
    }
</script>

Здесь JS-функция setSource принимает значение атрибута id html-элемента для отображения содержимого файла, поток данных документа, типа контента и заголовок для отображения. Чтобы воспользоваться этой функцией в коде нашего компонента добавим следующий метод в блок @code{}

private async Task ShowFileAsync(string url, string title, string id)
{
    using FileStream fileStream = File.OpenRead(url);
    var strRef = new DotNetStreamReference(fileStream);
    await JS.InvokeVoidAsync("setSource", id, strRef, contentType, title);
}

Этот метод получает поток FileStream файла, заключает его в DotNetStreamReference, чтобы выполнить потоковую передачу изображения клиенту с использованием JS и, затем, вызывает функцию setSource. Чтобы можно было вызвать JS в коде компонента, например, как это делается у нас:

await JS.InvokeVoidAsync("setSource", id, strRef, contentType, title);

в компонент Razor необходимо внедрить сервис Microsoft.JSInterop.IJSRuntime, используя директиву @inject. То есть, окончательный, на данный момент, код компонента Home у вас должен получиться таким:

@page "/"
@inject IJSRuntime JS

<script>
    window.setSource = async (elementId, stream, contentType, title) => {
      const arrayBuffer = await stream.arrayBuffer();
      let blobOptions = {};
      if (contentType) {
        blobOptions['type'] = contentType;
      }
      const blob = new Blob([arrayBuffer], blobOptions);
      const url = URL.createObjectURL(blob);
      const element = document.getElementById(elementId);
      element.title = title;
      element.onload = () => {
        URL.revokeObjectURL(url);
      }
      element.src = url;
    }
</script>

@code{
    private async Task ShowFileAsync(string url, string title, string id)
    {
        using FileStream fileStream = File.OpenRead(url);
        var strRef = new DotNetStreamReference(fileStream);
        await JS.InvokeVoidAsync("setSource", id, strRef, contentType, title);
    }
}

Теперь можно воспользоваться IMediaPicker для выбора фотографии. Добавим в разметку компонента следующий код:

<button @onclick="PickPhoto">Выбрать...</button>

<div class="d-flex flex-column">
    <img id="iframe" style="height: calc(100vh - 200px)" />
</div>

Обратите внимание на значение атрибута id у элемента img — именно это значение мы будем передавать в метод ShowFileAsync(). В код C# компнента добавим новый метод для выбора фотографии:

private async Task PickPhoto()
{
    FileResult? result = await MediaPicker.Default.PickPhotoAsync();
    if (result != null)
    {
        await ShowFileAsync(result.FullPath, result.FileName, "iframe");
    }
}

Здесь мы вызываем метод PickPhotoAsync(), затем проверяем выбрал ли пользователь файл:

if (result != null)

и вызываем вспомогательный метод ShowFileAsync() чтобы показать выбранный файл пользователю. Запустим приложение и проверим работу. Как только вы нажмете кнопку «Выбрать…» приложение откроет диалог выбора фотографий:

После выбора файла он прогрузится в нашем приложении

Как сделать фотографию

Непосредственно со съемкой фотографии ситуация не многим сложнее — вызываем метод CapturePhotoAsync()и повторяем процесс загрузки фотографии в приложении, как показано выше. Например, добавим в разметку компонента новую кнопку:

<button @onclick="CreatePhoto">Новое фото</button>

а в код компонента новый метод:

public async Task CreatePhoto()
{
    if (MediaPicker.Default.IsCaptureSupported)
    {
        FileResult? result = await MediaPicker.Default.CapturePhotoAsync();
        if (result != null)
        {
            await ShowFileAsync(result.FullPath, result.FileName, "iframe");
        }
    }
}

Теперь можете запустить приложение и убедиться, что фотография действительно загружается в приложении (при первой попытке сделать фотографию, приложение попросит разрешение на доступ к камере):

Однако, после того, как вы закроете приложение, то нигде не обнаружите свою фотографию так как она сохраняется в кэше для нашего приложения:

Чтобы сохранить файл фотографии в месте откуда этот файл будет доступен не только нашему приложению, необходимо использовать возможности конкретной платформы. Ниже представлен один из вариантов сохранения фотографии в папку DCIM на устройстве Android.

public async Task CreatePhoto()
{
    if (MediaPicker.Default.IsCaptureSupported)
    {
        FileResult? result = await MediaPicker.Default.CapturePhotoAsync();
        if (result != null)
        {
            await ShowFileAsync(result.FullPath, result.FileName, "iframe");
            var dcimFolder = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDcim).Path;

            string localFilePath = Path.Combine(dcimFolder, result.FileName);
            using Stream sourceStream = await result.OpenReadAsync();
            using FileStream fileStream = File.OpenWrite(localFilePath);

            await sourceStream.CopyToAsync(fileStream);
        }
    }
}

Здесь мы получаем путь к папке DCIM, используя объект Environment из пространства имен Android.OS:

var dcimFolder = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDcim).Path;

и далее, используя этот путь, копируем файл из кэша приложения:

string localFilePath = Path.Combine(dcimFolder, Path.GetFileName(result.FileName));

using Stream sourceStream = await result.OpenReadAsync();
using FileStream fileStream = File.OpenWrite(localFilePath);

await sourceStream.CopyToAsync(fileStream);

Теперь можно запустить приложение и убедиться, что сделанная вами фотография показывается в приложении, а также копируется в папку DCIM устройства.

Итого

Интерфейс IMediaPicker предоставляет нам базовый функционал по работе с мультимедиа на устройстве Android — выбрать файл, используя штатные средства устройства или сделать фотографию. Успешный вызов того или иного метода интерфейса возвращает нам объект типа FileResult, используя который, мы должны самостоятельно решать, что необходимо сделать с файлом — отобразить в приложении, сохранить в общедоступном месте на устройстве и так далее.

Подписаться
Уведомить о
guest
0 Комментарий
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии