Содержание
Еще одной достаточно распространенной функцией приложений Android является съемка фото- и видеоматериалов. С использованием интерфейса IMediaPicker мы можем выбирать различные медиафайлы, а также запускать фото- и видеокамеры для начала процесса съемки.
Определение необходимых разрешений
Для использования всех возможностей IMediaPicker нам потребуются следующие разрешения:
- CAMERA — для доступа к камере устройства.
Также, в зависимости от версии Android нам потребуются следующие разрешения:
- Если приложение предназначено для Android 12 или более поздней версий, необходимо запросить разрешения
WRITE_EXTERNAL_STORAGEиREAD_EXTERNAL_STORAGE. - Если приложение предназначено для Android 13 или более поздней версии и нуждается в доступе к файлам мультимедиа, созданным другими приложениями, необходимо запросить одно или несколько следующих разрешений вместо
READ_EXTERNAL_STORAGEразрешения:READ_MEDIA_IMAGESREAD_MEDIA_VIDEOREAD_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, используя который, мы должны самостоятельно решать, что необходимо сделать с файлом — отобразить в приложении, сохранить в общедоступном месте на устройстве и так далее.


