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

Понимание роли AVFoundation в поколении миниатюр

AVFoundation является основной структурой Apple для работы с аудиовизуальными носителями. Для миниатюрной генерации ключевыми классами являются AVAsset и AVAssetImageGenerator.

  • AVAsset: представляет собой временной аудиовизуальный актив (локальное или удаленное видео). Он содержит информацию о треке, продолжительность и метаданные.
  • AVAssetImageGenerator: Предоставляет способы получения CGImageRefs из актива через определённые промежутки времени. Он уважает ориентацию видео, применяет корректировки цветового пространства и поддерживает асинхронную генерацию объёмов.

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

Настройка проекта

Начните с импорта AVFoundation в ваш файл Swift:

import AVFoundation

Вам также нужен UIKit для отображения сгенерированных изображений. Создайте из URL. Для локальных файлов используйте . Для удаленных видео используйте и убедитесь, что актив может быть инициализирован:

// Local
let localURL = Bundle.main.url(forResource: "sample", withExtension: "mp4")!
let asset = AVAsset(url: localURL)

// Remote
let remoteURL = URL(string: "https://example.com/video.mp4")!
let remoteAsset = AVAsset(url: remoteURL)

Создание одного миниатюрного

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

let imageGenerator = AVAssetImageGenerator(asset: asset)
imageGenerator.appliesPreferredTrackTransform = true

let time = CMTime(seconds: 2.0, preferredTimescale: 600) // 2 seconds into the video

do {
 let cgImage = try imageGenerator.copyCGImage(at: time, actualTime: nil)
 let thumbnail = UIImage(cgImage: cgImage)
 // Update UI on main thread
 DispatchQueue.main.async {
 imageView.image = thumbnail
 }
} catch {
 print("Thumbnail generation failed: \(error.localizedDescription)")
}

Важно: Настройка на обеспечивает, чтобы полученное изображение уважало метаданные вращения видео (портрет против ландшафта).

Выбираем правильное время

Миниатюры часто берутся в начале видео, но рассмотрите возможность использования времени, которое представляет контент, например, 10% в продолжительность.

let duration = asset.duration
let midTime = CMTimeMultiplyByFloat64(duration, multiplier: 0.1)

Обработка ошибок и Edge Cases

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

  • Недействительный актив: Видео может быть повреждено или URL-адрес недостижим.
  • Пропущенный видео трек: Некоторые активы содержат только аудио; проверьте .
  • Время вне пределов: Убедитесь, что запрашиваемое время находится между и продолжительностью актива.
  • Ошибки сети: Удаленные активы могут не загружаться. Используйте перед генерацией миниатюр.

Пример свойств актива предварительной загрузки:

let assetKeys = ["tracks", "duration", "playable"]
asset.loadValuesAsynchronously(forKeys: assetKeys) {
 var error: NSError?
 let status = asset.statusOfValue(forKey: "tracks", error: &error)
 if status == .loaded {
 // Safe to generate thumbnails
 } else {
 // Handle error
 }
}

Настройка миниатюры Output

AVAssetImageGenerator предлагает несколько свойств для точной настройки выходного изображения.

Максимальный размер

Ограничьте генерируемое изображение конкретным ограничивающим окном, используя . Это уменьшает накладные расходы на память и ускоряет генерацию для больших видео.

imageGenerator.maximumSize = CGSize(width: 640, height: 480)

Сгенерированное изображение будет масштабироваться, чтобы соответствовать этим размерам, сохраняя соотношение сторон.

Требуемая толерантность ко времени

По умолчанию возвращает точный кадр в запрашиваемое время (или как можно ближе). Вы можете торговать точностью скорости, устанавливая и . Для миниатюр часто приемлем и быстрее допустимо отклонение в 0,5 секунды:

imageGenerator.requestedTimeToleranceBefore = CMTime(seconds: 0.5, preferredTimescale: 600)
imageGenerator.requestedTimeToleranceAfter = CMTime(seconds: 0.5, preferredTimescale: 600)

Режим диафрагмы

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

imageGenerator.apertureMode = .cleanAperture

Применение трансформаций

Если вам нужно повернуть или перевернуть изображение (например, для определенной ориентации), используйте преобразования Core Graphics. Один из подходов заключается в захвате необработанного изображения, а затем примените CGAffineTransform вручную. Альтернативно, вы можете установить на (как показано) и доверять естественной ориентации актива.

Эффективно генерировать несколько миниатюр

Для видеосетей, скруббинга временных рамок или предварительных просмотров раскадров часто требуются десятки миниатюр. Избегайте вызова в цикле на главном потоке. Вместо этого используйте асинхронный метод . Он эффективно обрабатывает многократные запросы времени, используя внутренний параллелизм.

var times: [NSValue] = []
for i in 0..<10 {
 let time = CMTime(seconds: Double(i) * 2.0, preferredTimescale: 600)
 times.append(NSValue(time: time))
}

imageGenerator.generateCGImagesAsynchronously(forTimes: times) { requestedTime, cgImage, actualTime, result, error in
 if result == .succeeded, let cgImage = cgImage {
 let thumbnail = UIImage(cgImage: cgImage)
 DispatchQueue.main.async {
 // Append to array or update collection view cell
 }
 } else {
 print("Failed at \(requestedTime): \(error?.localizedDescription ?? "unknown")")
 }
}

Этот метод хорошо масштабируется и позволяет постепенно обновлять пользовательский интерфейс. Не забудьте позвонить , если пользователь покидает экран, чтобы освободить ресурсы.

Работа с удаленными видео

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

let urlAsset = AVURLAsset(url: remoteURL, options: [AVURLAssetPreferPreciseDurationAndTimingKey: true])
urlAsset.loadValuesAsynchronously(forKeys: ["tracks"]) {
 // Then create image generator and proceed
}

Для производственных приложений рассмотрите возможность кэширования миниатюр на диске, чтобы повторный доступ к одному и тому же видео не возвращал изображение. Используйте или кэш на основе файлов, используя URL видео в качестве ключа.

Пример стратегии кэширования

class ThumbnailCache {
 static let shared = ThumbnailCache()
 private let cache = NSCache<NSString, UIImage>()

 func thumbnail(for url: URL) -> UIImage? {
 return cache.object(forKey: url.absoluteString as NSString)
 }

 func setThumbnail(_ image: UIImage, for url: URL) {
 cache.setObject(image, forKey: url.absoluteString as NSString)
 }
}

Объедините это с генератором изображений, чтобы избежать избыточной работы.

Соображения в отношении эффективности

Генерация миниатюр - это очень интенсивный процесс, особенно для видео высокого разрешения 4K. Следуйте этим лучшим практикам:

  • Всегда выполняйте синхронные вызовы в фоновой очереди (например, ).
  • Установите разумный для уменьшения выходных размеров изображения. Размер 320x240 часто бывает достаточным для миниатюр списка.
  • Используйте , чтобы позволить генератору пропустить ближайший кадр вместо решения точного декодирования.
  • Отмените любое асинхронное поколение, когда вид исчезает.
  • Для отображения в виде коллекции, генерируйте миниатюры при точном размере ячейки и избегайте изменения размера основной нити.

Интеграция с Swift Concurrency (Async/Await)

Если ваше приложение нацелено на iOS 15+, вы можете обернуть блокирующий вызов в продолжение для чистого кода асинхронизации / ожидания:

func generateThumbnail(forVideoAt url: URL, at time: CMTime) async throws -> UIImage {
 let asset = AVAsset(url: url)
 let imageGenerator = AVAssetImageGenerator(asset: asset)
 imageGenerator.appliesPreferredTrackTransform = true
 imageGenerator.maximumSize = CGSize(width: 640, height: 480)

 return try await withCheckedThrowingContinuation { continuation in
 let times = [NSValue(time: time)]
 imageGenerator.generateCGImagesAsynchronously(forTimes: times) { _, cgImage, _, result, error in
 if let cgImage = cgImage, result == .succeeded {
 let image = UIImage(cgImage: cgImage)
 continuation.resume(returning: image)
 } else {
 continuation.resume(throwing: error ?? ThumbnailError.generationFailed)
 }
 }
 }
}

Передовые кастомизации

Фотографии с видеокомпозицией

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

imageGenerator.videoComposition = myVideoComposition

Сгенерированный миниатюрный рисунок будет отражать композитный выход.

Создание миниатюр для живых фотографий

Live Photos - это комбинация видео и неподвижного изображения. Чтобы извлечь миниатюру из базового видео, используйте и запросите URL-адрес видео, прежде чем подавать его в AVFoundation.

Доступность и пользовательский опыт

Миниатюры улучшают визуальное сканирование, но всегда предоставляют текстовую альтернативу для пользователей VoiceOver. Используйте на просмотре изображения, описывая видеоконтент, если это возможно.

imageView.isAccessibilityElement = true
imageView.accessibilityLabel = "Thumbnail of video: \(videoTitle)"

Внешние ресурсы

Для более глубокого понимания, проконсультируйтесь со следующим:

Заключение

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