Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use fileCDN with video attachments #624

Merged
merged 2 commits into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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: 31 additions & 17 deletions Sources/StreamChatSwiftUI/Utils/Common/VideoPreviewLoader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<URL, UIImage>

public init(countLimit: Int = 50) {
Expand All @@ -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<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`.")
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<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
Loading