Содержание
Еще одной достаточно распространенной функцией приложений Android является съемка фото- и видеоматериалов. С использованием интерфейса IMediaPicker
мы можем выбирать различные медиафайлы, а также запускать фото- и видеокамеры для начала процесса съемки.
Определение необходимых разрешений
Для использования всех возможностей IMediaPicker
нам потребуются следующие разрешения:
- CAMERA — для доступа к камере устройства.
Также, в зависимости от версии Android нам потребуются следующие разрешения:
- Если приложение предназначено для Android 12 или более поздней версий, необходимо запросить разрешения
WRITE_EXTERNAL_STORAGE
иREAD_EXTERNAL_STORAGE
. - Если приложение предназначено для Android 13 или более поздней версии и нуждается в доступе к файлам мультимедиа, созданным другими приложениями, необходимо запросить одно или несколько следующих разрешений вместо
READ_EXTERNAL_STORAGE
разрешения:READ_MEDIA_IMAGES
READ_MEDIA_VIDEO
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
предоставляет нам следующие методы:
Метод | Тип | Описание |
Capture |
Task<FileResult> |
Открывает камеру для съемки. |
Capture |
Task<FileResult> |
Открывает камеру для съемки видео. |
Pick |
Task<FileResult> |
Открывает браузер мультимедиа для выбора фотографии. |
Pick |
Task<FileResult> |
Открывает браузер мультимедиа для выбора видео. |
а также одно свойство:
public bool IsCaptureSupported { get; }
которое возвращает значение, указывающее, поддерживается ли запись мультимедиа на этом устройстве.
Класс FileResult
представляет файл после его выбора пользователем и содержит следующие полезные для нас свойства:
Свойство | Тип | Описание |
Content |
string |
Тип контента файла в качестве типа MIME (например: image/png ). |
File |
string |
Имя файла |
Full |
string |
Полный путь, включая имя файла. |
Доступ к реализации IMediaPicker
по умолчанию осуществляется через свойство Default
класса MediaPicker
. Теперь рассмотрим основные моменты работы с IMediaPicker
на примере нашего приложения.
Как выбрать и открыть фотографию в приложении Blazor Hybrid
Выбор мультимедиа на устройстве Android осуществляется с использованием метода Capture
. Всё, что сделает этот метод — это вернет нам объект типа 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()
чтобы показать выбранный файл пользователю. Запустим приложение и проверим работу. Как только вы нажмете кнопку «Выбрать…» приложение откроет диалог выбора фотографий:
После выбора файла он прогрузится в нашем приложении
Как сделать фотографию
Непосредственно со съемкой фотографии ситуация не многим сложнее — вызываем метод Capture
и повторяем процесс загрузки фотографии в приложении, как показано выше. Например, добавим в разметку компонента новую кнопку:
<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
, используя который, мы должны самостоятельно решать, что необходимо сделать с файлом — отобразить в приложении, сохранить в общедоступном месте на устройстве и так далее.