Индикатор выполнения для AVAssetExportSession
У меня есть приложение, которое экспортирует AVMutableComposition
в .mov
файл, и я хотел бы, чтобы пользователь видел статус экспорта с индикатором выполнения так же, как если бы вы отправили текстовое сообщение или загрузили файл.
Я знаю, как создать индикатор выполнения, когда знаю продолжительность задачи (например, воспроизведение аудиофайла), но, поскольку для экспорта не задана продолжительность, я не уверен, что делать дальше.
В настоящее время у меня есть индикатор активности, но он не обеспечивает наилучшего пользовательского опыта.
У кого-нибудь есть указатели?
5 ответов
Некоторое время назад я придумал ответ, поэтому выложу его на тот случай, если он кому-нибудь поможет:
Во-первых, в методе, в котором вы вызываете AVAssetExportSession
Вы должны настроить таймер для обновления вашего UIProgressView
как только вы начали экспорт:
//`AVAssetExportSession` code here
self.exportProgressBarTimer = [NSTimer scheduledTimerWithTimeInterval:.1 target:self selector:@selector(updateExportDisplay) userInfo:nil repeats:YES];
...
Затем вам нужен метод для обновления вашего дисплея, принимая во внимание, что свойство progress на AVAssetExportSession
выходит из 0 - 1:
- (void)updateExportDisplay {
self.exportProgressBar.progress = exportSession.progress;
if (self.exportProgressBar.progress > .99) {
[self.exportProgressBarTimer invalidate];
}
}
Swift 3 пример
Использование Центра уведомлений для отправки обновлений о прогрессе слушателям
//`AVAssetExportSession` code above
var exportProgressBarTimer = Timer() // initialize timer
if #available(iOS 10.0, *) {
exportProgressBarTimer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in
// Get Progress
let progress = Float((exportSession?.progress)!);
if (progress < 0.99) {
let dict:[String: Float] = ["progress": progress]
NotificationCenter.default.post(name: Notification.Name("ProgressBarPercentage"), object: nil, userInfo: dict)
}
}
}
// on exportSession completed
exportSession?.exportAsynchronously(completionHandler: {
exportProgressBarTimer.invalidate(); // remove/invalidate timer
if exportSession?.status == AVAssetExportSessionStatus.completed {
// [....Some Completion Code Here]
}
})
Затем настройте прослушиватель центра уведомлений в любом месте, где вы хотите
NotificationCenter.default.addObserver(self, selector: #selector(self.statusUpdate(_:)), name: NSNotification.Name(rawValue: "ProgressBarPercentage"), object: nil)
С той же проблемой, с которой я столкнулся в iOS 8.0, я решил ее, используя способ отправки
- (void)convertVideoToLowQuailtyWithInputURL:(NSURL*)inputURL outputURL:(NSURL*)outputURL handler:(void (^)(AVAssetExportSession*))handler{
[[NSFileManager defaultManager] removeItemAtURL:outputURL error:nil];
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:inputURL options:nil];
exportSession2 = [[AVAssetExportSession alloc] initWithAsset:asset presetName:AVAssetExportPresetLowQuality];
exportSession2.outputURL = outputURL;
exportSession2.outputFileType = AVFileTypeQuickTimeMovie;
[exportSession2 exportAsynchronouslyWithCompletionHandler:^(void)
{
handler(exportSession2);
}];
dispatch_async(dispatch_get_main_queue(), ^(void){
self.exportProgressBarTimer = [NSTimer scheduledTimerWithTimeInterval:.1 target:self selector:@selector(updateExportDisplay) userInfo:nil repeats:YES];
});
}
Используйте ниже строки кода.
AVAssetExportSession *session = [AVAssetExportSession exportSessionWithAsset:composition presetName:AVAssetExportPresetMediumQuality];
self.exportSession = session;
// 出力先(テンポラリファイル)の設定。
NSString *filePath = NSTemporaryDirectory();
filePath = [filePath stringByAppendingPathComponent:@"out.mov"];
[[NSFileManager defaultManager] removeItemAtPath:filePath error:nil];
session.outputURL = [NSURL fileURLWithPath:filePath];
// 出力タイプの設定。
session.outputFileType = AVFileTypeQuickTimeMovie;
// 非同期エクスポートの開始。
[session exportAsynchronouslyWithCompletionHandler:^{
if (session.status == AVAssetExportSessionStatusCompleted) {
// フォトアルバムへの書き込み。
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[library writeVideoAtPathToSavedPhotosAlbum:session.outputURL completionBlock:^(NSURL *assetURL, NSError *error){
if (error) {
self.resultLabel.text = [NSString stringWithFormat:@"アセット書き込み失敗\n%@", error];
} else {
self.resultLabel.text = [NSString stringWithFormat:@"完了\n%@", assetURL];
}
}];
[library autorelease];
} else if (session.status == AVAssetExportSessionStatusCancelled) {
self.resultLabel.text = @"エクスポート中断";
} else {
self.resultLabel.text = [NSString stringWithFormat:@"エクスポート失敗\n%@", session.error];
}
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
while (session.status == AVAssetExportSessionStatusExporting) {
dispatch_sync(dispatch_get_main_queue(), ^{
self.progressView.progress = session.progress;
});
}
});
Ссылочная ссылка: https://github.com/keijiro/iOS4BookSampleCode/blob/master/3.3.SimpleExport/Classes/SimpleExportViewController.m
У меня есть решение, которое хорошо работает со SwiftUI и async/await.
class ObservableExporter {
var progressTimer: Timer?
let session: AVAssetExportSession
public let progress: Binding<Double>
public var duration: TimeInterval?
init(session: AVAssetExportSession, progress: Binding<Double>) {
self.session = session
self.progress = progress
}
func export() async throws -> AVAssetExportSession.Status {
progressTimer = Timer(timeInterval: 0.1, repeats: true, block: { timer in
self.progress.wrappedValue = Double(self.session.progress)
})
RunLoop.main.add(progressTimer!, forMode: .common)
let startDate = Date()
await session.export()
progressTimer?.invalidate()
let endDate = Date()
duration = endDate.timeIntervalSince(startDate)
if let error = session.error {
throw error
} else {
return session.status
}
}
}
Вы просто инициализируетеObservableExporter
класс сsession
иprogress
является привязкой к@State
переменная в вашем классе SwiftUI, подходящая для использованияProgressView
.
Пример, который показывает, как это воплощено в образце приложения, — Образец приложения Watermark.