'UILabels and UISlider for song time not updating in Mac Catalyst music player
I recently built a simple music app that does fine on my deactivated iPhone. I have since bought a Mac mini so I could run it natively on that platform with Mac Catalyst. So far it has adapted well, but I am stymied by UILabels and UISlider for elapsed and remaining playback times that do not update. I researched the problem for the last week here, but no solution has yet worked. These include calling string variables upon view controller activation and making the labels update on the main thread.
I call up a timer in viewDidLoad
:
timer2 = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(self.tick), userInfo: nil, repeats: true)
The tick function presents a clock label that displays current time:
@objc func tick() {
let date = Date()
labelTime.text = DateFormatter.localizedString(from: date, dateStyle: .none, timeStyle: .medium)
It also controls two labels for elapsed and remaining time and a time slider:
if let curTrack = mp.nowPlayingItem {
//Call length of track in seconds
let trackDuration = curTrack.playbackDuration
//Get elapsed time by calling currentPlaybackTime
let trackElapsed = mp.currentPlaybackTime
// avoid crash
if trackElapsed.isNaN
{
return
}
// Display running time
let trackElapsedMins = Int(trackElapsed / 60)
let trackElapsedSecs = Int(trackElapsed.truncatingRemainder(dividingBy: 60))
if trackElapsedSecs < 10 {
labelElapsed.text = "\(trackElapsedMins):0\(trackElapsedSecs)"
} else {
labelElapsed.text = "\(trackElapsedMins):\(trackElapsedSecs)"
}
//Display remaining time: subtract elapsed from duration
let trackRemaining = Int(trackDuration) - Int(trackElapsed)
let trackRemainingMins = trackRemaining / 60
let trackRemainingSecs = trackRemaining % 60
if trackRemainingSecs < 10 {
labelRem.text = "\(trackRemainingMins):0\(trackRemainingSecs)"
} else {
labelRem.text = "\(trackRemainingMins):\(trackRemainingSecs)"
}
sliderTime.maximumValue = Float(trackDuration)
sliderTime.value = Float(trackElapsed)
}
The labels and slider are your standard outlets:
@IBOutlet weak var labelTime: UILabel!
@IBOutlet weak var labelElapsed: UILabel!
@IBOutlet weak var labelRem: UILabel!
@IBOutlet weak var sliderTime: UISlider!
I see the function working because the clock faithfully counts seconds, and all the variables contain data when I insert breakpoints, but the labels and slider only display the play times in progress in the Music app when my app starts up, and nothing more. The slider will jump to different points in the song when manipulated, but never update afterward. So my question is: what could be inhibiting these updating operations in Mac Catalyst?
Solution 1:[1]
For what it’s worth, I’m now certain that the problem lies not with Catalyst but the sorely lacking Music app API, which only seems to call up correct currentPlaybackTime
on initialization or pause, otherwise its value reverts to 0. I installed the app on my active iPhone running iOS 14 and it has the same problem. Maybe this will get fixed in a future release, but given the complaints here and elsewhere about similar problems going back many months now, I’m not holding my breath…
UPDATE: For those in similar straits who found this post seeking a solution, here’s my workaround…
After rejecting ideas for a separate timer (too inaccurate) and an AppleScript bridge to return the value of the player position property (likely not possible in Mac Catalyst), I discovered the currentPlaybackTime
refreshed every time I hit the play button. So I built this function
@objc func playTime() -> Double {
let trackElapsed = mp.currentPlaybackTime
if isPlaying == true {
mp.play()
}
return trackElapsed
}
that sends a play
command every second while the isPlaying
variable is true, and thus returns the updated playhead position value to my labels and sliders. Problem solved…
Solution 2:[2]
Thanks for this! I'd have never guessed calling play when the musicPlayer was already playing would cause currentPlaybackTime to update.
I added this code to my existing one second timer code and it works pretty well. As you mentioned, if running on an M1 Mac, pausing in the Music app will be overridden by this, but it functions as expected in iOS.
if ((previousPlaybackTime == musicPlayer.currentPlaybackTime) &&
([musicPlayer playbackState] == MPMusicPlaybackStatePlaying))
{
NSLog(@">>>update time is same as one second ago and state is playing so try calling play to tickle currentPlaybackTime");
musicPlayer.play;
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | Dave Norfleet |