Video playback (#308)

* Create media player screen

* Introduce `FileCache` to cache message attachments

* Add file loading functionality into the media provider

* Process tap action on timeline items

* Pass item taps to view model

* Navigate to media player on view model callback

* Commit project file

* Add changelog

* Rename media to video

* Add a loader when large videos being processed

* Add back button explicitly on video screen, fixes for light scheme

* Handle right swipe to dismiss video
This commit is contained in:
ismailgulek
2022-11-14 11:58:18 +03:00
committed by GitHub
parent 8f1e8e6c2a
commit 2e56fe9380
27 changed files with 820 additions and 39 deletions
@@ -20,13 +20,16 @@ import UIKit
struct MediaProvider: MediaProviderProtocol {
private let clientProxy: ClientProxyProtocol
private let imageCache: Kingfisher.ImageCache
private let fileCache: FileCache
private let backgroundTaskService: BackgroundTaskServiceProtocol
init(clientProxy: ClientProxyProtocol,
imageCache: Kingfisher.ImageCache,
fileCache: FileCache,
backgroundTaskService: BackgroundTaskServiceProtocol) {
self.clientProxy = clientProxy
self.imageCache = imageCache
self.fileCache = fileCache
self.backgroundTaskService = backgroundTaskService
}
@@ -88,6 +91,51 @@ struct MediaProvider: MediaProviderProtocol {
return .failure(.failedRetrievingImage)
}
}
func fileFromSource(_ source: MediaSource?, fileExtension: String) -> URL? {
guard let source else {
return nil
}
let cacheKey = fileCacheKeyForURLString(source.underlyingSource.url())
return fileCache.file(forKey: cacheKey, fileExtension: fileExtension)
}
@discardableResult func loadFileFromSource(_ source: MediaSource, fileExtension: String) async -> Result<URL, MediaProviderError> {
if let url = fileFromSource(source, fileExtension: fileExtension) {
return .success(url)
}
let loadFileBgTask = await backgroundTaskService.startBackgroundTask(withName: "LoadFile: \(source.url.hashValue)")
defer {
loadFileBgTask?.stop()
}
let cacheKey = fileCacheKeyForURLString(source.url)
do {
let data = try await clientProxy.loadMediaContentForSource(source.underlyingSource)
let url = try fileCache.store(data, with: fileExtension, forKey: cacheKey)
return .success(url)
} catch {
MXLog.error("Failed retrieving file with error: \(error)")
return .failure(.failedRetrievingImage)
}
}
func fileFromURLString(_ urlString: String?, fileExtension: String) -> URL? {
guard let urlString else {
return nil
}
return fileFromSource(MediaSource(source: clientProxy.mediaSourceForURLString(urlString)),
fileExtension: fileExtension)
}
func loadFileFromURLString(_ urlString: String, fileExtension: String) async -> Result<URL, MediaProviderError> {
await loadFileFromSource(MediaSource(source: clientProxy.mediaSourceForURLString(urlString)),
fileExtension: fileExtension)
}
// MARK: - Private
@@ -98,6 +146,13 @@ struct MediaProvider: MediaProviderProtocol {
return urlString
}
}
private func fileCacheKeyForURLString(_ urlString: String) -> String {
guard let component = urlString.split(separator: "/").last else {
return urlString
}
return String(component)
}
}
private extension ImageCache {
@@ -19,6 +19,7 @@ import UIKit
enum MediaProviderError: Error {
case failedRetrievingImage
case failedRetrievingFile
case invalidImageData
}
@@ -30,6 +31,14 @@ protocol MediaProviderProtocol {
func imageFromURLString(_ urlString: String?, avatarSize: AvatarSize?) -> UIImage?
@discardableResult func loadImageFromURLString(_ urlString: String, avatarSize: AvatarSize?) async -> Result<UIImage, MediaProviderError>
func fileFromSource(_ source: MediaSource?, fileExtension: String) -> URL?
@discardableResult func loadFileFromSource(_ source: MediaSource, fileExtension: String) async -> Result<URL, MediaProviderError>
func fileFromURLString(_ urlString: String?, fileExtension: String) -> URL?
@discardableResult func loadFileFromURLString(_ urlString: String, fileExtension: String) async -> Result<URL, MediaProviderError>
}
extension MediaProviderProtocol {
@@ -49,4 +49,20 @@ struct MockMediaProvider: MediaProviderProtocol {
return .success(image)
}
func fileFromSource(_ source: MediaSource?, fileExtension: String) -> URL? {
nil
}
@discardableResult func loadFileFromSource(_ source: MediaSource, fileExtension: String) async -> Result<URL, MediaProviderError> {
.failure(.failedRetrievingFile)
}
func fileFromURLString(_ urlString: String?, fileExtension: String) -> URL? {
nil
}
@discardableResult func loadFileFromURLString(_ urlString: String, fileExtension: String) async -> Result<URL, MediaProviderError> {
.failure(.failedRetrievingFile)
}
}