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
@@ -109,6 +109,8 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
func processItemAppearance(_ itemId: String) async { }
func processItemDisappearance(_ itemId: String) async { }
func processItemTap(_ itemId: String) async -> RoomTimelineControllerAction { .none }
func sendMessage(_ message: String) async { }
@@ -98,15 +98,36 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
switch timelineItem {
case let item as ImageRoomTimelineItem:
await loadImageForImageTimelineItem(item)
await loadThumbnailForImageTimelineItem(item)
case let item as VideoRoomTimelineItem:
await loadImageForVideoTimelineItem(item)
await loadThumbnailForVideoTimelineItem(item)
default:
break
}
}
func processItemDisappearance(_ itemId: String) { }
func processItemTap(_ itemId: String) async -> RoomTimelineControllerAction {
guard let timelineItem = timelineItems.first(where: { $0.id == itemId }) else {
return .none
}
switch timelineItem {
case let item as VideoRoomTimelineItem:
await loadVideoForTimelineItem(item)
guard let index = timelineItems.firstIndex(where: { $0.id == itemId }),
let item = timelineItems[index] as? VideoRoomTimelineItem else {
return .none
}
if let videoURL = item.cachedVideoURL {
return .displayVideo(videoURL: videoURL)
}
return .none
default:
return .none
}
}
func sendMessage(_ message: String) async {
switch await timelineProvider.sendMessage(message) {
@@ -244,7 +265,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
return .middle
}
private func loadImageForImageTimelineItem(_ timelineItem: ImageRoomTimelineItem) async {
private func loadThumbnailForImageTimelineItem(_ timelineItem: ImageRoomTimelineItem) async {
if timelineItem.image != nil {
return
}
@@ -268,7 +289,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
}
}
private func loadImageForVideoTimelineItem(_ timelineItem: VideoRoomTimelineItem) async {
private func loadThumbnailForVideoTimelineItem(_ timelineItem: VideoRoomTimelineItem) async {
if timelineItem.image != nil {
return
}
@@ -291,6 +312,32 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
break
}
}
private func loadVideoForTimelineItem(_ timelineItem: VideoRoomTimelineItem) async {
if timelineItem.cachedVideoURL != nil {
// already cached
return
}
guard let source = timelineItem.source else {
return
}
// This is not great. We could better estimate file extension from the mimetype.
let fileExtension = String(timelineItem.text.split(separator: ".").last ?? "mp4")
switch await mediaProvider.loadFileFromSource(source, fileExtension: fileExtension) {
case .success(let fileURL):
guard let index = timelineItems.firstIndex(where: { $0.id == timelineItem.id }),
var item = timelineItems[index] as? VideoRoomTimelineItem else {
return
}
item.cachedVideoURL = fileURL
timelineItems[index] = item
case .failure:
break
}
}
private func loadUserAvatarForTimelineItem(_ timelineItem: EventBasedTimelineItemProtocol) async {
if timelineItem.shouldShowSenderDetails == false {
@@ -24,6 +24,11 @@ enum RoomTimelineControllerCallback {
case finishedBackPaginating
}
enum RoomTimelineControllerAction {
case displayVideo(videoURL: URL)
case none
}
enum RoomTimelineControllerError: Error {
case generic
}
@@ -38,6 +43,8 @@ protocol RoomTimelineControllerProtocol {
func processItemAppearance(_ itemId: String) async
func processItemDisappearance(_ itemId: String) async
func processItemTap(_ itemId: String) async -> RoomTimelineControllerAction
func paginateBackwards(_ count: UInt) async -> Result<Void, RoomTimelineControllerError>
@@ -33,6 +33,7 @@ struct VideoRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Equa
let source: MediaSource?
let thumbnailSource: MediaSource?
var image: UIImage?
var cachedVideoURL: URL?
var width: CGFloat?
var height: CGFloat?