I want to get the queue from the Media Player. I believe that I can't read the queue though (is this correct?) and that I can only get the queue when I set it, like when I select a certain playlist to play. However I'm struggling to get the shuffled queue.
let musicPlayerController = MPMusicPlayerController.systemMusicPlayer
let myMediaQuery = MPMediaQuery.songs()
let predicateFilter = MPMediaPropertyPredicate(value: chosenPlaylist, forProperty: MPMediaPlaylistPropertyName)
myMediaQuery.filterPredicates = NSSet(object: predicateFilter) as? Set<MPMediaPredicate>
musicPlayerController.setQueue(with: myMediaQuery)
musicPlayerController.repeatMode = .all
musicPlayerController.shuffleMode = .songs
musicPlayerController.play()
for track in myMediaQuery.items! {
print(track.value(forProperty: MPMediaItemPropertyTitle)!)
} // here I don't get the shuffled order that is going to play, just get the original order of the playlist
I need the shuffled as I want to be able to display what's going to play next.
I've had trouble with .shuffleMode in the past. Call shuffled() on the collection instead:
// shuffle
func shufflePlaylist() {
let query = MPMediaQuery.songs()
let predicate = MPMediaPropertyPredicate(value: "Art Conspiracy",
forProperty: MPMediaPlaylistPropertyName,
comparisonType: .equalTo)
query.addFilterPredicate(predicate)
guard let items = query.items else { return }
let collection = MPMediaItemCollection(items: items.shuffled())
player.setQueue(with: collection)
player.prepareToPlay()
player.play()
}
Related
I fetch the timestamps from every frame and store them in an array using the showTimestamps function. I now want to "draw" each timestamp on each frame of the video, and export it.
func showTimestamps(videoFile : URL) -> [String] {
let asset = AVAsset(url:videoFile)
let track = asset.tracks(withMediaType: AVMediaType.video)[0]
let output = AVAssetReaderTrackOutput(track: track, outputSettings: nil)
guard let reader = try? AVAssetReader(asset: asset) else {exit(1)}
output.alwaysCopiesSampleData = false
reader.add(output)
reader.startReading()
var times : [String] = []
while(reader.status == .reading){
if let sampleBuffer = output.copyNextSampleBuffer() , CMSampleBufferIsValid(sampleBuffer) && CMSampleBufferGetTotalSampleSize(sampleBuffer) != 0 {
let frameTime = CMSampleBufferGetOutputPresentationTimeStamp(sampleBuffer)
if (frameTime.isValid){
times.append(String(format:"%.3f", frameTime.seconds))
}
}
}
return times.sorted()
}
However, I cannot figure out how to export a new video with each frame containing it's respectful timestamp? i.e How can I implement this code:
func generateNewVideoWithTimestamps(videoFile: URL, timestampsForFrames: [String]) {
// TODO
}
I want to keep the framerate, video quality, etc., the same. The only thing that should differ is to add some text on the bottom.
To get this far, I used these guides and failed: Frames, Static Text, Watermark
What problem am I observing on a few occasion: only 1 of the array of users that needs too be displayed show up on screen
What do I suspect: In the firebase snapshot that determines which users get displayed, I suspect async processes compete and only 1 user meets the criteria because the criteria for the others wasn't run in time before they were evaluated.
The firebase async issue: I used to think of async as multiple function running concurrently but the more I read it seems the code inside a viewDidLoad firebase snapshot also runs asynchronously, so that leaves me concerned that stuff will be evaluated before it is run.
Here is my code inside viewDidLoad that determines which users get displayed
let uid = Auth.auth().currentUser!.uid
let thisUserRef = Database.database().reference().child("people").child(uid)
let myPeopleRef = thisUserRef.child("peopleWhoLike")
var handle: UInt = 0
handle = myPeopleRef.queryLimited(toLast: 30).observe(DataEventType.value, with: { [self] snapshot in
let uniqueArray1 = snapshot.children.allObjects as! [DataSnapshot]
let peopleArray = Array(Set(uniqueArray1))
for person4 in peopleArray where uid == Auth.auth().currentUser?.uid {
self.dg.enter()
let personUid = person4.value as! String
let planDict18 = person4.key
let time22 = self.decode(autoId: planDict18)
let time11 = Int(time22)
let date = Date(timeIntervalSince1970: TimeInterval(time11)/1000.0)
print(date,"pdate")
defer{self.dg.leave()}
self.dg.notify(queue: .main) {
if Calendar.current.isDateInToday(date){
self.printPersonInfo(uid: personUid) ///this gets all the data for each user and reloads data
}
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + 4) {
let ref = Database.database().reference().child("people")
ref.removeObserver(withHandle: handle)
ref.removeObserver(withHandle: self.handleA)
}
})
Summary:
Is there a way to control the sort order for an album created by an app on a users device? I would like an album I created to sort by date, but it sorts by download order.
Details:
I am using the function save() below to save a UIImage to an album on a user's iPhone. It works great but the image loses its original create time when the copy is saved. To address this I read the create time when I upload an image to the server. That way I just assign it to the image when the asset is created with assetChangeRequest.creationDate = storedImageDate
PHPhotoLibrary.shared().performChanges({
let assetChangeRequest = PHAssetChangeRequest.creationRequestForAsset(from: image)
let assetPlaceholder = assetChangeRequest.placeholderForCreatedAsset
let albumChangeRequest = PHAssetCollectionChangeRequest(for: assetCollection)
assetChangeRequest.creationDate = storedImageDate
let assetEnumeration:NSArray = [assetPlaceholder!]
albumChangeRequest!.addAssets(assetEnumeration)
}, completionHandler: { success, error in
if error != nil {
print("\(error?.localizedDescription ?? "")")
}
})
The above works great and when I look at the newly created image it is in the created album, the images have the correct date on the image, and the images viewed in camera roll are sorted by the image create date as expected. The issue arises when go to the album my app created for images. There the images have the image date, but they are sorted by the order they downloaded not the image. I also noticed that when I long press images in the album it lets me move the order around.
Did I do something wrong creating my album? Can I set it to sort by image date? Perhaps there is a way to change the sort order for my album? Is there a way to fetch loop through the album to order it by date?
This is the full function where an image is saved into a created album which does not sort by the image creation date.
func save(image:UIImage, albumName:String) {
var albumFound = false
var assetCollection: PHAssetCollection!
var assetCollectionPlaceholder: PHObjectPlaceholder!
// https://stackoverflow.com/questions/27008641/save-images-with-phimagemanager-to-custom-album
// Check if the Album exists and get collection
let fetchOptions = PHFetchOptions()
fetchOptions.predicate = NSPredicate(format: "title = %#", albumName)
let collection : PHFetchResult = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: fetchOptions)
if let _: AnyObject = collection.firstObject {
albumFound = true
assetCollection = collection.firstObject
} else {
//If not found - Then create a new album
PHPhotoLibrary.shared().performChanges({
let createAlbumRequest : PHAssetCollectionChangeRequest = PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: albumName)
assetCollectionPlaceholder = createAlbumRequest.placeholderForCreatedAssetCollection
}, completionHandler: { success, error in
albumFound = success
if (success) {
let collectionFetchResult = PHAssetCollection.fetchAssetCollections(withLocalIdentifiers: [assetCollectionPlaceholder.localIdentifier], options: nil)
print(collectionFetchResult)
assetCollection = collectionFetchResult.firstObject
}
})
}
if albumFound {
PHPhotoLibrary.shared().performChanges({
let assetChangeRequest = PHAssetChangeRequest.creationRequestForAsset(from: image)
let assetPlaceholder = assetChangeRequest.placeholderForCreatedAsset
let albumChangeRequest = PHAssetCollectionChangeRequest(for: assetCollection)
assetChangeRequest.creationDate = storedImageDate
let assetEnumeration:NSArray = [assetPlaceholder!]
albumChangeRequest!.addAssets(assetEnumeration)
}, completionHandler: { success, error in
if error != nil {
print("\(error?.localizedDescription ?? "")")
}
})
}
}
Instead of using addAssets like you did in performChanges 'albumChangeRequest!.addAssets(assetEnumeration)'.
You should use insertAssets at indexes which controls the position the images are added to the album, much like NSArray insertObject atIndex method.
Swift
func insertAssets(_ assets: NSFastEnumeration,
at indexes: IndexSet)
Objective-C
- (void)insertAssets:(id<NSFastEnumeration>)assets
atIndexes:(NSIndexSet *)indexes;
https://developer.apple.com/documentation/photokit/phassetcollectionchangerequest/1619447-insertassets
Currently, I am initially loading the user's messages through :
func fetchMessages() {
if started == true {
let messageRef = Database.database().reference().child("messages").queryOrdered(byChild: "convoID").queryEqual(toValue: convoID).queryLimited(toLast: 10)
messageRef.observe(.childAdded) { (snapshot) in
if let value = snapshot.value as? NSDictionary {
let newMessage = message()
newMessage.messageText = value["content"] as? String
newMessage.sender = value["sender"] as? String
newMessage.messageID = snapshot.key
self.messageList.append(newMessage)
self.queryingStatus = true
self.messagesTableView.reloadData()
self.scrollToBottom()
}
}
}
}
Now, to minimize the data download, I decided to break the messages into chunks as such so that the user will download ten subsequent messages each time they pull up on the table view:
#objc func handleRefresh(_ refreshControl: UIRefreshControl) {
let lastIDDictionary = messageList[0]
let lastIDQueried = lastIDDictionary.messageID
let messageRefAddition = Database.database().reference().child("messages").queryOrdered(byChild: "convoID").queryLimited(toLast: 10).queryEnding(atValue: convoID, childKey: lastIDQueried!)
messageRefAddition.observeSingleEvent(of: .value) { (snapshot) in
for child in snapshot.children.allObjects as! [DataSnapshot] {
if let messageValue = child.value as? NSDictionary {
let newMessage = message()
newMessage.messageText = messageValue["content"] as? String
newMessage.sender = messageValue["sender"] as? String
newMessage.messageID = child.key
self.messageList.insert(newMessage, at: 0)
self.messagesTableView.reloadData()
}
}
}
refreshControl.endRefreshing()
}
The problem is, when I pull up on the table view, the first time it returns some new messages (I am not sure whether the order is even correct). However, when I pull on the table view again to refresh, it adds those same ten messages again. I printed the lastIDQueried in the refresh method, and after the initial load the ID remains the same even though I am accessing the first item in the array of dictionaries? Basically,when I refresh the table view, it is not querying the correct data and my pagination implementation does not seem to be working correctly.
Basically, the problem was that I was inserting the post in the wrong place in the array and the last item was still being added to the array which was always the same (as I ended on the value). As such, I added a counter that incremented each time a value was added. Then, I inserted the subsequent post at the counter value in the array then again incremented. Finally, if the message ID was equal to the current first message in array, I would not insert it.
This is my data structure: many clubs, each club has address. I tried to make the database flat.
Now I want to load a few club info on table view. When I swipe down iPhone screen, it will load next a few club info.
This is my code. But it loads all club info. How can I load only a few club, and load next a few club when user swipe down?
func loadClubs() {
ref = Database.database().reference()
ref.child("club").observe(DataEventType.value, with: { (snapshot) in
//print("clubs: \(snapshot)")
let array:NSArray = snapshot.children.allObjects as NSArray
for obj in array {
let snapshot:DataSnapshot = obj as! DataSnapshot
if let childSnapshot = snapshot.value as? [String: AnyObject] {
if let clubName = childSnapshot["name"] as? String {
print(clubName)
}
}
}
})
}
Firebase's queries support pagination, but it's slightly different from what you're used to. Instead of working with offsets, Firebase uses so-called anchor values to determine where to start.
Getting the items for the first page is easy, you just specify a limit to the number of items to retrieve:
ref = Database.database().reference()
query = ref.child("club").queryOrderedByKey().limitToFirst(10)
query.observe(DataEventType.value, with: { (snapshot) in
Now within the block, keep track of the key of the last item you've shown to the user:
for obj in array {
let snapshot:DataSnapshot = obj as! DataSnapshot
if let childSnapshot = snapshot.value as? [String: AnyObject] {
lastKey = childSnapshot.key
if let clubName = childSnapshot["name"] as? String {
print(clubName)
}
}
}
Then to get the next page, construct a query that starts at the last key you've seen:
query = ref.child("club").queryOrderedByKey().startAt(lastKey).limitToFirst(11)
You'll need to retrieve one more item than your page size, since the anchor item is retrieve in both pages.
i think the right way make reference to array of elements,
and make variable for index
var i = 0;
var club = null;
club = loadClubs(index); // here should return club with specified index;
// and increment index in loadClubs func,
// also if you need steped back --- and one more
// argument to function, for example
// loadClubs(index, forward) // where forward is
// boolean that says in which way we should
// increment or decrement our index
so in your example will be something like this:
func loadClubs(index, forward) {
ref = Database.database().reference()
ref.child("club").observe(data => {
var club = data[index];
if(forward){
index ++
}else{
index--
}
return club;
})
}