So I have created simple code like below.
I have 2 problems:
- first is when you try do drag first video it is not being removed, instead last one (from the background) is.
- I have a
Task
in.onAppear
which download all videos at the same time. How to make is sync, so next video will be downloaded only when first one is completed?
import SwiftUI
import AVKit
struct Video: Identifiable {
var id: Int
var cacheName: String
var reply: Bool
}
struct PlayerView: View {
@Binding var data: [Video]
@State private var offset = CGSize.zero
@State private var video: Video?
var body: some View {
ZStack {
ForEach(self.data.indices.reversed(), id: \.self) { index in
let i = self.data[index]
if index < 3 {
if let player = CacheManager.shared.get(name: i.cacheName) {
Player(player: player)
.frame(width: 300, height: 300)
.overlay {
Text("\(index) \(i.id)").foregroundColor(.blue)
}
.scaleEffect(1 - 0.05 * CGFloat(index), anchor: .top)
.offset(
x: 0,
y: ({
if (index > 0) {
return CGFloat(index) * -5.5
}
//not a first card, or swiping up
if offset.height < 0 {
return 0
}
// swiping down
return offset.height * 1.1
}())
)
.onTapGesture(perform: {
print("tapped", index)
if index == 0 {
self.video = i
}
})
.gesture(
DragGesture()
.onChanged { gesture in
offset = gesture.translation
}
.onEnded { value in
if abs(offset.height) > 100 && index == 0 {
withAnimation {
print("iiii", i)
// <<<<<< this function removes invalid element from the array, WHY?
self.data.removeAll { $0.cacheName == i.cacheName }
offset = .zero
}
} else {
offset = .zero
}
})
.onAppear {
if index == 0 {
player.play()
} else {
player.pause()
}
}
.onDisappear {
player.pause()
}
} else {
Text("not found")
}
} else {
EmptyView()
}
}
}
.fullScreenCover(item: $video, content: { video in
if let player = CacheManager.shared.get(name: video.cacheName) {
ZStack {
Player(player: player)
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
.ignoresSafeArea()
VStack {
Button(action: {
player.seek(to: .zero, completionHandler: {_ in
player.play()
})
}, label: {
Text("PLAY").foregroundColor(.black)
})
Button(action: {
self.video = nil
}, label: {
Text("CLOSE").foregroundColor(.black)
})
}
}
} else {
Text("none")
}
})
}
}
struct Player: UIViewControllerRepresentable {
var player: AVPlayer
func makeUIViewController(context: Context) -> AVPlayerViewController {
let view = AVPlayerViewController()
view.player = player
view.showsPlaybackControls = false
view.videoGravity = .resizeAspectFill
return view
}
func updateUIViewController(_ uiViewController: AVPlayerViewController, context: Context) {
}
}
let urls = [
"https://dnr07ippqik25.cloudfront.net/01GXX7WFGJVZ11RCC6Q7XMYH02.mp4",
"https://dnr07ippqik25.cloudfront.net/01GXX7D1MJRNG10BT16EV3RF4Z.mp4",
"https://dnr07ippqik25.cloudfront.net/01GXX7FXXHPP3WGX7ZY5SCXCC5.mp4",
"https://dnr07ippqik25.cloudfront.net/01GXX7F5R5MTEF6AWTRA806SAZ.mp4"
]
struct ContentView: View {
@State var data: [Video] = []
private let cm = CacheManager.shared
var body: some View {
PlayerView(data: $data)
.onAppear {
urls.indices.forEach { index in
Task {
do {
let url = URL(string: urls[index])!
let folder = try! FileManager.default
.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
let destination = folder.appendingPathComponent(url.lastPathComponent)
if cm.get(name: urls[index]) != nil {
data.append(.init(id: index, cacheName: urls[index], reply: false))
} else {
try await downloadFiles(url: url, destination: destination)
cm.add(player: AVPlayer(url: destination), name: urls[index])
data.append(.init(id: index, cacheName: urls[index], reply: false))
}
} catch {
print(error)
}
}
}
}
}
}
func downloadFiles(url: URL, destination: URL) async throws {
if !FileManager.default.fileExists(atPath: destination.path) {
let (source, _) = try await URLSession.shared.download(from: url)
try FileManager.default.moveItem(at: source, to: destination)
} else {
print("file already downloaded")
}
}
class CacheManager {
static let shared = CacheManager()
private init() {}
var avPlayerCache: NSCache<NSString, AVPlayer> = {
let cache = NSCache<NSString, AVPlayer>()
cache.countLimit = 50
cache.totalCostLimit = 1024 * 1024 * 100 // 100MB
return cache
}()
func add(player: AVPlayer, name: String) {
avPlayerCache.setObject(
player,
forKey: name as NSString
)
}
func remove(name: String) {
avPlayerCache.removeObject(forKey: name as NSString)
}
func get(name: String) -> AVPlayer? {
return avPlayerCache.object(forKey: name as NSString)
}
}