diff --git a/kDrive/UI/Controller/Files/Preview/PreviewViewController.swift b/kDrive/UI/Controller/Files/Preview/PreviewViewController.swift
index 243eb2a5d..6064036a5 100644
--- a/kDrive/UI/Controller/Files/Preview/PreviewViewController.swift
+++ b/kDrive/UI/Controller/Files/Preview/PreviewViewController.swift
@@ -16,6 +16,7 @@
along with this program. If not, see .
*/
+import AVFoundation
import FloatingPanel
import InfomaniakCore
import InfomaniakDI
@@ -451,6 +452,8 @@ final class PreviewViewController: UIViewController, PreviewContentCellDelegate,
let file = previewFiles[index]
if ConvertedType.documentTypes.contains(file.convertedType) {
handleOfficePreviewError(error, previewIndex: index)
+ } else if file.convertedType == .audio {
+ handleAudioPreviewError(error, previewIndex: index)
}
// We have to delay reload because errorWhilePreviewing can be called when the collectionView requests a new cell in
@@ -460,6 +463,19 @@ final class PreviewViewController: UIViewController, PreviewContentCellDelegate,
}
}
+ func handleAudioPreviewError(_ error: Error, previewIndex: Int) {
+ let file = previewFiles[previewIndex]
+
+ guard let avError = error as? AVError,
+ avError.code == .fileFormatNotRecognized else {
+ return
+ }
+
+ previewErrors[file.id] = PreviewError(fileId: file.id, downloadError: nil)
+ guard file.isLocalVersionOlderThanRemote else { return }
+ downloadFile(at: IndexPath(item: previewIndex, section: 0))
+ }
+
func handleOfficePreviewError(_ error: Error, previewIndex: Int) {
let file = previewFiles[previewIndex]
@@ -526,6 +542,10 @@ final class PreviewViewController: UIViewController, PreviewContentCellDelegate,
return
}
+ downloadFile(at: indexPath)
+ }
+
+ private func downloadFile(at indexPath: IndexPath) {
DownloadQueue.instance.temporaryDownload(
file: currentFile,
userId: accountManager.currentUserId,
@@ -558,6 +578,7 @@ final class PreviewViewController: UIViewController, PreviewContentCellDelegate,
} else {
(collectionView.cellForItem(at: indexPath) as? DownloadingPreviewCollectionViewCell)?
.previewDownloadTask?.cancel()
+ previewErrors[currentFile.id] = nil
collectionView.endEditing(true)
collectionView.reloadItems(at: [indexPath])
updateNavigationBar()
diff --git a/kDrive/UI/View/Files/Preview/AudioCollectionViewCell.swift b/kDrive/UI/View/Files/Preview/AudioCollectionViewCell.swift
index 7522a2c24..a357afef4 100644
--- a/kDrive/UI/View/Files/Preview/AudioCollectionViewCell.swift
+++ b/kDrive/UI/View/Files/Preview/AudioCollectionViewCell.swift
@@ -20,6 +20,7 @@ import Combine
import InfomaniakCore
import kDriveCore
import kDriveResources
+import Kingfisher
import MediaPlayer
import UIKit
@@ -39,6 +40,7 @@ final class AudioCollectionViewCell: PreviewCollectionViewCell {
public lazy var singleTrackPlayer = SingleTrackPlayer(driveFileManager: driveFileManager)
private var cancellables = Set()
+ private var thumbnailDownloadTask: Kingfisher.DownloadTask?
override func awakeFromNib() {
super.awakeFromNib()
@@ -77,6 +79,7 @@ final class AudioCollectionViewCell: PreviewCollectionViewCell {
override func prepareForReuse() {
super.prepareForReuse()
+ thumbnailDownloadTask?.cancel()
setControls(enabled: false)
singleTrackPlayer.reset()
songTitleLabel.text = ""
@@ -91,12 +94,12 @@ final class AudioCollectionViewCell: PreviewCollectionViewCell {
Task { @MainActor in
await singleTrackPlayer.setup(with: frozenFile)
setControls(enabled: true)
- setupObservation()
+ setupObservationFor(frozenFile: frozenFile)
}
}
/// Setup data flow
- @MainActor func setupObservation() {
+ @MainActor func setupObservationFor(frozenFile: File) {
cancellables.forEach { $0.cancel() }
cancellables.removeAll()
@@ -153,9 +156,15 @@ final class AudioCollectionViewCell: PreviewCollectionViewCell {
.onCurrentTrackMetadata
.receive(on: DispatchQueue.main)
.sink { metadata in
- self.artworkImageView.image = metadata.artwork ?? KDriveResourcesAsset.music.image
- self.artistNameLabel.text = metadata.artist
- self.songTitleLabel.text = metadata.title
+ self.handleMetadata(metadata, file: frozenFile)
+ }
+ .store(in: &cancellables)
+
+ singleTrackPlayer
+ .onItemError
+ .receive(on: DispatchQueue.main)
+ .sink { error in
+ self.previewDelegate?.errorWhilePreviewing(fileId: frozenFile.id, error: error)
}
.store(in: &cancellables)
}
@@ -167,6 +176,20 @@ final class AudioCollectionViewCell: PreviewCollectionViewCell {
iconHeightConstraint.constant = isPortrait ? 254 : 120
}
+ func handleMetadata(_ metadata: MediaMetadata, file: File) {
+ if let artwork = metadata.artwork {
+ artworkImageView.image = artwork
+ } else {
+ artworkImageView.image = KDriveResourcesAsset.music.image
+ thumbnailDownloadTask = file.getThumbnail { thumbnail, isThumbnailAvailable in
+ guard isThumbnailAvailable else { return }
+ self.artworkImageView.image = thumbnail
+ }
+ }
+ artistNameLabel.text = metadata.artist
+ songTitleLabel.text = metadata.title
+ }
+
@objc func rotated() {
setUpPlayButtons()
}
diff --git a/kDriveCore/AudioPlayer/SingleTrackPlayer.swift b/kDriveCore/AudioPlayer/SingleTrackPlayer.swift
index 3af228a57..fc69d2fe6 100644
--- a/kDriveCore/AudioPlayer/SingleTrackPlayer.swift
+++ b/kDriveCore/AudioPlayer/SingleTrackPlayer.swift
@@ -45,6 +45,7 @@ public final class SingleTrackPlayer: Pausable {
private var timeObserver: Any?
private var rateObserver: NSKeyValueObservation?
private var statusObserver: NSKeyValueObservation?
+ private var currentItemStatusObserver: NSKeyValueObservation?
private var isInterrupted = false
// MARK: Data flow
@@ -56,6 +57,7 @@ public final class SingleTrackPlayer: Pausable {
public let onPositionChange = PassthroughSubject()
public let onPositionMaximumChange = PassthroughSubject()
public let onCurrentTrackMetadata = PassthroughSubject()
+ public let onItemError = PassthroughSubject()
var player: AVPlayer?
@@ -113,6 +115,10 @@ public final class SingleTrackPlayer: Pausable {
self.player = AVPlayer(playerItem: AVPlayerItem(asset: asset))
await self.setMetaData(from: asset.commonMetadata, playableFileName: playableFile.name)
self.setUpObservers()
+
+ self.currentItemStatusObserver = self.player?.observe(\.currentItem?.status) { _, _ in
+ self.handleItemStatusChange()
+ }
}
}
} else {
@@ -223,6 +229,12 @@ public final class SingleTrackPlayer: Pausable {
}
}
+ private func handleItemStatusChange() {
+ guard let error = player?.currentItem?.error else { return }
+
+ onItemError.send(error)
+ }
+
// MARK: - Observation
private func setUpObservers() {
@@ -271,6 +283,9 @@ public final class SingleTrackPlayer: Pausable {
statusObserver?.invalidate()
statusObserver = nil
+ currentItemStatusObserver?.invalidate()
+ currentItemStatusObserver = nil
+
removeAllRemoteControlEvents()
}