Irbisa · Шпаргалка

Изображения, видео и камера

Пайплайн изображений, ImageCache, декодирование, video_player, камера, аудио, EXIF, превью

Middle-разработчик · 21 вопрос

  1. 01

    Как Flutter выбирает между версиями ассета 1x, 2x и 3x и что пойдёт не так, если положить только одну?

    AssetImage находит в манифесте ассетов все варианты пути, выбирает тот, чей коэффициент папки (2.0x, 3.0x) ближе всего к device pixel ratio экрана, и выставляет картинке scale, поэтому она занимает одинаковый логический размер на любом экране.

    # pubspec.yaml: list the main file (or its folder); variants are found automaticallyflutter:  assets:    - assets/images/logo.png # On disk:…
  2. 02

    Как показать SVG во Flutter и почему виджет Image не умеет его отображать?

    Через пакет flutter_svg: SvgPicture.asset, SvgPicture.network или SvgPicture.string.

    import 'package:flutter_svg/flutter_svg.dart'; Widget emptyState() => SvgPicture.asset(      'assets/icons/empty_state.svg', // declared under flutter: assets:      width: 160,      colorFilter: const ColorFilter.mode(Colors.grey, BlendMode.srcIn),…
  3. 03

    Как воспроизвести аудиофайл во Flutter и что нужно сделать с плеером, когда экран закрывается?

    Взять пакет вроде just_audio или audioplayers: создать AudioPlayer, задать источник (ассет, файл или URL), вызвать play() и вызвать dispose(), когда владелец уходит, потому что плеер держит нативный декодер и аудиофокус.

    class DingButton extends StatefulWidget {  const DingButton({super.key});   @override  State<DingButton> createState() => _DingButtonState();}…
  4. 04

    Как Image.network загружает и кеширует картинку и какую роль играют ImageProvider, ImageStream и ImageCache?

    Image.network создаёт NetworkImage (ImageProvider, то есть ключ кеша плюс загрузчик).

  5. 05

    Сетка фотографий в полном разрешении съедает всю память, хотя imageCache ограничен 100 МБ. Почему и как это исправить?

    Лимит управляет лишь тем, что кеш держит про запас; картинку на экране удерживает сам виджет, и вытеснение её не освобождает.

    class PhotoGrid extends StatelessWidget {  const PhotoGrid({super.key, required this.urls});  final List<String> urls;   @override  Widget build(BuildContext context) {…
  6. 06

    Аватар чёткий на старом телефоне, но мыльный на новом, а некоторые фото выглядят сплющенными. Что не так с этим кодом?

    Widget avatar(String url) {  return ClipOval(    child: Image.network(      url,      width: 96,      height: 96,      fit: BoxFit.cover,      cacheWidth: 96,      cacheHeight: 96,    ),  );}

    cacheWidth и cacheHeight задаются в физических пикселях: на экране 3x круг в 96 логических пикселей требует 288, поэтому bitmap в 96 пикселей растягивается и мылится.

  7. 07

    Вы прогреваете кеш hero-картинки вот так, но экран деталей всё равно сначала показывает плейсхолдер. Почему?

    Future<void> openDetails(BuildContext context, Product p) async {  await precacheImage(NetworkImage(p.heroUrl), context);  if (!context.mounted) return;  Navigator.of(context).push(MaterialPageRoute(    builder: (_) => Image.network(      p.heroUrl,      cacheWidth: 1080,      loadingBuilder: (context, child, progress) =>          progress == null ? child : const HeroPlaceholder(),    ),  ));}

    Прогрев положил в кеш ключ NetworkImage(url), а экран просит ResizeImage(NetworkImage(url), width: 1080): cacheWidth меняет ключ кеша, поэтому экран промахивается мимо кеша и начинает загрузку заново.

  8. 08

    Что cached_network_image добавляет поверх Image.network и что всё равно приходится настраивать самому?

    Дисковый кеш: через flutter_cache_manager он сохраняет скачанные файлы в кеш-директорию приложения, и после перезапуска картинки грузятся с диска, а не из сети.

    final feedCache = CacheManager(  Config(    'feedImages',    stalePeriod: const Duration(days: 7), // unused files are deleted after 7 days; freshness comes from Cache-Control    maxNrOfCacheObjects: 2000,  ),…
  9. 09

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

    class _ProfileHeaderState extends State<ProfileHeader> {  late User _user = widget.user;   Future<void> _changeAvatar(Uint8List cropped) async {    // The server overwrites users/<id>/avatar.jpg and returns the updated user.    final updated = await widget.api.uploadAvatar(_user.id, cropped);    if (!mounted) return;    setState(() => _user = updated);  }   @override  Widget build(BuildContext context) => CircleAvatar(        radius: 48,        backgroundImage: NetworkImage(_user.avatarUrl),      );}

    URL не меняется, а NetworkImage использует URL (и scale) как ключ кэша: после setState аватар получает равный провайдер, поэтому Flutter берёт старое декодированное изображение и новый файл вообще не скачивает.

  10. 10

    Как дать пользователю выбрать фото или сделать снимок через image_picker и какие разрешения для этого нужны на iOS и Android?

    ImagePicker().pickImage(source: ...) открывает системный выбор фото или камеру и возвращает XFile с копией во временной директории либо null, если пользователь отменил выбор.

    final _picker = ImagePicker(); Future<XFile?> pickPhoto({required bool fromCamera}) {  return _picker.pickImage(    source: fromCamera ? ImageSource.camera : ImageSource.gallery,    maxWidth: 2048,             // resized natively, before Dart sees it…
  11. 11

    Фото, загруженные с телефона, на сайте отображаются повёрнутыми. Почему и как это исправить до загрузки?

    Камера хранит пиксели в ориентации сенсора, а поворот записывает в EXIF-тег Orientation.

    Future<Uint8List> prepareForUpload(String path) async {  final bytes = await FlutterImageCompress.compressWithFile(    path,    minWidth: 2048,  // shrinks until the shorter side is 2048; never upscales    minHeight: 2048,    quality: 82,…
  12. 12

    Сжатие фото перед загрузкой замораживает UI на секунду-две. Почему await не помогает и как это исправить?

    Декодирование, ресайз и перекодирование 12-мегапиксельного фото чистым Dart-пакетом image занимают секунды работы процессора, а async-функция всё равно выполняет её на UI-изоляте: await отдаёт управление только в настоящих асинхронных паузах.

    // wrong: runs on the UI isolate; no frames until it returns.Future<Uint8List> shrinkOnUiIsolate(Uint8List bytes) async {  final decoded = img.decodeImage(bytes)!;  return img.encodeJpg(img.copyResize(decoded, width: 2048), quality: 82);}…
  13. 13

    Как воспроизвести видео через video_player и что нужно учесть в жизненном цикле контроллера?

    Создать VideoPlayerController (networkUrl, asset или file) в initState, вызвать initialize(), показывать VideoPlayer(controller) внутри AspectRatio, когда value.isInitialized станет true, и вызвать dispose() в dispose.

    class ClipPlayer extends StatefulWidget {  const ClipPlayer({super.key, required this.url});  final Uri url;   @override  State<ClipPlayer> createState() => _ClipPlayerState();…
  14. 14

    Этот ролик начинается сначала и память растёт при каждой перестройке родителя. Что не так с виджетом?

    class ClipView extends StatelessWidget {  const ClipView({super.key, required this.url});  final Uri url;   @override  Widget build(BuildContext context) {    final controller = VideoPlayerController.networkUrl(url)      ..initialize()      ..play();    return AspectRatio(      aspectRatio: 16 / 9,      child: VideoPlayer(controller),    );  }}

    Контроллер, а с ним и нативный плеер, создаётся внутри build, поэтому каждая перестройка создаёт новый, который стартует с нуля, а у старых никто не вызывает dispose().

  15. 15

    Что нужно, чтобы аудио продолжало играть при заблокированном экране и показывало управление на экране блокировки: на iOS, Android и в Dart?

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

    // Info.plist: UIBackgroundModes = [audio]// AndroidManifest.xml: FOREGROUND_SERVICE and FOREGROUND_SERVICE_MEDIA_PLAYBACK,// plus audio_service's service with foregroundServiceType="mediaPlayback".late final AudioHandler audioHandler; Future<void> main() async {…
  16. 16

    Найдите баг: после возврата приложения из фона превью камеры остаётся чёрным, а в логе появляется CameraException.

    class _ScannerState extends State<Scanner> with WidgetsBindingObserver {  late final _camera =      CameraController(widget.camera, ResolutionPreset.medium)        ..initialize().then((_) => mounted ? setState(() {}) : null);  // initState: addObserver. dispose: removeObserver, _camera.dispose()  // build: CameraPreview(_camera)   @override  void didChangeAppLifecycleState(AppLifecycleState state) {    // free the camera for other apps    if (state == AppLifecycleState.inactive) _camera.dispose();    if (state == AppLifecycleState.resumed) {      _camera.initialize(); // take it back    }  }}

    На resumed код вызывает initialize() у контроллера, который сам же освободил на inactive.

  17. 17

    Где должен происходить ресайз картинок (на клиенте, в CDN-сервисе изображений или в пайплайне загрузки) и почему?

    До того, как байты попадут на телефон: CDN-сервис изображений или пайплайн загрузки готовит варианты нужных размеров, а клиент запрашивает наименьший, который покрывает его слот.

  18. 18

    Покрывает ли video_player адаптивный стриминг (HLS/DASH) и DRM и что использовать, когда не покрывает?

    Адаптивный стриминг есть: дайте ему HLS-манифест, и ExoPlayer или AVPlayer сами переключают битрейт.

  19. 19

    Как с плагином camera анализировать кадры из startImageStream, чтобы превью не дёргалось?

    Подтормаживания возникают от тяжёлой работы с кадром на UI-изоляте; обрабатывайте по одному кадру вне его, остальные кадры, пришедшие тем временем, отбрасывайте.

    CameraController createController(CameraDescription camera) => CameraController(      camera,      ResolutionPreset.medium, // enough for detection, far less work per frame      enableAudio: false,      imageFormatGroup:          Platform.isAndroid ? ImageFormatGroup.nv21 : ImageFormatGroup.bgra8888,…
  20. 20

    Когда для показа кадров видео или камеры использовать Texture, а когда platform view и сколько каждый стоит на кадр?

    По умолчанию берут Texture: нативный код рисует кадры в GPU-поверхность движка, и Flutter композитит её как обычный слой, без копирования в Dart.

  21. 21

    Как бы вы спроектировали офлайн-загрузку видео или выпусков подкаста: что скачивать, где хранить и как ограничить занимаемое место?

    Скачивать одну выбранную версию каждого выпуска целым файлом, который принадлежит вам, класть его в директорию приложения, которую система не чистит и не бэкапит, и учитывать каждый файл в небольшом индексе с бюджетом в байтах и вытеснением давно не прослушанного.