I want to fill a tableView with the last heard songs. With .nowPlayingItem I can get the very last song, but how can I get the songs heard before?
I think this question already has an answer at Retrieve list of songs ordered by last play time in iOS, but it is in Objective-C and I´m not able to translate it. Or is there an even better way to do it in Swift instead?
You could do something like this:
let startTime: NSTimeInterval = NSDate().timeIntervalSince1970
let songsQuery: MPMediaQuery = MPMediaQuery.songsQuery()
let songsArray: [MPMediaItem] = songsQuery.items!
let songsNSArray : NSArray = NSArray(array: songsArray)
let descriptor: NSSortDescriptor = NSSortDescriptor(key: MPMediaItemPropertyLastPlayedDate, ascending: false)
let sortedResults: NSArray = songsNSArray.sortedArrayUsingDescriptors([descriptor])
let finishTime: NSTimeInterval = NSDate().timeIntervalSince1970
NSLog("Execution took %f seconds to return %i results.", finishTime - startTime, sortedResults.count)
The results would be stored in the sortedResults array
This is how you can do it in Swift,
let start = NSDate().timeIntervalSince1970
let songsQuery = MPMediaQuery.songsQuery()
if let songsArray = songsQuery.items {
let sortedArray = songsArray.sort { item1, item2 in
if let lastPlayedDateForItem1 = item1.lastPlayedDate,
let lastPlayedDateForItem2 = item2.lastPlayedDate {
return lastPlayedDateForItem1.compare(lastPlayedDateForItem2) == .OrderedDescending
}
return false
}
}
Related
With the series 6 Apple Watch, you can now get a measure of your SP02, hemoglobin content in your blood oxygen. The health app on the iPhone shows you all the measurements in the Respiratory section. This is a critical component for COVID patients.
I have not been able to find anyway to access this information programatically.
I have checked all HKObjectTypes in the latest Apple documentation. Is this information currently available to iOS developers?
Any information would be of great use as several researchers are requesting it.
Ok, I am being told that this is the same as Oxygen Saturation.Here is the code I use to query HK for Oxygen Saturation:
// Get SPO2
func getOxygenSaturation()
{
// Type is SPO2 SDNN
let osType:HKQuantityType = HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.oxygenSaturation)!
let predicate = HKQuery.predicateForSamples(withStart: Date.distantPast, end: Date(), options: .strictEndDate)
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: false)
let osUnit:HKUnit = HKUnit(from: "%")
let osQuery = HKSampleQuery(sampleType: osType,
predicate: predicate,
limit: 10,
sortDescriptors: [sortDescriptor]) { (query, results, error) in
guard error == nil else { print("error"); return }
// Get the array of results from the sample query
let sampleArray:[HKSample]? = results!
// Loop through the array of rsults
for (_, sample) in sampleArray!.enumerated()
{
// Be sure something is there
if let currData:HKQuantitySample = sample as? HKQuantitySample
{
let os: Double = (currData.quantity.doubleValue(for: osUnit) * 100.0)
let d1: Date = currData.startDate
let str1 = SwiftLib.returnDateAndTimeWithTZ(date: d1, info: self.info!)
Dispatch.DispatchQueue.main.async {
self.tvOxygenValue.text = String(format: "%.0f%#", os, "%");
self.tvOxygenDate.text = str1
//print("\(os)");
}
}
}
print("Done")
self.loadAndDisplayActivityInformation()
}
healthStore!.execute(osQuery)
}
I'm currently using the following code to query for the number of hours the user was asleep in the last 24 hours:
func getHealthKitSleep() {
let healthStore = HKHealthStore()
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false)
// Get all samples from the last 24 hours
let endDate = Date()
let startDate = endDate.addingTimeInterval(-1.0 * 60.0 * 60.0 * 24.0)
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: [])
// Sleep query
let sleepQuery = HKSampleQuery(
sampleType: HKObjectType.categoryType(forIdentifier: HKCategoryTypeIdentifier.sleepAnalysis)!,
predicate: predicate,
limit: 0,
sortDescriptors: [sortDescriptor]){ (query, results, error) -> Void in
if error != nil {return}
// Sum the sleep time
var minutesSleepAggr = 0.0
if let result = results {
for item in result {
if let sample = item as? HKCategorySample {
if sample.value == HKCategoryValueSleepAnalysis.asleep.rawValue && sample.startDate >= startDate {
let sleepTime = sample.endDate.timeIntervalSince(sample.startDate)
let minutesInAnHour = 60.0
let minutesBetweenDates = sleepTime / minutesInAnHour
minutesSleepAggr += minutesBetweenDates
}
}
}
self.sleep = Double(String(format: "%.1f", minutesSleepAggr / 60))!
print("HOURS: \(String(describing: self.sleep))")
}
}
// Execute our query
healthStore.execute(sleepQuery)
}
This works great if the user has only one sleep app as the source for the data. The problem is if the user is using 2 sleep apps, for example, as sources, the data will be doubled. How can I differentiate the sources? If able to differentiate the sources, I would like to either only grab data from one source, or maybe take the average of the sources.
When you're looping over the samples, you can access information about the source for each. I only accept a single source, so I just keep a variable of the source name and if the current sample has a different source name I continue looping without processing the data from that sample, but you could combine the data in other ways if you wanted to.
Here's how to access the source info:
if let sample = item as? HKCategorySample {
let name = sample.sourceRevision.source.name
let id = sample.sourceRevision.source.bundleIdentifier
}
There's some more info on the HKSourceRevision object in the docs here.
I have string which text is "00:01:30" in formate of hh:mm:ss.
I want this time as seek time for my video player. Means in sort i want to set 'seekToTime' from string value which is hh:mm:ss. I convert this in NSDate formate but not able to seeToTime using this. Please suggest.
Thank you.
self.audioPlayer.timeFormat(self.audioPlayer.getCurrentAudioTime()))
func getCurrentAudioTime(){
let dur = CMTimeGetSeconds(audioPlayer.currentTime())
let cmValue = NSTimeInterval(dur)
let newTime = CMTimeMakeWithSeconds(value, 1)
self.audioPlayer.seekToTime(newTime)
}
func timeFormat(value:Double) -> NSString
{
let someFloat = Float(value)
let floatvalue = lroundf(someFloat)
let floorvalue = Double(floatvalue)
let minutes = floor(floorvalue / 60)
let seconds = floorvalue - (minutes * 60)
let roundsec = Float(seconds)
let roundmin = Float(minutes)
let roundedSeconds = lroundf(roundsec)
let roundedMinutes = lroundf(roundmin)
var time : NSString!
time = NSString.init(format:"%d:%02d", roundedMinutes,roundedSeconds)
return time;
}
if you convert the string to CMTime format then assign the seek time, and the code is given below,
let fileURL: NSURL = NSURL(string: timeString)!
let asset = AVURLAsset(URL: fileURL, options: nil)
let audioDuration = asset.duration
let newTime:CMTime = CMTimeGetSeconds(audioDuration)
playerVal.seekToTime(newTime)
hope its helpful
You can convert the string to CMTime using the code below,
func seekTime(timeString:String) -> CMTime? {
let dateFormatter = NSDateFormatter.init()
dateFormatter.dateFormat = "HH:mm:ss"
if let startDate = dateFormatter.dateFromString("00:00:00") {
if let neededDate = dateFormatter.dateFromString(timeString) {
let interval = neededDate.timeIntervalSinceDate(startDate)
return CMTime.init(value: CMTimeValue(interval), timescale: 1)
}
}
return nil;
}
I have built a method that imports a sleep sample but I can't get it to return the proper value for hours asleep.
The method to query for sleep data looks like this:
func updateHealthCategories() {
let categoryType = HKObjectType.categoryTypeForIdentifier(HKCategoryTypeIdentifierSleepAnalysis)
let start = NSDate(dateString:"2015-11-04")
let end = NSDate(dateString:"2015-11-05")
let categorySample = HKCategorySample(type: categoryType!,
value: HKCategoryValueSleepAnalysis.Asleep.rawValue,
startDate: start,
endDate: end)
self.hoursSleep = Double(categorySample.value)
print(categorySample.value)
}
The date is formatted like this:
extension NSDate
{
convenience
init(dateString:String) {
let dateStringFormatter = NSDateFormatter()
dateStringFormatter.dateFormat = "yyyy-MM-dd"
dateStringFormatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")
let d = dateStringFormatter.dateFromString(dateString)!
self.init(timeInterval:0, sinceDate:d)
}
}
I'm calling data from November 4-5, which contains this data:
However, the categorySample.value returns 1 instead of 3.
The value you are accessing is the category sample value, an HKCategoryType, and not the number of hours of sleep.
The definition for HKCategoryTypeIdentifierSleepAnalysis
typedef enum : NSInteger {
HKCategoryValueSleepAnalysisInBed,
HKCategoryValueSleepAnalysisAsleep,
} HKCategoryValueSleepAnalysis;
defines two possible values, 0 or 1 where the value of 1 matches HKCategoryValueSleepAnalysisAsleep.
Getting the hours asleep requires setting up a HKSampleQuery.
The code looks something like this:
if let sleepType = HKObjectType.categoryTypeForIdentifier(HKCategoryTypeIdentifierSleepAnalysis) {
let predicate = HKQuery.predicateForSamplesWithStartDate(startDate, endDate: endDate, options: .None)
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false)
let query = HKSampleQuery(sampleType: sleepType, predicate: predicate, limit: 30, sortDescriptors: [sortDescriptor]) { (query, tmpResult, error) -> Void in
if let result = tmpResult {
for item in result {
if let sample = item as? HKCategorySample {
let value = (sample.value == HKCategoryValueSleepAnalysis.InBed.rawValue) ? "InBed" : "Asleep"
print("sleep: \(sample.startDate) \(sample.endDate) - source: \(sample.source.name) - value: \(value)")
let seconds = sample.endDate.timeIntervalSinceDate(sample.startDate)
let minutes = seconds/60
let hours = minutes/60
}
}
}
}
healthStore.executeQuery(query)
}
I summarized this from http://benoitpasquier.fr/sleep-healthkit/.
Here is Swift 5, iOS 16 compatible answer if someone is still looking. You can parse/operate data as per your needs.
func getSleepAnalysis() {
let healthStore = HKHealthStore()
let endDate = Date()
guard let startDate = Calendar.current.date(byAdding: .day, value: -7, to: endDate) else {
fatalError("*** Unable to create the start date ***")
}
// first, we define the object type we want
guard let sleepType = HKObjectType.categoryType(forIdentifier: .sleepAnalysis) else {
return
}
// we create a predicate to filter our data
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: .strictStartDate)
// I had a sortDescriptor to get the recent data first
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false)
// we create our query with a block completion to execute
let query = HKSampleQuery(sampleType: sleepType, predicate: predicate, limit: Int(HKObjectQueryNoLimit), sortDescriptors: [sortDescriptor]) { (query, result, error) in
if error != nil {
// handle error
return
}
if let result = result {
// do something with those data
result
.compactMap({ $0 as? HKCategorySample })
.forEach({ sample in
guard let sleepValue = HKCategoryValueSleepAnalysis(rawValue: sample.value) else {
return
}
let isAsleep = sleepValue == .asleep
print("HealthKit sleep \(sample.startDate) \(sample.endDate) - source \(sample.sourceRevision.source.name) - isAsleep \(isAsleep)")
})
}
}
// finally, we execute our query
healthStore.execute(query)
}
I hope you'll get the authorization for SleepAnalysis before this so data is retrived.
I have managed to create an up next queuing system that works (almost) perfectly. I can move, delete and add songs to the list without any issues at all. The only problem i am having is that when the user decides to use a shuffle mode (e.g. MPMusicShuffleMode.Songs), the up next view still displays the old order (assumes shuffle mode is set to off). The app Ecoute has a similar queuing system but it also works when shuffling.
I've realized that Ecoute does not use the MPMusicShuffleMode, if you go into the stock music player after pressing shuffle in Ecoute, it stays the same. I've figured out a way to display the correct order for shuffled songs (just shuffle the NSMutableArray and set this as the new queue). The real problem is how would i shuffle this array when the user wants to 'Shuffle Albums'? The NSMutableArray contains MPMediaItem if that helps.
Many thanks.
EDIT:
This attempt works but the time grows exponentially for the number of songs. 2000 songs took about 25-35 seconds, so very slow.
else if(self.shuffleMode == "Songs"){
self.shuffleMode = "Albums"
self.shuffle.setTitle("Shuffle Albums", forState: UIControlState.Normal)
var queueCopy = NSMutableArray(array: self.queue.items)
var newQueue = NSMutableArray(array: [])
var albums = MPMediaQuery.albumsQuery().collections as [MPMediaItemCollection]
var albumsCopy = NSMutableArray(array: albums)
shuffleArray(albumsCopy)
for album in albumsCopy{
for item in queueCopy{
if (album.representativeItem!.valueForProperty(MPMediaItemPropertyAlbumTitle) as NSString == item.valueForProperty(MPMediaItemPropertyAlbumTitle) as NSString){
for(var i = 0; i < album.items!.count; i++){
if(album.items![i].valueForProperty(MPMediaItemPropertyTitle) as NSString == item.valueForProperty(MPMediaItemPropertyTitle) as NSString){
newQueue.addObject(item as MPMediaItem)
}
}
}
}
}
let newQueueToSet = MPMediaItemCollection(items: newQueue)
let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
appDelegate.currentQueue = newQueueToSet
self.queue = newQueueToSet
self.player.setQueueWithItemCollection(newQueueToSet)
}
EDIT 2:
Managed to get it down to 8 seconds but it's nowhere near Ecoute's 1 to 2 seconds.
8 second version:
var albumNames = [String]()
for item in queueCopy{
albumNames.append(item.valueForProperty(MPMediaItemPropertyAlbumTitle) as NSString)
}
let unique = NSSet(array: albumNames).allObjects as [MPMediaItem]
var uniqueAlbumNames = NSMutableArray(array: unique)
shuffleArray(uniqueAlbumNames)
for name in uniqueAlbumNames{
for item in queueCopy{
if item.valueForProperty(MPMediaItemPropertyAlbumTitle) as NSString == name as NSString{
newQueue.addObject(item as MPMediaItem)
}
}
}
Final Edit:
final piece of code that i'm sticking with. takes about 7-8 seconds for nearly 3000 songs.
#IBAction func shufflePressed(sender: AnyObject) {
if self.shuffleMode == "Off"{
self.shuffleMode = "Songs"
self.shuffle.setTitle("Shuffle All", forState: UIControlState.Normal)
self.shuffle.setTitle("Shuffling", forState: UIControlState.Highlighted)
var queueCopy = NSMutableArray(array: self.queue.items)
self.unshuffledQueueCopy = self.queue
shuffleArray(queueCopy)
let newQueue = MPMediaItemCollection(items: queueCopy)
let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
appDelegate.currentQueue = newQueue
self.queue = newQueue
self.player.setQueueWithItemCollection(newQueue)
}
else if(self.shuffleMode == "Songs"){
self.shuffleMode = "Albums"
self.shuffle.setTitle("Shuffle Albums", forState: UIControlState.Normal)
var queueCopy = NSMutableArray(array: self.queue.items)
var newQueue = NSMutableArray(array: [])
var albums = MPMediaQuery.albumsQuery().collections as [MPMediaItemCollection]
var albumsCopy = NSMutableArray(array: albums)
shuffleArray(albumsCopy)
// Takes 8 seconds for 3000 songs
var albumNames = [String]()
for item in queueCopy{
albumNames.append(item.valueForProperty(MPMediaItemPropertyAlbumTitle) as NSString)
}
let unique = NSSet(array: albumNames).allObjects as [MPMediaItem]
var uniqueAlbumNames = NSMutableArray(array: unique)
shuffleArray(uniqueAlbumNames)
for name in uniqueAlbumNames{
for item in queueCopy{
if item.valueForProperty(MPMediaItemPropertyAlbumTitle) as NSString == name as NSString{
newQueue.addObject(item as MPMediaItem)
}
}
}
let newQueueToSet = MPMediaItemCollection(items: newQueue)
let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
appDelegate.currentQueue = newQueueToSet
self.queue = newQueueToSet
self.player.setQueueWithItemCollection(newQueueToSet)
}
else if(self.shuffleMode == "Albums"){
self.shuffleMode = "Off"
self.shuffle.setTitle("Shuffle", forState: UIControlState.Normal)
var queueCopy = NSMutableArray(array: self.unshuffledQueueCopy.items)
var nowPlayingItem = self.player.nowPlayingItem
for(var i = 0; i < queueCopy.count; i++){
if(queueCopy[i] as MPMediaItem == nowPlayingItem){
self.fromIndex = i
}
}
let newQueue = MPMediaItemCollection(items: queueCopy)
let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
appDelegate.currentQueue = newQueue
self.queue = newQueue
self.player.setQueueWithItemCollection(newQueue)
self.fromItem = newQueue.items[fromIndex + 1] as MPMediaItem
}
So in pseudo code it would look something like the following.
Loop the Array making a new array of the albums only
Shuffle the new Albums array
Loop the new albums array, looking through the original array for items in that album.
On match, write those songs into a third array which will become your new playlist.
Thanks