Merge pull request #5 from talksik/preloading/caching-messages-for-faster-listen
Preloading/caching messages for faster listen
This commit is contained in:
@@ -33,7 +33,7 @@ final class AuthSessionStore: ObservableObject, SessionStore {
|
||||
@Published var sessionState: SessionState = SessionState.notCheckedYet
|
||||
|
||||
// TODO: figure out which ones to publish
|
||||
var messagesArr: [Message] = []
|
||||
@Published var messagesArr: [Message] = []
|
||||
var userFriendsDict: [String: UserFriends] = [:] // all active and inactive relationships
|
||||
|
||||
// transformed data for the views
|
||||
@@ -337,7 +337,7 @@ extension AuthSessionStore {
|
||||
|
||||
// if this user is not already a friend, active or inactive/rejected, then get their user data and add to inbox
|
||||
if self.userFriendsDict.keys.contains(userFriend!.userId) {
|
||||
print("already have this user in my circle or I rejected them \(userFriend?.userId)")
|
||||
// print("already have this user in my circle or I rejected them \(userFriend?.userId)")
|
||||
continue
|
||||
}
|
||||
else {
|
||||
@@ -380,12 +380,12 @@ extension AuthSessionStore {
|
||||
//optimize this? but also saving on memory and same db reads
|
||||
self.relevantMessagesByUserDict.removeAll()
|
||||
|
||||
self.messagesArr = documents.compactMap { (queryDocumentSnapshot) -> Message? in
|
||||
do {
|
||||
let currMessage = try queryDocumentSnapshot.data(as: Message.self)
|
||||
print("new message received! \(currMessage!.sentTimestamp)")
|
||||
|
||||
DispatchQueue.main.async {
|
||||
DispatchQueue.main.async {
|
||||
self.messagesArr = documents.compactMap { (queryDocumentSnapshot) -> Message? in
|
||||
do {
|
||||
let currMessage = try queryDocumentSnapshot.data(as: Message.self)
|
||||
// print("new message received! \(currMessage!.sentTimestamp)")
|
||||
|
||||
if currMessage != nil { // not really possible but just check
|
||||
// if the user doesn't exist for the dictionary, then add it
|
||||
// this means it's most likely someone new (never had user_friend relationship before) messaging for the user's inbox
|
||||
@@ -407,15 +407,14 @@ extension AuthSessionStore {
|
||||
}
|
||||
}
|
||||
|
||||
self.objectWillChange.send()
|
||||
return currMessage
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
|
||||
return currMessage
|
||||
} catch {
|
||||
print(error)
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
self.listenersActive = true
|
||||
|
||||
@@ -368,10 +368,15 @@ struct CircleGridView: View {
|
||||
}
|
||||
|
||||
self.animateLiveConvos = true
|
||||
}// univseral alert TODO: move to inner circle view and use this pattern for all view models
|
||||
// .alert(item: self.$convoVM.error) {error in
|
||||
// error.alert
|
||||
// }
|
||||
}
|
||||
.onReceive(self.authSessionStore.$messagesArr) {_ in
|
||||
// TODO: don't know if this fires if I am active and I receive a message but that's not something I am allowing right now with the continuous convo feature
|
||||
|
||||
print("new messages found!!!! on receive going to cache all messages to load them faster for you!")
|
||||
|
||||
// TODO: might be including messages of inbox users? overloading memory?
|
||||
self.innerCircleVM.cacheIncomingMessages(friendMessagesDict: self.authSessionStore.relevantMessagesByUserDict)
|
||||
}
|
||||
} // scrollview reader
|
||||
}
|
||||
|
||||
|
||||
@@ -71,12 +71,14 @@ class InnerCircleViewModel: NSObject, ObservableObject {
|
||||
}
|
||||
|
||||
var audioRecorder : AVAudioRecorder!
|
||||
|
||||
var queuePlayer = AVQueuePlayer()
|
||||
private var cachedPlayerItemsDict: [String: URL] = [:] // firebase audio url to local url
|
||||
|
||||
@Published var isRecording : Bool = false
|
||||
|
||||
@Published var messagesListeningProgress: Float = 1.0
|
||||
static let multiplier: Float64 = 0.05
|
||||
static let multiplier: Float64 = 0.01
|
||||
|
||||
let audioSession = AVAudioSession.sharedInstance()
|
||||
|
||||
@@ -255,15 +257,23 @@ extension InnerCircleViewModel {
|
||||
// clearing the player to make room for this friend's convo or to deselect this user
|
||||
self.stopPlayingAnyAudio()
|
||||
|
||||
// reset progress
|
||||
self.messagesListeningProgress = Float(0)
|
||||
|
||||
var AVPlayerItems: [AVPlayerItem] = []
|
||||
for url in audioUrls {
|
||||
let asset = AVAsset(url: url)
|
||||
let playerItem = AVPlayerItem(asset: asset)
|
||||
|
||||
// notification for when each playeritem is done playing
|
||||
// NotificationCenter.default.addObserver(self, selector: #selector(self.playerDidFinishPlaying(sender:)), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: playerItem)
|
||||
|
||||
AVPlayerItems.append(playerItem)
|
||||
// if we have a player in the cache, then play it from there
|
||||
if self.cachedPlayerItemsDict.keys.contains(url.absoluteString), let localUrl = self.cachedPlayerItemsDict[url.absoluteString] {
|
||||
// keep the cachedplayer item in tact as the player messes with those items
|
||||
let asset = AVAsset(url: localUrl)
|
||||
let playerItem = AVPlayerItem(asset: asset)
|
||||
AVPlayerItems.append(playerItem)
|
||||
print("playing a message from cache")
|
||||
} else {
|
||||
let asset = AVAsset(url: url)
|
||||
let playerItem = AVPlayerItem(asset: asset)
|
||||
AVPlayerItems.append(playerItem)
|
||||
}
|
||||
}
|
||||
|
||||
if AVPlayerItems.count <= 0 {
|
||||
@@ -284,9 +294,6 @@ extension InnerCircleViewModel {
|
||||
|
||||
print("player queued up items!!!")
|
||||
|
||||
// reset progress
|
||||
self.messagesListeningProgress = Float(Self.multiplier * 2)
|
||||
|
||||
DispatchQueue.global(qos: .background).async {
|
||||
self.addBoundaryTimeObserver(playerItems: AVPlayerItems)
|
||||
}
|
||||
@@ -298,6 +305,11 @@ extension InnerCircleViewModel {
|
||||
totalDuration = CMTimeAdd(totalDuration, item.asset.duration)
|
||||
}
|
||||
|
||||
// TODO: not hitting the last one/100%
|
||||
// it either plays on time or there is a little lag on multiple items...
|
||||
// offset this by adding 1 when it reaches the totalDuration as it's longer
|
||||
totalDuration = CMTimeSubtract(totalDuration, CMTimeMakeWithSeconds(1, preferredTimescale: 1))
|
||||
|
||||
var times = [NSValue]()
|
||||
// Set initial time to zero
|
||||
var currentTime = CMTime.zero
|
||||
@@ -309,11 +321,10 @@ extension InnerCircleViewModel {
|
||||
currentTime = currentTime + interval
|
||||
times.append(NSValue(time: currentTime))
|
||||
}
|
||||
// this last one to make sure we get a full loop
|
||||
times.append(NSValue(time: totalDuration))
|
||||
|
||||
|
||||
|
||||
// Add time observer. Observe boundary time changes on the main queue.
|
||||
// TODO: not hitting the last one/100%
|
||||
self.queuePlayer.addBoundaryTimeObserver(forTimes: times, queue: .main) { [weak self] in
|
||||
// Update UI
|
||||
self?.messagesListeningProgress += Float(Self.multiplier)
|
||||
@@ -357,14 +368,53 @@ extension InnerCircleViewModel {
|
||||
// self.timeObserverToken = nil
|
||||
// }
|
||||
// }
|
||||
|
||||
func cacheIncomingMessages(friendMessagesDict: [String: [Message]]) {
|
||||
// TODO: not firing for some reason onreceive of new data or not including new data
|
||||
guard let userId = AuthSessionStore.getCurrentUserId() else {return}
|
||||
|
||||
DispatchQueue.global(qos: .background).async {
|
||||
// get all relevant messages that the user may listen to
|
||||
// don't want to cache all messages
|
||||
for friendId in friendMessagesDict.keys {
|
||||
let messagesRelatedToFriend = friendMessagesDict[friendId] ?? []
|
||||
|
||||
if messagesRelatedToFriend.count == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
for message in messagesRelatedToFriend {
|
||||
// if it's starting to get to my messages then don't play
|
||||
if message.senderId == userId {
|
||||
break
|
||||
}
|
||||
|
||||
// only add to queue if we can convert the database url to a valid url here
|
||||
if let audioUrl = URL(string: message.audioDataUrl) { // check if it's a valid url
|
||||
if !self.cachedPlayerItemsDict.keys.contains(message.audioDataUrl) {
|
||||
// save to play from mem later
|
||||
|
||||
let task = URLSession.shared.dataTask(with: audioUrl) {[weak self] (data, response, error) in
|
||||
guard let data = data else { return }
|
||||
print(data)
|
||||
|
||||
if let cacheFilePath = self?.getTemporaryDirectory().appendingPathComponent("\(UUID().uuidString).m4a") {
|
||||
try? data.write(to: cacheFilePath)
|
||||
|
||||
// save in local cache
|
||||
self?.cachedPlayerItemsDict[audioUrl.absoluteString] = cacheFilePath
|
||||
}
|
||||
}
|
||||
|
||||
task.resume()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: need way of deleting cache when we close app or something...can rile up the cost
|
||||
}
|
||||
}
|
||||
|
||||
extension InnerCircleViewModel {
|
||||
// @objc func playerDidFinishPlaying(sender: Notification) {
|
||||
// // Your code here
|
||||
// print("finished playing an item")
|
||||
//
|
||||
// self.numberofMessagesToPlay -= 1
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user