Post

Replies

Boosts

Views

Activity

Reply to AVAudioEngine Hangs/Locks Apps After Call to -connect:to:format:
The documentation states that playerTimeForNodeTime: will return nil if the player node is not playing. Not sure if I can call the tap block or if it's possible to run into the same issue (like reading isRunning on AVAudioEngine on the main thread and playerNode.isPlaying on another thread) I was thinking of just using my own atomic bool and setting it when I play/pause the player node. Then in the tap block, just read that BOOL instead of reading from playerNode directly. But I imagine it would be possible for the system to stop the player node. And my flag could be out of sync with the true playerNode.isPlaying property. Is there a notification for such an event? So many questions.
Topic: App & System Services SubTopic: Core OS Tags:
Dec ’24
Reply to AVAudioEngine Hangs/Locks Apps After Call to -connect:to:format:
So ideally (at least in my case) is that I could just use the when parameter passed to the tap block to figure out where in the audio I'm at, but the when needs to be converted to player time in order to get a time that makes sense (I need to determine my time relative to the entire audio buffer I scheduled on the player node, not the buffer of the tap block). Also when the playerNode is paused, the when parameter doesn't factor the paused time in. The tap block continues to fire while the player node is paused and the when time doesn't know anything about paused/stopped so any UI synchronization you do will just jump all over the place once you start pausing/resuming. -playerTimeForNodeTime: does know about all this, but.... I don't think it's safe to call any of the audio engine/player node APIs in the tap block without risking a deadlock. If I'm wrong about all this I'd be grateful for an education. The documentation seems a bit scarce, and the dev forums have been pretty quiet lately. What I came up with now is to just sync my own atomic_bool with my calls that pause/play the player node. Read the atomic bool in the tap block instead of if (!playerNode.isPlaying) { return; } Then to account for the node time/ player time situation.. every time I schedule a buffer, I reset a counter to 0 that's synchronized with the tap block. In the tap block I increment it on each invocation to compute sample time relative to the entire audio buffer. I only schedule one buffer at a time (for now). I suppose I would need to figure out a good place to reset the counter to 0 if I scheduled two buffers at the same time. If the system ever stops my player node (on error or something) my tap block could be out of sync since my flag I use to track playerNode.isPlaying is not binded to 'the truth' but it's better than deadlocking. If there is a cleaner way to achieve this, I'm all ears.
Topic: App & System Services SubTopic: Core OS Tags:
Dec ’24
Reply to Whats the Appkit equivalent of SwiftUI's NavigationSplitView?
I don't think it's hard to do this in AppKit. It's just different. I haven't done this in awhile. But I'm pretty sure you could use NSSplitViewController. @interface NSSplitViewController (NSSplitViewControllerToggleSidebarAction) /// Animatedly collapses or uncollapses the first sidebar split view item in the receiver. Does nothing if the receiver does not contain any sidebars. - (IBAction)toggleSidebar:(nullable id)sender API_AVAILABLE(macos(10.11)); /// Animatedly collapses or uncollapses the first inspector split view item in the receiver. Does nothing if the receiver does not contain any inspectors. - (IBAction)toggleInspector:(nullable id)sender API_AVAILABLE(macos(14.0)); @end To get the toggle sidebar button in the toolbar you need to build an NSToolbar on your window. You could do this in Interface Builder or programmatically. Add a toolbar item to the toolbar with the following identifier: // A standard item that is configured to send -toggleSidebar: to the firstResponder when invoked. APPKIT_EXTERN NSToolbarItemIdentifier NSToolbarToggleSidebarItemIdentifier You might find this sample code helpful. https://developer.apple.com/documentation/appkit/integrating-a-toolbar-and-touch-bar-into-your-app?language=objc
Topic: UI Frameworks SubTopic: AppKit Tags:
Dec ’24
Reply to Populating Now Playing with Objective-C
Never used MPNowPlayingSession (I set up info/remote command center directly) but... Maybe try setting .automaticallyPublishesNowPlayingInfo = YES; and try not setting up nowPlayingInfoCenter at all...to tell the session to get metadata from AVPlayer. If that's not what you want or doesn't work.... Try using: MPNowPlayingInfoCenter *infoCenter = self.nowPlayingSession.nowPlayingInfoCenter Instead of +defaultCenter (may be the same object, but seems cleaner to do it this way). Also I think you may need to configure the MPRemoteCommandCenter. MPRemoteCommandCenter *remoteCommandCenter = self.nowPlayingSession.remoteCommandCenter; id playPauseTarget = [remoteCommandCenter.togglePlayPauseCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent *_Nonnull event) { // implement }]; When not using a "session" and just populating the info I believe it is required you configure the remote command center as well (add play/pause etc.) to get your now playing info to show up.
Topic: Media Technologies SubTopic: Audio Tags:
Feb ’25
Reply to MPRemoteCommandCenter not updating play/pause button to proper state on iOS
After fiddling with this some more I noticed that -When I deactivate the audio session after pausing the player node (and get AVAudioSessionErrorCodeIsBusy but the play/pause button in the 'now playing center' does update its image properly)...this has a bad consequence when I resume audio. When I resume the player node I don't hear any playback. My code looks something like this: -(void)resume { [self runOperationToActivateAudioSession]; [self.playerNode play]; } So after the calls -pause (which gives AVAudioSessionErrorCodeIsBusy) and then -resume... when resuming the player node just skips to the end of the buffer instead of resuming playback (I don't hear any audio). Now if I go back to the pause method and comment out the method call to deactivate the audio session resuming playback works as expected. So it seems deactivating the audio session on pause has this side effect on resume. But if I don't deactivate the session the 'Now Playing' center shows the wrong image for the play/pause button.
Topic: Media Technologies SubTopic: Audio Tags:
Feb ’25
Reply to MPRemoteCommandCenter not updating play/pause button to proper state on iOS
I tried pausing the player node and the audio engine: -(void)pause { [self.playerNode pause]; [self.audioEngine pause]; [self runOperationToDeactivateAudioSession]; // This does nothing on iOS: MPNowPlayingInfoCenter *nowPlayingCenter = [MPNowPlayingInfoCenter defaultCenter]; nowPlayingCenter.playbackState = MPNowPlayingPlaybackStatePaused; } The good news? My AVAudioSession -setActive:error: call to deactivate the session no longer gives me a AVAudioSessionErrorCodeIsBusy error. The bad news? When resuming playback, it doesn't play any audio still. The rest of the buffer at the time of the call to -pause is just skipped. No errors reported
Topic: Media Technologies SubTopic: Audio Tags:
Feb ’25
Reply to MPRemoteCommandCenter not updating play/pause button to proper state on iOS
So I think the playback time issue (audio being skipped) when I pause both the playerNode and the audio engine is that when resuming the playback...I call a helper method that does something like this: -(void)resume { if (!self.playerNode.isPlaying) { [self _startAudioEngineIfNotRunningAndConnectPlayerNodeToMainMixer]; } else { // Already playing. } } Which has a check like -(BOOL)_startAudioEngineIfNotRunningAndConnectPlayerNodeToMainMixer if (!self.audioEngine.isRunning) { // connect the player node to the main mixer node and start the engine.. //code here... } else { // engine already running... return YES; } If I modify the _startAudioEngineIfNotRunningAndConnectPlayerNodeToMainMixer to include this check: NSArray<AVAudioConnectionPoint *> *connections = [engine outputConnectionPointsForNode:playerNode outputBus:0]; if (connections.count > 0) { // player node already connected... only need to start the engine } else{ // connect the player node... } Playback position isn't lost on resume. So that's good. When I deactivated the audio session and got that AVAudioSessionErrorCodeIsBusy error perhaps the system paused the audio session and returned that error to tell me about my "incorrect API use." In regards to my initial issue regarding the MPRemoteCommandCenter I think what I'm going to do is this: Only pause the player node in my -pause method and keep the audio session running if my app is in the foreground. When/if my app is moved to the background if my player node is paused I'll pause the audio engine as well and deactivate the audio session for the sake of getting the 'now playing center' to update the toggle play-pause button image. Since this is async and in a background task I have to hope it runs and updates the now playing center button before the user can see it I'm still thinking about this: I don't think this is currently possible...but I'm not sure if there is ever a situation where my app can be on screen at the same time as the "Now Playing Center" in a multi-window situation. If so the play-pause toggle button will have the wrong image until my app is backgrounded. So there is that, if that's even possible but I don't think I have any other options? -- Perhaps my thinking is naive but I think this could be a lot easier if the system just read the MPNowPlayingInfoCenter.playbackState property on iOS instead of inferring it from AVAudioSession. If I don't own the now playing center who cares what I set the .playbackState property to? Feels like the system is in a better position to make a decision of whether or not my audio session should be deactivated than I am. But I have to explicitly deactivate my audio session when my app is backgrounded every time (if my audio is paused).
Topic: Media Technologies SubTopic: Audio Tags:
Feb ’25
Reply to MPRemoteCommandCenter not updating play/pause button to proper state on iOS
So instead of deactivating the audio session in my pause method I just leave it active and wait this: // Listen for UIApplicationWillResignActiveNotification -(void)appWillResignActive:(NSNotification*)notification { // player node paused? if (self.amIPaused) // { // Then we must audio engine pause. [self.audioEngine pause]; // Then we must deactivate the audio session. [self runOperationToDeactivateAudioSession]; } }
Topic: Media Technologies SubTopic: Audio Tags:
Feb ’25
Reply to App Group, Shared User Defaults, Finder Sync Extension Not getting user defaults.
Meh. Somehow I must've done something (or there is some Xcode bug) but I got Xcode to generate another .entitlement file for the extension without realizing it.....it was called something like: MyEntitlement.release.entitlements....and it was set to Release mode. The group container ID was only getting added to the release mode entitlement file, not the .entitlement file being used in debug mode. Fixed that.
Topic: UI Frameworks SubTopic: AppKit Tags:
May ’25
Reply to Does INIntent no longer work on macOS? Can't get shortcut to show up in Shortcuts app
So I got one to show up..not sure how. I temporarily added a parameter (but my shortcut shouldn't take any parameters). I also tried adding it via: [[INVoiceShortcutCenter sharedCenter] setShortcutSuggestions:@[shortCut1]]; So I'm not sure if adding the parameter or using -setShortcutSuggestions: did it. Now I removed the parameter in the .intentdefinition. AND I cleared INVoiceShortcutCenter like so: [[INVoiceShortcutCenter sharedCenter] setShortcutSuggestions:@[]]; // empty array And rebuilt the app. And now I'm having the problem in reverse (it remains in Shortcuts app list and I can't get it to be removed). Also added a second shortcut (I need two)...and the first one stubbornly won't go away and the second one won't show up....
May ’25