From 00fd3b66f581df7f98e0b3ffbf3c4677c76ab7c4 Mon Sep 17 00:00:00 2001 From: Ben Pollman Date: Mon, 21 Oct 2024 10:05:19 +1100 Subject: [PATCH] Use fileCDN with video attachments --- .../ChatChannel/Gallery/GalleryView.swift | 40 ++++++++++++---- .../ChatChannel/Gallery/VideoPlayerView.swift | 27 ++++++++--- .../MessageList/FileAttachmentPreview.swift | 2 +- .../Utils/Common/FileCDN.swift | 2 +- .../Utils/Common/VideoPreviewLoader.swift | 48 ++++++++++++------- 5 files changed, 84 insertions(+), 35 deletions(-) diff --git a/Sources/StreamChatSwiftUI/ChatChannel/Gallery/GalleryView.swift b/Sources/StreamChatSwiftUI/ChatChannel/Gallery/GalleryView.swift index 4c0ade86..633fc626 100644 --- a/Sources/StreamChatSwiftUI/ChatChannel/Gallery/GalleryView.swift +++ b/Sources/StreamChatSwiftUI/ChatChannel/Gallery/GalleryView.swift @@ -153,20 +153,40 @@ public struct GalleryView: View { } struct StreamVideoPlayer: View { - - @State var player: AVPlayer - + + @Injected(\.utils) private var utils + + private var fileCDN: FileCDN { + utils.fileCDN + } + + let url: URL + + @State var avPlayer: AVPlayer? + @State var error: Error? + init(url: URL) { - let player = AVPlayer(url: url) - _player = State(wrappedValue: player) + self.url = url } var body: some View { - VideoPlayer(player: player) - .clipped() - .onAppear { - try? AVAudioSession.sharedInstance().setCategory(.playback, options: []) - player.play() + VStack { + if let avPlayer { + VideoPlayer(player: avPlayer) + .clipped() + } + } + .onAppear { + fileCDN.adjustedURL(for: url) { result in + switch result { + case let .success(url): + self.avPlayer = AVPlayer(url: url) + try? AVAudioSession.sharedInstance().setCategory(.playback, options: []) + self.avPlayer?.play() + case let .failure(error): + self.error = error + } } + } } } diff --git a/Sources/StreamChatSwiftUI/ChatChannel/Gallery/VideoPlayerView.swift b/Sources/StreamChatSwiftUI/ChatChannel/Gallery/VideoPlayerView.swift index f1a7bd13..665de463 100644 --- a/Sources/StreamChatSwiftUI/ChatChannel/Gallery/VideoPlayerView.swift +++ b/Sources/StreamChatSwiftUI/ChatChannel/Gallery/VideoPlayerView.swift @@ -12,12 +12,18 @@ public struct VideoPlayerView: View { @Injected(\.fonts) private var fonts @Injected(\.colors) private var colors + @Injected(\.utils) private var utils + + private var fileCDN: FileCDN { + utils.fileCDN + } let attachment: ChatMessageVideoAttachment let author: ChatUser @Binding var isShown: Bool - private let avPlayer: AVPlayer + @State private var avPlayer: AVPlayer? + @State private var error: Error? public init( attachment: ChatMessageVideoAttachment, @@ -26,7 +32,6 @@ public struct VideoPlayerView: View { ) { self.attachment = attachment self.author = author - avPlayer = AVPlayer(url: attachment.payload.videoURL) _isShown = isShown } @@ -37,7 +42,9 @@ public struct VideoPlayerView: View { subtitle: author.onlineText, isShown: $isShown ) - VideoPlayer(player: avPlayer) + if let avPlayer { + VideoPlayer(player: avPlayer) + } Spacer() HStack { ShareButtonView(content: [attachment.payload.videoURL]) @@ -48,11 +55,19 @@ public struct VideoPlayerView: View { .foregroundColor(Color(colors.text)) } .onAppear { - try? AVAudioSession.sharedInstance().setCategory(.playback, options: []) - avPlayer.play() + fileCDN.adjustedURL(for: attachment.payload.videoURL) { result in + switch result { + case let .success(url): + self.avPlayer = AVPlayer(url: url) + try? AVAudioSession.sharedInstance().setCategory(.playback, options: []) + self.avPlayer?.play() + case let .failure(error): + self.error = error + } + } } .onDisappear { - avPlayer.replaceCurrentItem(with: nil) + avPlayer?.replaceCurrentItem(with: nil) } } } diff --git a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/FileAttachmentPreview.swift b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/FileAttachmentPreview.swift index 8cef1050..0ccb4b58 100644 --- a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/FileAttachmentPreview.swift +++ b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/FileAttachmentPreview.swift @@ -18,7 +18,7 @@ public struct FileAttachmentPreview: View { var url: URL - @State var adjustedUrl: URL? + @State private var adjustedUrl: URL? @State private var isLoading = false @State private var title: String? @State private var error: Error? diff --git a/Sources/StreamChatSwiftUI/Utils/Common/FileCDN.swift b/Sources/StreamChatSwiftUI/Utils/Common/FileCDN.swift index 477fc8e2..d502103b 100644 --- a/Sources/StreamChatSwiftUI/Utils/Common/FileCDN.swift +++ b/Sources/StreamChatSwiftUI/Utils/Common/FileCDN.swift @@ -5,7 +5,7 @@ import Foundation import StreamChat -/// A protocol the video preview uploader implementation must conform to. +/// FileCDN provides a set of functions to improve handling of files & videos from CDN. public protocol FileCDN: AnyObject { /// Prepare and return an adjusted or signed `URL` for the given file `URL` /// This function can be used to intercept an unsigned URL and return a valid signed URL diff --git a/Sources/StreamChatSwiftUI/Utils/Common/VideoPreviewLoader.swift b/Sources/StreamChatSwiftUI/Utils/Common/VideoPreviewLoader.swift index 1a27147d..366496c5 100644 --- a/Sources/StreamChatSwiftUI/Utils/Common/VideoPreviewLoader.swift +++ b/Sources/StreamChatSwiftUI/Utils/Common/VideoPreviewLoader.swift @@ -17,6 +17,8 @@ public protocol VideoPreviewLoader: AnyObject { /// The `VideoPreviewLoader` implemenation used by default. public final class DefaultVideoPreviewLoader: VideoPreviewLoader { + @Injected(\.utils) var utils + private let cache: Cache public init(countLimit: Int = 50) { @@ -39,26 +41,38 @@ public final class DefaultVideoPreviewLoader: VideoPreviewLoader { return call(completion, with: .success(cached)) } - let asset = AVURLAsset(url: url) - let imageGenerator = AVAssetImageGenerator(asset: asset) - let frameTime = CMTime(seconds: 0.1, preferredTimescale: 600) - - imageGenerator.appliesPreferredTrackTransform = true - imageGenerator.generateCGImagesAsynchronously(forTimes: [.init(time: frameTime)]) { [weak self] _, image, _, _, error in - guard let self = self else { return } - - let result: Result - if let thumbnail = image { - result = .success(.init(cgImage: thumbnail)) - } else if let error = error { - result = .failure(error) - } else { - log.error("Both error and image are `nil`.") + utils.fileCDN.adjustedURL(for: url) { result in + + let adjustedUrl: URL + switch result { + case let .success(url): + adjustedUrl = url + case let .failure(error): + self.call(completion, with: .failure(error)) return } - self.cache[url] = try? result.get() - self.call(completion, with: result) + let asset = AVURLAsset(url: adjustedUrl) + let imageGenerator = AVAssetImageGenerator(asset: asset) + let frameTime = CMTime(seconds: 0.1, preferredTimescale: 600) + + imageGenerator.appliesPreferredTrackTransform = true + imageGenerator.generateCGImagesAsynchronously(forTimes: [.init(time: frameTime)]) { [weak self] _, image, _, _, error in + guard let self = self else { return } + + let result: Result + if let thumbnail = image { + result = .success(.init(cgImage: thumbnail)) + } else if let error = error { + result = .failure(error) + } else { + log.error("Both error and image are `nil`.") + return + } + + self.cache[url] = try? result.get() + self.call(completion, with: result) + } } }