Skip to content

Commit

Permalink
Use fileCDN with video attachments
Browse files Browse the repository at this point in the history
  • Loading branch information
bpollman committed Oct 18, 2024
1 parent 6c5cfeb commit 95ea66f
Show file tree
Hide file tree
Showing 9 changed files with 111 additions and 40 deletions.
40 changes: 30 additions & 10 deletions Sources/StreamChatSwiftUI/ChatChannel/Gallery/GalleryView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -26,7 +32,6 @@ public struct VideoPlayerView: View {
) {
self.attachment = attachment
self.author = author
avPlayer = AVPlayer(url: attachment.payload.videoURL)
_isShown = isShown
}

Expand All @@ -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])
Expand All @@ -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)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,7 @@ struct MediaAttachment {
} else if type == .video {
utils.videoPreviewLoader.loadPreviewForVideo(
at: url,
fileCDN: utils.fileCDN,
completion: completion
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ struct VideoAttachmentContentView: View {
)
}
.onAppear {
videoPreviewLoader.loadPreviewForVideo(at: attachment.videoURL) { result in
videoPreviewLoader.loadPreviewForVideo(at: attachment.videoURL, fileCDN: utils.fileCDN) { result in
switch result {
case let .success(image):
self.previewImage = image
Expand Down
2 changes: 1 addition & 1 deletion Sources/StreamChatSwiftUI/Utils/Common/FileCDN.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
48 changes: 30 additions & 18 deletions Sources/StreamChatSwiftUI/Utils/Common/VideoPreviewLoader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public protocol VideoPreviewLoader: AnyObject {
/// - Parameters:
/// - url: A video URL.
/// - completion: A completion that is called when a preview is loaded. Must be invoked on main queue.
func loadPreviewForVideo(at url: URL, completion: @escaping (Result<UIImage, Error>) -> Void)
func loadPreviewForVideo(at url: URL, fileCDN: FileCDN, completion: @escaping (Result<UIImage, Error>) -> Void)
}

/// The `VideoPreviewLoader` implemenation used by default.
Expand All @@ -34,31 +34,43 @@ public final class DefaultVideoPreviewLoader: VideoPreviewLoader {
NotificationCenter.default.removeObserver(self)
}

public func loadPreviewForVideo(at url: URL, completion: @escaping (Result<UIImage, Error>) -> Void) {
public func loadPreviewForVideo(at url: URL, fileCDN: FileCDN, completion: @escaping (Result<UIImage, Error>) -> Void) {
if let cached = cache[url] {
return call(completion, with: .success(cached))
}

let asset = AVURLAsset(url: url)
let imageGenerator = AVAssetImageGenerator(asset: asset)
let frameTime = CMTime(seconds: 0.1, preferredTimescale: 600)
fileCDN.adjustedURL(for: url) { result in

imageGenerator.appliesPreferredTrackTransform = true
imageGenerator.generateCGImagesAsynchronously(forTimes: [.init(time: frameTime)]) { [weak self] _, image, _, _, error in
guard let self = self else { return }

let result: Result<UIImage, Error>
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`.")
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<UIImage, Error>
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)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class VideoPreviewLoader_Mock: VideoPreviewLoader {

var loadPreviewVideoCalled = false

func loadPreviewForVideo(at url: URL, completion: @escaping (Result<UIImage, Error>) -> Void) {
func loadPreviewForVideo(at url: URL, fileCDN: FileCDN, completion: @escaping (Result<UIImage, Error>) -> Void) {
loadPreviewVideoCalled = true
}
}
Expand All @@ -30,3 +30,14 @@ class ImageLoaderUtils_Mock: ImageLoading {
loadImageCalled = true
}
}

class FileCDNUtils_Mock: FileCDN {

var adjustedURLCalled = false

func adjustedURL(for url: URL, completion: @escaping ((Result<URL, any Error>) -> Void)) {
adjustedURLCalled = true
}


}
16 changes: 14 additions & 2 deletions StreamChatSwiftUITests/Tests/Utils/StreamChat_Utils_Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ class StreamChat_Utils_Tests: StreamChatTestCase {
override func setUp() {
let utils = Utils(
videoPreviewLoader: VideoPreviewLoader_Mock(),
imageLoader: ImageLoaderUtils_Mock()
imageLoader: ImageLoaderUtils_Mock(),
fileCDN: FileCDNUtils_Mock()
)
streamChat = StreamChat(chatClient: chatClient, utils: utils)
}
Expand All @@ -26,7 +27,7 @@ class StreamChat_Utils_Tests: StreamChatTestCase {
let videoPreviewLoader = utils.videoPreviewLoader as! VideoPreviewLoader_Mock

// When
videoPreviewLoader.loadPreviewForVideo(at: testURL, completion: { _ in })
videoPreviewLoader.loadPreviewForVideo(at: testURL, fileCDN: utils.fileCDN, completion: { _ in })

// Then
XCTAssert(videoPreviewLoader.loadPreviewVideoCalled == true)
Expand All @@ -48,4 +49,15 @@ class StreamChat_Utils_Tests: StreamChatTestCase {
// Then
XCTAssert(imageLoader.loadImageCalled == true)
}

func test_streamChatUtils_injectFileCDN() {
// Given
let fileCDN = utils.fileCDN as! FileCDNUtils_Mock

// When
fileCDN.adjustedURL(for: testURL, completion: { _ in })

// Then
XCTAssert(fileCDN.adjustedURLCalled == true)
}
}

0 comments on commit 95ea66f

Please sign in to comment.