mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 06:55:59 +00:00
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:
@@ -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?
|
||||
|
||||
Reference in New Issue
Block a user