Audio files won't play with AirPods Pro on iOS 15 - ios

I am using this code to play audio. My code works fine on iOS 14 with all headphones model, but when customers have updated their devices to iOS 15 and are using AirPods Pro, no audio files play. On other AirPods models and when playing audio files through the iPhone speaker, everything works. What's happened. How to fix it?
Update:
After a long wait, I was given AirPods Pro. And at first I removed that line setupMediaPlayerNotificationView(true) and the app played a sound fine. But some of the functions on Lock Screen were removed. And with this line in the app there was no sound. In the App Store, I had 3 apps with the same code. And after ios 15 only one worked. And I did not understand in any way what is the reason if the code is the same. Why aren't the others working? But it turned out that the app that worked had 1 word in its name - Build Settings -> Product Name -> "myAppName". And the rest had a few words. And when I renamed them to 1 word everything working fine. Sound play fine. What was it? I still don't understand? If anyone has a version, share it.
code:
let url = Bundle.main.url(forResource: "\(masterIndex)0", withExtension: "m4a")!
do {
audioPlayer = try AVAudioPlayer(contentsOf: url)
audioPlayer.delegate = self
audioPlayer.prepareToPlay()
play(sender:AnyObject.self as AnyObject)
setupMediaPlayerNotificationView(true)
lockScreen()
} catch {
}
other code:
func lockScreen() {
var albumArtwork : MPMediaItemArtwork!
let image:UIImage = UIImage(named: "infoImage")!
albumArtwork = MPMediaItemArtwork.init(boundsSize: image.size, requestHandler: { (size) -> UIImage in
return image
})
let infotitle = "\(firstArray[index])"
MPNowPlayingInfoCenter.default().nowPlayingInfo = [
MPMediaItemPropertyArtist : "",
MPMediaItemPropertyTitle : infotitle,
MPMediaItemPropertyArtwork : albumArtwork,
MPMediaItemPropertyAlbumTitle : "",
MPNowPlayingInfoPropertyElapsedPlaybackTime : Int(audioPlayer.currentTime),
MPMediaItemPropertyPlaybackDuration: Int(audioPlayer.duration)]
}
#objc func lockScreenPlay(_ event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
self.audioPlayer.play()
self.lockScreen()
self.playButton.setImage(UIImage(named: "pause.png"), for: UIControlState.normal)
return .success
}
#objc func lockScreenPause(_ event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
self.audioPlayer.pause()
self.lockScreen()
self.playButton.setImage(UIImage(named: "play.png"), for: UIControlState.normal)
return .success
}
#objc func lockScreenFastForward(_ event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
var time: TimeInterval = audioPlayer.currentTime
time += 15.0
if time > audioPlayer.duration {
audioPlayerDidFinishPlaying(audioPlayer, successfully: true)
} else {
audioPlayer.currentTime = time
updateTime()
}
self.lockScreen()
return .success
}
#objc func lockScreenFastBackward(_ event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
var time: TimeInterval = audioPlayer.currentTime
time -= 15.0
if time < 0 {
audioPlayer.currentTime = 0
updateTime()
} else {
audioPlayer.currentTime = time
updateTime()
}
self.lockScreen()
return .success
}
#objc func changedThumbSlider(_ event: MPChangePlaybackPositionCommandEvent) -> MPRemoteCommandHandlerStatus {
let time = event.positionTime
audioPlayer.currentTime = TimeInterval(time)
self.lockScreen()
return .success
}
func setupMediaPlayerNotificationView(_ enable: Bool) {
let commandCenter = MPRemoteCommandCenter.shared()
if enable {
commandCenter.playCommand.addTarget(self, action: #selector(lockScreenPlay))
commandCenter.pauseCommand.addTarget(self, action: #selector(lockScreenPause))
commandCenter.skipForwardCommand.preferredIntervals = [15]
commandCenter.skipForwardCommand.addTarget(self, action: #selector(lockScreenFastForward))
commandCenter.skipBackwardCommand.preferredIntervals = [15]
commandCenter.skipBackwardCommand.addTarget(self, action: #selector(lockScreenFastBackward))
commandCenter.changePlaybackPositionCommand.addTarget(self, action: #selector(self.changedThumbSlider(_:)))
} else {
commandCenter.playCommand.removeTarget(self, action: #selector(lockScreenPlay))
commandCenter.pauseCommand.removeTarget(self, action: #selector(lockScreenPause))
commandCenter.skipForwardCommand.removeTarget(self, action: #selector(lockScreenFastForward))
commandCenter.skipBackwardCommand.removeTarget(self, action: #selector(lockScreenFastBackward))
commandCenter.changePlaybackPositionCommand.removeTarget(self, action: #selector(self.changedThumbSlider(_:)))
}
}
#IBAction func play(sender: AnyObject) {
if !audioPlayer.isPlaying{
animationStatus()
audioPlayer.play()
slider.maximumValue = Float(audioPlayer.duration)
timer = Timer(timeInterval: 0.1, target: self, selector: #selector(self.updateTime), userInfo: nil, repeats: true)
RunLoop.main.add(timer!, forMode: .commonModes)
restorePlayerCurrentTime()
playButton.setImage(UIImage(named: "pause.png"), for: UIControlState.normal)
} else {
animationStatus()
audioPlayer.pause()
playButton.setImage(UIImage(named: "play.png"), for: UIControlState.normal)
timer?.invalidate()
}
}
#IBAction func fastForward(sender: AnyObject) {
var time: TimeInterval = audioPlayer.currentTime
time += 15.0 // Go Forward by 15 Seconds
if time > audioPlayer.duration {
audioPlayerDidFinishPlaying(audioPlayer, successfully: true)
} else {
audioPlayer.currentTime = time
updateTime()
}
self.lockScreen()
}
#IBAction func fastBackward(sender: AnyObject) {
var time: TimeInterval = audioPlayer.currentTime
time -= 15.0 // Go Back by 15 Seconds
if time < 0 {
audioPlayer.currentTime = 0
updateTime()
} else {
audioPlayer.currentTime = time
updateTime()
}
self.lockScreen()
}
private func restorePlayerCurrentTime() {
let currentTimeFromUserDefaults : Double? = UserDefaults.standard.value(forKey: "currentTime\(masterIndex)\(index)") as! Double?
if let currentTimeFromUserDefaultsValue = currentTimeFromUserDefaults {
audioPlayer.currentTime = currentTimeFromUserDefaultsValue
slider.value = Float.init(audioPlayer.currentTime)
}
}
#objc func updateTime() {
let currentTime = Int(audioPlayer.currentTime)
let minutes = currentTime/60
let seconds = currentTime - minutes * 60
let durationTime = Int(audioPlayer.duration) - Int(audioPlayer.currentTime)
let minutes1 = durationTime/60
let seconds1 = durationTime - minutes1 * 60
timeElapsed.text = NSString(format: "%02d:%02d", minutes,seconds) as String
timeDuration.text = NSString(format: "-%02d:%02d", minutes1,seconds1) as String
UserDefaults.standard.set(currentTime, forKey: "currentTime\(masterIndex)\(index)")
UserDefaults.standard.set(durationTime, forKey: "durationTime\(masterIndex)\(index)")
slider.value = Float.init(audioPlayer.currentTime)
}
func audioPlayerDidFinishPlaying(_ audioPlayer: AVAudioPlayer, successfully flag: Bool) {
let currentTime = 0
let durationTime = 0.1
UserDefaults.standard.set(currentTime, forKey: "currentTime\(masterIndex)\(index)")
UserDefaults.standard.set(durationTime, forKey: "durationTime\(masterIndex)\(index)")
slider.value = Float.init(audioPlayer.currentTime)
timer?.invalidate()
let path = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)
let documentDirectoryPath:String = path[0]
let fileManager = FileManager()
let destinationURLForFile = URL(fileURLWithPath: documentDirectoryPath.appendingFormat("/\(masterIndex)/\(index+1).mp3"))
if fileManager.fileExists(atPath: destinationURLForFile.path){
if endOfChapterSleepTimer == true {
endOfChapterSleepTimer = false
} else {
index = index + 1
viewDidLoad()
}
} else {
}
}
func animationStatus() {
let vinylLayer = vinylView.layer
pause = !pause
if pause {
pauseLayer(layer: vinylLayer)
} else {
if vinylStatus == "true" {
resumeLayer(layer: vinylLayer)
} else {
rotateImageView()
resumeLayer(layer: vinylLayer)
}
}
}
private func rotateImageView() {
vinylStatus = "true"
UIView.animate(withDuration: 3, delay: 0, options: .curveLinear, animations: {
self.vinylView.transform = self.vinylView.transform.rotated(by: .pi / 2)
}) { (finished) in
if finished {
self.rotateImageView()
}
}
}

May be it is a problem of iOS 15?
I've found some papers for search query: "sound doesn't play AirPods Pro on ios 15": one, tow, three .
As I understand, this problem was fixed in iOS 15.1.
Sometimes Apple makes mistakes (for example, every updating of Xcode :) )

Related

Unable to play audio file using url in my application in iOS swift

audio link
I uploaded a recording in my google drive link(mentioned above) and set that to public. I am trying to play the audio using audio kit and other audio player(cocoapods) with url, downloading the file and play and I tried converting to other formats nothing worked for me. I am unable to play it. This audio is not playing in safari browser also. The recording is done from the web platform and the generated audio format is .opus
Try please the code below and tell me if is working for you.
import UIKit
import AVFoundation
class ViewController: UIViewController {
var player: AVPlayer?
var playerItem:AVPlayerItem?
fileprivate let seekDuration: Float64 = 10
#IBOutlet weak var labelCurrentTime: UILabel!
#IBOutlet weak var labelOverallDuration: UILabel!
#IBOutlet weak var playbackSlider: UISlider!
#IBOutlet weak var playButtonOutlet: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
initAudioPlayer()
}
//call this mehtod to init audio player
func initAudioPlayer() {
let url = URL(string: "https://argaamplus.s3.amazonaws.com/eb2fa654-bcf9-41de-829c-4d47c5648352.mp3")
let playerItem:AVPlayerItem = AVPlayerItem(url: url!)
player = AVPlayer(playerItem: playerItem)
playbackSlider.minimumValue = 0
//To get overAll duration of the audio
let duration : CMTime = playerItem.asset.duration
let seconds : Float64 = CMTimeGetSeconds(duration)
labelOverallDuration.text = self.stringFromTimeInterval(interval: seconds)
//To get the current duration of the audio
let currentDuration : CMTime = playerItem.currentTime()
let currentSeconds : Float64 = CMTimeGetSeconds(currentDuration)
labelCurrentTime.text = self.stringFromTimeInterval(interval: currentSeconds)
playbackSlider.maximumValue = Float(seconds)
playbackSlider.isContinuous = true
player!.addPeriodicTimeObserver(forInterval: CMTimeMakeWithSeconds(1, preferredTimescale: 1), queue: DispatchQueue.main) { (CMTime) -> Void in
if self.player!.currentItem?.status == .readyToPlay {
let time : Float64 = CMTimeGetSeconds(self.player!.currentTime());
self.playbackSlider.value = Float ( time );
self.labelCurrentTime.text = self.stringFromTimeInterval(interval: time)
}
let playbackLikelyToKeepUp = self.player?.currentItem?.isPlaybackLikelyToKeepUp
if playbackLikelyToKeepUp == false{
print("IsBuffering")
self.playButtonOutlet.isHidden = true
} else {
//stop the activity indicator
print("Buffering completed")
self.playButtonOutlet.isHidden = false
}
}
//change the progress value
playbackSlider.addTarget(self, action: #selector(playbackSliderValueChanged(_:)), for: .valueChanged)
//check player has completed playing audio
NotificationCenter.default.addObserver(self, selector: #selector(self.finishedPlaying(_:)), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: playerItem)}
#objc func playbackSliderValueChanged(_ playbackSlider:UISlider) {
let seconds : Int64 = Int64(playbackSlider.value)
let targetTime:CMTime = CMTimeMake(value: seconds, timescale: 1)
player!.seek(to: targetTime)
if player!.rate == 0 {
player?.play()
}
}
#objc func finishedPlaying( _ myNotification:NSNotification) {
playButtonOutlet.setImage(UIImage(named: "play"), for: UIControl.State.normal)
//reset player when finish
playbackSlider.value = 0
let targetTime:CMTime = CMTimeMake(value: 0, timescale: 1)
player!.seek(to: targetTime)
}
#IBAction func playButton(_ sender: Any) {
print("play Button")
if player?.rate == 0
{
player?.play()
self.playButtonOutlet.isHidden = true
playButtonOutlet.setImage(UIImage(systemName: "pause"), for: UIControl.State.normal)
} else {
player?.pause()
playButtonOutlet.setImage(UIImage(systemName: "play"), for: UIControl.State.normal)
}
}
func stringFromTimeInterval(interval: TimeInterval) -> String {
let interval = Int(interval)
let seconds = interval % 60
let minutes = (interval / 60) % 60
let hours = (interval / 3600)
return String(format: "%02d:%02d:%02d", hours, minutes, seconds)
}
#IBAction func seekBackWards(_ sender: Any) {
if player == nil { return }
let playerCurrenTime = CMTimeGetSeconds(player!.currentTime())
var newTime = playerCurrenTime - seekDuration
if newTime < 0 { newTime = 0 }
player?.pause()
let selectedTime: CMTime = CMTimeMake(value: Int64(newTime * 1000 as Float64), timescale: 1000)
player?.seek(to: selectedTime)
player?.play()
}
#IBAction func seekForward(_ sender: Any) {
if player == nil { return }
if let duration = player!.currentItem?.duration {
let playerCurrentTime = CMTimeGetSeconds(player!.currentTime())
let newTime = playerCurrentTime + seekDuration
if newTime < CMTimeGetSeconds(duration)
{
let selectedTime: CMTime = CMTimeMake(value: Int64(newTime * 1000 as Float64), timescale: 1000)
player!.seek(to: selectedTime)
}
player?.pause()
player?.play()
}
}
}
Here is a DEMO:
https://github.com/florentin89/PlaySongFromExternalSource

Countdown timer in table view cell shows different values after scrolling

The problem is described in title, but to be more specific here is a full picture.
I have a custom table view cell subclass with label inside it displaying the countdown timer. When there a small portion of timers it works fine, but with a lot of data I need to display timers far beyond the visible cells and when I scroll down fast and then scroll up fast, the timer values in cells start to show different values until a certain point in time, after which it shows the right value.
I tried different variants for those reuseable cells, but I can’t spot a problem. Help needed!!!
Here is the code of implementation of logic.
Custom cell subclass:
let calendar = Calendar.current
var timer: Timer?
var deadlineDate: Date? {
didSet {
updateTimeLabel()
}
}
override func awakeFromNib() {
purchaseCellCardView.layer.cornerRadius = 10
let selectedView = UIView(frame: CGRect.zero)
selectedView.backgroundColor = UIColor.clear
selectedBackgroundView = selectedView
}
override func prepareForReuse() {
super.prepareForReuse()
if timer != nil {
print("Invalidated!")
timer?.invalidate()
timer = nil
}
}
func configure(for purchase: Purchase) {
purchaseSubjectLabel.text = purchase.subject
startingPriceLabel.text = purchase.NMC
stageLabel.text = purchase.stage
fzImageView.image = purchase.fedLaw.contains("44") ? #imageLiteral(resourceName: "FZ44") : #imageLiteral(resourceName: "FZ223")
timeLabel.isHidden = purchase.stage == "Работа комиссии"
warningImageView.image = purchase.warningImage
}
func updateTimeLabel() {
setTimeLeft()
timer = Timer(timeInterval: 1, repeats: true) { [weak self] _ in
guard let strongSelf = self else {return}
strongSelf.setTimeLeft()
}
RunLoop.current.add(timer!, forMode: .commonModes)
}
#objc private func setTimeLeft() {
let currentDate = getCurrentLocalDate()
if deadlineDate?.compare(currentDate) == .orderedDescending {
var components = calendar.dateComponents([.day, .hour, .minute, .second], from: currentDate, to: deadlineDate!)
let dayText = (components.day! == 0 || components.day! < 0) ? "" : String(format: "%i", components.day!)
let hourText = (components.hour == 0 || components.hour! < 0) ? "" : String(format: "%i", components.hour!)
switch (dayText, hourText) {
case ("", ""):
timeLabel.text = String(format: "%02i", components.minute!) + ":" + String(format: "%02i", components.second!)
case ("", _):
timeLabel.text = hourText + " ч."
default:
timeLabel.text = dayText + " дн."
}
} else {
stageLabel.text = "Работа комиссии"
timeLabel.text = ""
timeLabel.isHidden = true
timer?.invalidate()
}
}
private func getCurrentLocalDate() -> Date {
var now = Date()
var nowComponents = calendar.dateComponents([.year, .month, .day, .hour, .minute, .second], from: now)
nowComponents.timeZone = TimeZone(abbreviation: "UTC")
now = calendar.date(from: nowComponents)!
return now
}
deinit {
print("DESTROYED")
timer?.invalidate()
timer = nil
}
The most important part of tableView(_cellForRowAt:)
case .results:
if filteredArrayOfPurchases.isEmpty {
let cell = tableView.dequeueReusableCell(
withIdentifier: TableViewCellIdentifiers.nothingFoundCell,
for: indexPath)
let label = cell.viewWithTag(110) as! UILabel
switch segmentedControl.index {
case 1:
label.text = "Нет закупок способом\n«Запрос предложений»"
case 2:
label.text = "Нет закупок способом\n«Конкурс»"
case 3:
label.text = "Нет закупок способом\n«Аукцион»"
default:
label.text = "Нет закупок способом\n«Запрос котировок»"
}
return cell
} else {
let cell = tableView.dequeueReusableCell(
withIdentifier: TableViewCellIdentifiers.purchaseCell,
for: indexPath) as! PurchaseCell
cell.containerViewTopConstraint.constant = indexPath.row == 0 ? 8.0 : 4.0
cell.containerViewBottomConstraint.constant = indexPath.row == filteredArrayOfPurchases.count - 1 ? 8.0 : 4.0
let purchase = filteredArrayOfPurchases[indexPath.row]
cell.configure(for: purchase)
if cell.timer != nil {
cell.updateTimeLabel()
} else {
search.getDeadlineDateAndTimeToApply(purchase.purchaseURL, purchase.fedLaw, purchase.stage, completion: { (date) in
cell.deadlineDate = date
})
}
return cell
}
And the last piece of a puzzle:
func getDeadlineDateAndTimeToApply(_ url: URL?, _ fedLaw: String, _ stage: String, completion: #escaping (Date) -> ()) {
var deadlineDateAndTimeToApply = Date()
guard stage != "Работа комиссии" else { return }
if let url = url {
dataTask = URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
if let error = error as NSError?, error.code == -403 {
// TODO: Add alert here
return
}
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200, let data = data, let html = String(data: data, encoding: .utf8), let purchasePageBody = try? SwiftSoup.parse(html), let purchaseCard = try? purchasePageBody.select("td").array() else {return}
let mappedArray = purchaseCard.map(){String(describing: $0)}
if fedLaw.contains("44") {
guard let deadlineDateToApplyString = try? purchaseCard[(mappedArray.index(of: "<td class=\"fontBoldTextTd\">Дата и время окончания подачи заявок</td>"))! + 1].text().components(separatedBy: " ") else {return}
dateFormatter.dateFormat = "dd.MM.yyyy HH:mm"
let deadlineDateToApply = deadlineDateToApplyString.first!
let deadlineTimeToApply = deadlineDateToApplyString[1]
guard let deadlineDateAndTimeToApplyCandidate = dateFormatter.date(from: "\(deadlineDateToApply) \(deadlineTimeToApply)") else {return}
deadlineDateAndTimeToApply = deadlineDateAndTimeToApplyCandidate
} else {
guard let deadlineDateToApplyString = try? purchaseCard[(mappedArray.index(of: "<td>Дата и время окончания подачи заявок<br> (по местному времени заказчика)</td>"))! + 1].text().components(separatedBy: " ") else {return}
dateFormatter.dateFormat = "dd.MM.yyyy HH:mm"
let deadlineDateToApply = deadlineDateToApplyString.first!
let deadlineTimeToApply = deadlineDateToApplyString[2]
guard let deadlineDateAndTimeToApplyCandidate = dateFormatter.date(from: "\(deadlineDateToApply) \(deadlineTimeToApply)") else {return}
deadlineDateAndTimeToApply = deadlineDateAndTimeToApplyCandidate
}
DispatchQueue.main.async {
completion(deadlineDateAndTimeToApply)
}
})
dataTask?.resume()
}
}
A few notes:
Tried resetting deadlineDate to nil in prepareForReuse() - doesn’t help;
Using SwiftSoup Framework to parse HTML as you can see in the last code example if it matters.
This is quite a lot of code but from what you are describing your issue is in reusing cells.
You would do well to separate the timers out of the cells and put them inside your objects. It is where they belong (or in some manager like view controller). Imagine having something like the following:
class MyObject {
var timeLeft: TimeInterval = 0.0 {
didSet {
if timeLeft > 0.0 && timer == nil {
timer = Timer.scheduled...
} else if timeLeft <= 0.0, let timer = timer {
timer.invalidate()
self.timer = nil
}
delegate?.myObject(self, updatedTimeLeft: timeLeft)
}
}
weak var delegate: MyObjectDelegate?
private var timer: Timer?
}
Now all you need is is a cell for row at index path to assign your object: cell.myObject = myObjects[indexPath.row].
And your cell would do something like:
var myObject: MyObject? {
didSet {
if oldValue.delegate == self {
oldValue.delegate = nil // detach from previous item
}
myObject.delegate = self
refreshUI()
}
}
func myObject(_ sender: MyObject, updatedTimeLeft timeLeft: TimeInterval) {
refreshUI()
}
I believe the rest should be pretty much straight forward...
Your problem is here:
search.getDeadlineDateAndTimeToApply(purchase.purchaseURL,
purchase.fedLaw,
purchase.stage,
completion: { (date) in
cell.deadlineDate = date
})
getDeadlineDateAndTimeToApply runs asynchronously, calculates something, and then updates the cell.deadlineData in the main thread (which is fine). But in the meantime, while calculating something, the user might have scrolled up and down, the cell might have been reused for another row, and now the update updates the cell incorrectly.
What you need to do is: Do not store the UITableViewCell directly. Instead, keep track of the IndexPath to be updated, and once the caluclation is done, retrieve the the cell that belongs to that IndexPath and update this.

Count-Up and Count-Down Timer with Date() in Swift 4 should stop after a certain time period

I am quite new to Swift and already learned a lot using the questions here.
In one of my first projects I try to write a soccer playtime timer app. The first timer is counting up after the whistle button is pressed showing the minutes played and the second timer is counting down to zero showing the minutes left to play. This works so far.
Now both timers should stop automatically when the halftime is over, so that I can start a third overtime timer. So far the invalidate statement of the timer is not working - both timers keep running. There seems to be something wrong with my if-statements, but at the moment I have no clue what. So any help would be very appreciated.
var countUpClock: Timer?
var countDownClock: Timer?
private var formatter: DateComponentsFormatter = {
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .positional
formatter.allowedUnits = [.minute, .second]
formatter.zeroFormattingBehavior = .pad
return formatter
}()
func runPlaytimeClocks() {
let startTime = Date()
let countTime = Date() + 2700 //45min of playtime
if startTime <= countTime {
countUpClock = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
self?.timePlayed.text = self?.formatter.string(from: startTime, to: Date())
}
}
else {
countUpClock?.invalidate()
}
if startTime <= countTime {
countDownClock = Timer.scheduledTimer(withTimeInterval: -1.0, repeats: true) { [weak self] _ in
self?.timetoPlay.text = self?.formatter.string(from: Date(), to: countTime)
}
}
else {
countDownClock?.invalidate()
}
Thank you very much for the replies.
I found exactly what I was looking for here: http://ioscake.com/swift-nstimer-in-background.html
I adapted the solution to my for clocks (CountUpClock, CountDownClock, OvertimeClock, HalftimeClock).
Do you have any suggestions what would be the best solution to start the second halftime of the soccer game?
So far the CountUpClock starts again at 0:00 when I press the whistle button after the halftime break. But it should keep running from minute 45:00 to 90:00 - while the CountDownClock should counting down from 45:00 to 0:00 again.
What would be the best solution for such a behaviour?
import UIKit
import UserNotifications
private let stopTimeKey = "stopTimeKey"
class ViewController: UIViewController {
//Outlets
#IBOutlet weak var timePlayed: UILabel!
#IBOutlet weak var timeToPlay: UILabel!
#IBOutlet weak var overtime: UILabel!
#IBOutlet weak var halftime: UILabel!
#IBOutlet weak var halftimeButton: UIButton!
private var stopTime: Date?
override func viewDidLoad() {
super.viewDidLoad()
registerForLocalNotifications()
stopTime = UserDefaults.standard.object(forKey: stopTimeKey) as? Date
if let time = stopTime {
if time > Date() {
startTimers(time, includeNotification: false)
} else {
notifyTimerCompleted()
}
}
}
private func registerForLocalNotifications() {
if #available(iOS 10, *) {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { granted, error in
guard granted && error == nil else {
// display error
print("\(String(describing: error))")
return
}
}
} else {
let types: UIUserNotificationType = [.badge, .sound, .alert]
let settings = UIUserNotificationSettings(types: types, categories: nil)
UIApplication.shared.registerUserNotificationSettings(settings)
}
}
//Actions
#IBAction func whistleButtonTapped(_ sender: UIButton) {
overtimeClock?.invalidate()
overtimeClock = nil
halftimeClock?.invalidate()
halftimeClock = nil
overtime.text = "00:00"
halftime.text = "00:00"
halftimeButton.isHidden = true
//add 10 seconds per halftime to try out
let time = Date() + 10
if time > Date() {
startTimers(time)
} else {
timeToPlay.text = "error"
}
}
#IBAction func halftimeButton(_ sender: UIButton) {
halftimeButtoPressed()
}
// Code for different Timers
private var countDownClock: Timer?
private var countUpClock: Timer?
var overtimeClock: Timer?
var halftimeClock: Timer?
private func startTimers(_ stopTime: Date, includeNotification: Bool = true) {
// save `stopTime` in case app is terminated
UserDefaults.standard.set(stopTime, forKey: stopTimeKey)
self.stopTime = stopTime
// start Timer
countDownClock = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(handleCountDownTimer(_:)), userInfo: nil, repeats: true)
countUpClock = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(handleCountUpTimer(_:)), userInfo: nil, repeats: true)
guard includeNotification else { return }
// start local notification (so we're notified if timer expires while app is not running)
if #available(iOS 10, *) {
let content = UNMutableNotificationContent()
content.title = "Overtime is starting soon"
content.body = "In 5 seconds the overtime will start"
content.sound = UNNotificationSound.default()
//5 seconds warning before overtime starts
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: stopTime.timeIntervalSinceNow - 5, repeats: false)
let notification = UNNotificationRequest(identifier: "timer", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(notification)
} else {
let notification = UILocalNotification()
//5 seconds warning before overtime starts
notification.fireDate = stopTime - 5
notification.alertBody = "Overtime is starting soon"
UIApplication.shared.scheduleLocalNotification(notification)
}
}
private func stopTimer() {
countDownClock?.invalidate()
countDownClock = nil
countUpClock?.invalidate()
countUpClock = nil
}
private func halftimeButtoPressed() {
overtimeClock?.invalidate()
overtimeClock = nil
startHalftimeClock()
halftimeButton.isHidden = true
}
private let dateComponentsFormatter: DateComponentsFormatter = {
let _formatter = DateComponentsFormatter()
_formatter.allowedUnits = [.minute, .second]
_formatter.unitsStyle = .positional
_formatter.zeroFormattingBehavior = .pad
return _formatter
}()
#objc func handleCountDownTimer(_ timer: Timer) {
let now = Date()
if stopTime! > now {
timeToPlay.text = dateComponentsFormatter.string(from: now, to: stopTime!)
} else {
stopTimer()
notifyTimerCompleted()
startOvertimeClock()
halftimeButton.isHidden = false
}
}
#objc func handleCountUpTimer(_ timer: Timer) {
//add 10 seconds per halftime to try out
let now = Date() + 10
if now > stopTime! {
timePlayed.text = dateComponentsFormatter.string(from: stopTime!, to: now)
} else {
stopTimer()
notifyTimerCompleted()
}
}
//Overtime Clock
#objc func startOvertimeClock() {
let startOvertime = Date()
overtimeClock = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { [weak self] _ in
self?.overtime.text = self?.dateComponentsFormatter.string(from: startOvertime, to: Date())
}
}
//Halftime Clock
#objc func startHalftimeClock() {
let startHalftime = Date()
halftimeClock = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { [weak self] _ in
self?.halftime.text = self?.dateComponentsFormatter.string(from: startHalftime, to: Date())
}
}
private func notifyTimerCompleted() {
timeToPlay.text = "End"
timePlayed.text = "End"
}
}

Swift: Use of unresolved Identifier. Target Membership

I have a Swift script below relating to Sleep Analysis.
Error:
View Controller has no member 'updateTime'.
I tried adding this target using file inspector and adding it to target membership, but the target itself won't show up, which is beyond weird. Any feedback will be greatly appreciated.
PS: Another error message not saying that 'error' is an unresolved identifier for an if != nil statement keeps popping up. Any help here, would also be highly appreciated.
import UIKit
import HealthKit
let healthStore = HKHealthStore()
class ViewController: UIViewController {
#IBOutlet var displayTimeLabel: UILabel!
var startTime = TimeInterval()
var timer:Timer = Timer()
var endTime: NSDate!
var alarmTime: NSDate!
func saveSleepAnalysis() {
//1. startTime(alarmTime) and endTime are NSDate Objects//
if let sleepType = HKObjectType.categoryType(forIdentifier: HKCategoryTypeIdentifier.sleepAnalysis) {
//we create a new object that we want to add into our Health app(This is our INBED object)//
let object1 = HKCategorySample(type:sleepType, value: HKCategoryValueSleepAnalysis.inBed.rawValue, start: self.alarmTime as Date, end: self.endTime as Date)
// Time to save the object//
healthStore.save(object1, withCompletion: { (success, errpr) -> Void in
if error != nil
{
return
}
if success {
print("My new data was saved in HealthKit")
} else {
//something happened again//
}
})
//This our ASLEEP object//
let object2 = HKCategorySample(type:sleepType, value: HKCategoryValueSleepAnalysis.asleep.rawValue, start: self.alarmTime as Date, end: self.endTime as Date)
//now we save our objects to our mainLibrary known as HealthStore
healthStore.save(object2, withCompletion: { (success, error) -> Void in
if error != nil {
//Something went wrong//
return
if success {
print("My new data (2: Asleep data) was saved into HealthKit")
} else {
//something happened again//
}
}
}
)}
func retrieveSleepAnalysis() {
//first, define our object type that we watn again in BOOLEAN FORMAT//
if let sleepType = HKObjectType.categoryType(forIdentifier: HKCategoryTypeIdentifier.sleepAnalysis) {
//use sortDescriptor to get teh recent data first: so from MostRecentData to PastData//
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false)
//we create our query with a block completion to execute
let query = HKSampleQuery(sampleType: sleepType, predicate: nil, limit:30, sortDescriptors: [sortDescriptor]) { (query, tmpResult, error) -> Void in
if error != nil {
//something happends//
return
}
if let result = tmpResult {
//then i want the computer to do something with my data//
for item in result {
if let sample = item as? HKCategorySample {
let value = (sample.value == HKCategoryValueSleepAnalysis.inBed.rawValue) ? "InBed" : "Asleep"
print("Healthkit sleep: \(sample.startDate) \(sample.endDate) = value: \(value)")
}
}
}
}
//finally, we execute our query: Print out our output file //
healthStore.execute(query)
}
}
func viewDidLoad() {
super.viewDidLoad()
let typestoRead = Set([
HKObjectType.categoryType(forIdentifier: HKCategoryTypeIdentifier.sleepAnalysis)!
])
let typestoShare = Set([
HKObjectType.categoryType(forIdentifier: HKCategoryTypeIdentifier.sleepAnalysis)!
])
healthStore.requestAuthorization(toShare: typestoShare, read: typestoRead) { (success, error) -> Void in
if success == false {
NSLog(" Display not allowed")
}
}
}
func updateTime() {
let currentTime = NSDate.timeIntervalSinceReferenceDate
//Find the difference between current time and start time.
var elapsedTime: TimeInterval = currentTime - startTime
//calculate the minutes in elapsed time.
let minutes = UInt8(elapsedTime / 60.0)
elapsedTime -= (TimeInterval(minutes) * 60)
//calculate the seconds in elapsed time.
let seconds = UInt8(elapsedTime)
elapsedTime -= TimeInterval(seconds)
//find out the fraction of milliseconds to be displayed.
let fraction = UInt8(elapsedTime * 100)
//add the leading zero for minutes, seconds and millseconds and store them as string constants
let strMinutes = String(format: "%02d", minutes)
let strSeconds = String(format: "%02d", seconds)
let strFraction = String(format: "%02d", fraction)
//concatenate minuets, seconds and milliseconds as assign it to the UILabel
displayTimeLabel.text = "\(strMinutes):\(strSeconds):\(strFraction)"
}
func start(sender: AnyObject) {
alarmTime = NSDate()
if (!timer.isValid) {
let Selector : Selector = #selector(ViewController.updateTime)
timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: Selector, userInfo: nil, repeats: true)
startTime = NSDate.timeIntervalSinceReferenceDate
}
}
func stop(sender: AnyObject) {
endTime = NSDate()
saveSleepAnalysis()
retrieveSleepAnalysis()
timer.invalidate()
}
func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
}
Replace your code with this
import UIKit
import HealthKit
let healthStore = HKHealthStore()
class ViewController: UIViewController {
#IBOutlet var displayTimeLabel: UILabel!
var startTime = TimeInterval()
var timer:Timer = Timer()
var endTime: NSDate!
var alarmTime: NSDate!
func saveSleepAnalysis() {
//1. startTime(alarmTime) and endTime are NSDate Objects//
if let sleepType = HKObjectType.categoryType(forIdentifier: HKCategoryTypeIdentifier.sleepAnalysis) {
//we create a new object that we want to add into our Health app(This is our INBED object)//
let object1 = HKCategorySample(type:sleepType, value: HKCategoryValueSleepAnalysis.inBed.rawValue, start: self.alarmTime as Date, end: self.endTime as Date)
// Time to save the object//
healthStore.save(object1, withCompletion: { (success, errpr) -> Void in
if errpr != nil
{
return
}
if success {
print("My new data was saved in HealthKit")
} else {
//something happened again//
}
})
//This our ASLEEP object//
let object2 = HKCategorySample(type:sleepType, value: HKCategoryValueSleepAnalysis.asleep.rawValue, start: self.alarmTime as Date, end: self.endTime as Date)
//now we save our objects to our mainLibrary known as HealthStore
healthStore.save(object2, withCompletion: { (success, error) -> Void in
if error != nil {
//Something went wrong//
return
if success {
print("My new data (2: Asleep data) was saved into HealthKit")
} else {
//something happened again//
}
}
}
)}
}
func retrieveSleepAnalysis() {
//first, define our object type that we watn again in BOOLEAN FORMAT//
if let sleepType = HKObjectType.categoryType(forIdentifier: HKCategoryTypeIdentifier.sleepAnalysis) {
//use sortDescriptor to get teh recent data first: so from MostRecentData to PastData//
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false)
//we create our query with a block completion to execute
let query = HKSampleQuery(sampleType: sleepType, predicate: nil, limit:30, sortDescriptors: [sortDescriptor]) { (query, tmpResult, error) -> Void in
if error != nil {
//something happends//
return
}
if let result = tmpResult {
//then i want the computer to do something with my data//
for item in result {
if let sample = item as? HKCategorySample {
let value = (sample.value == HKCategoryValueSleepAnalysis.inBed.rawValue) ? "InBed" : "Asleep"
print("Healthkit sleep: \(sample.startDate) \(sample.endDate) = value: \(value)")
}
}
}
}
//finally, we execute our query: Print out our output file //
healthStore.execute(query)
}
}
override func viewDidLoad() {
super.viewDidLoad()
let typestoRead = Set([
HKObjectType.categoryType(forIdentifier: HKCategoryTypeIdentifier.sleepAnalysis)!
])
let typestoShare = Set([
HKObjectType.categoryType(forIdentifier: HKCategoryTypeIdentifier.sleepAnalysis)!
])
healthStore.requestAuthorization(toShare: typestoShare, read: typestoRead) { (success, error) -> Void in
if success == false {
NSLog(" Display not allowed")
}
}
}
func updateTime() {
let currentTime = NSDate.timeIntervalSinceReferenceDate
//Find the difference between current time and start time.
var elapsedTime: TimeInterval = currentTime - startTime
//calculate the minutes in elapsed time.
let minutes = UInt8(elapsedTime / 60.0)
elapsedTime -= (TimeInterval(minutes) * 60)
//calculate the seconds in elapsed time.
let seconds = UInt8(elapsedTime)
elapsedTime -= TimeInterval(seconds)
//find out the fraction of milliseconds to be displayed.
let fraction = UInt8(elapsedTime * 100)
//add the leading zero for minutes, seconds and millseconds and store them as string constants
let strMinutes = String(format: "%02d", minutes)
let strSeconds = String(format: "%02d", seconds)
let strFraction = String(format: "%02d", fraction)
//concatenate minuets, seconds and milliseconds as assign it to the UILabel
displayTimeLabel.text = "\(strMinutes):\(strSeconds):\(strFraction)"
}
func start(sender: AnyObject) {
alarmTime = NSDate()
if (!timer.isValid) {
let Selector : Selector = #selector(ViewController.updateTime)
timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: Selector, userInfo: nil, repeats: true)
startTime = NSDate.timeIntervalSinceReferenceDate
}
}
func stop(sender: AnyObject) {
endTime = NSDate()
saveSleepAnalysis()
retrieveSleepAnalysis()
timer.invalidate()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}

Get valid Float value to use in AVPlayer from CMTime

Im trying to set a slider to the current value of an audio file which is playing. But I'm getting problems when setting the values for the slider.
when i use a timer from ViewDidLoad:
self.timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(PlayerViewController.audioSliderUpdate), userInfo: nil, repeats: true)
which calls my function:
func audioSliderUpdate() {
var currentTime : CMTime = (self.audioPlayerItem?.currentTime())!
var seconds : Float64 = CMTimeGetSeconds(currentTime)
var time : Float = Float(seconds)
self.audioSlider.value = time
}
The audio track starts skipping and the slider is jumping about and it returns this to the console:
[aqme] 255: AQDefaultDevice (173): skipping input stream 0 0 0x0
and when i try set the max value with this code:
let duration : CMTime = (self.audioPlayerItem?.duration)!
let seconds : Float64 = CMTimeGetSeconds(duration)
let maxTime : Float = Float(seconds)
self.audioSlider.valueMaximum = maxTime
i get this error:
<Error>: Error: this application, or a library it uses, has passed an invalid numeric value (NaN, or not-a-number) to CoreGraphics API and this value is being ignored. Please fix this problem.
Jan 20 07:24:23 ParseStarterProject-Swift[2441] <Error>: If you want to see the backtrace, please set CG_NUMERICS_SHOW_BACKTRACE environmental variable.
I've read all over SO and they all have simple solutions like
let floatTime = Float(player.currentTime)
which don't work. as you cant convert CMT directly to float in swift 3????
So my question is how do i set the values for the slider?
this is the entire class to shed light if my explanation was unclear:
import UIKit
import Parse
import AVFoundation
import AVKit
class PlayerViewController: UIViewController, AVAudioPlayerDelegate {
#IBOutlet var audioSlider: MTCircularSlider!
// passed objectID from PackViewController
var selectedAudio: String!
var audioPlayer: AVPlayer?
var audioPlayerItem: AVPlayerItem?
fileprivate var timer: Timer?
fileprivate var direction: Float = 0.01
// query parse to get the file and stream it
func getAudio() {
let query = PFQuery(className: "Part")
query.whereKey("objectId", equalTo: selectedAudio)
query.getFirstObjectInBackground { (object, error) in
if error != nil || object == nil {
print("The getFirstObject request failed.")
} else {
print("There is an object now get the Audio. ")
if let audioFileURL = (object?.object(forKey: "partAudio") as! PFFile).url {
// create an item for use by notification center
self.audioPlayerItem = AVPlayerItem(url: NSURL(string: audioFileURL) as! URL)
self.audioPlayer = AVPlayer(playerItem: self.audioPlayerItem)
self.audioPlayer?.play()
var duration : CMTime = (self.audioPlayerItem?.duration)!
var seconds : Float64 = CMTimeGetSeconds(duration)
var maxTime : Float = Float(seconds)
self.audioSlider.valueMaximum = maxTime
self.timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(PlayerViewController.audioSliderUpdate), userInfo: nil, repeats: true)
}
}
}
}
#IBOutlet var playerButton: UIButton!
func playerButtonTapped() {
// if the player is not playing
if audioPlayer?.rate == 0 {
audioPlayer?.play()
self.playerButton.setImage(UIImage(named: "play"), for: UIControlState.normal)
} else {
audioPlayer?.pause()
self.playerButton.setImage(UIImage(named: "pause"), for: UIControlState.normal)
}
}
func finishedPlaying (myNotification: NSNotification) {
self.playerButton.setImage(UIImage(named: "play"), for: UIControlState.normal)
let stoppedPlayerItem: AVPlayerItem = myNotification.object as! AVPlayerItem
//create a CMTime for zero seconds so we can go back to the beginning
let seconds : Int64 = 0
let preferredTimeScale : Int32 = 1
let seekTime : CMTime = CMTimeMake(seconds, preferredTimeScale)
stoppedPlayerItem.seek(to: seekTime)
}
override func viewDidLoad() {
super.viewDidLoad()
self.playerButton.addTarget(self, action: #selector(PlayerViewController.playerButtonTapped), for: UIControlEvents.touchUpInside)
getAudio()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(audioPlayerItem as Any, selector: #selector(PlayerViewController.finishedPlaying), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: audioPlayer)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillAppear(animated)
// remove the observer when leaving page
NotificationCenter.default.removeObserver(audioPlayer?.currentItem! as Any)
}
#IBAction func audioSliderActioned(_ sender: Any) {
audioPlayer?.pause()
//audioPlayer.currentTime() = TimeInterval(audioSlider.value)
//audioPlayer.prepareToPlay()
audioPlayer?.play()
}
func audioSliderUpdate() {
var currentTime : CMTime = (self.audioPlayerItem?.currentTime())!
var seconds : Float64 = CMTimeGetSeconds(currentTime)
var time : Float = Float(seconds)
self.audioSlider.value = time
}
}

Resources