Xcode app retrieving "now playing" music title even though it's paused - xcode

I'm creating a mac app that retrieves the title of the "now playing" music on iTunes.
I'm using this code:
iTunesApplication *iTunes = [SBApplication applicationWithBundleIdentifier:#"com.apple.iTunes"];
NSString* fullTrackName = [NSString stringWithFormat:#"Now Playing: %# - %#", iTunes.currentTrack.artist, iTunes.currentTrack.name];
NSLog(#"%#", fullTrackName);
If a music is playing it works just fine, but if i pause iTunes and run the code it will automatically grab the music name, even though it's not playing.
How can i fix it?
Thank you.
And btw, i'm using iTunes.h and ScriptingBridge.framework for this.

You probably need to look for a player state property on iTunesApplication to give you the state of the current track.
Apparently it's like this:
if ([iTunes playerState] == iTunesEPlSPlaying) {
// Code if iTunes is not playing
}

Related

AppleScriptingBridge for Music app not returning sources

With the advent of Catalina OSX, iTunes has been removed from OSX, instead a Music app has been introduced, apparently similar but exclusively for music content with podcast etc.
Following code written in Objective C does not return any sources from the music, iTunes object.
Similar code worked for iTunes. but is not working for new Music app of apple.
Please advise, any experts.
MusicApplication* music = [SBApplication applicationWithBundleIdentifier:#"com.apple.music"];
NSArray *sources = [music sources];
// sources are empty, zero length array always
for (MusicSource *source in sources)
{
SBElementArray *userPlaylists = [source userPlaylists];
for(MusicUserPlaylist* playList in userPlaylists)
{ }
}

Cannot play album with AVAudioPlayer

I'm working on a very simple OSX application that will allow me to play either a song or a folder of songs.
I can choose a song and play it and everything is fine. I can choose a folder and create an array of songs and... play the last one.
var currentSong: NSURL?
var album: [NSURL] = []
var audioPlayer = AVAudioPlayer()
My playing function is
func playCurrent(){
do {
try audioPlayer = AVAudioPlayer(contentsOfURL: currentSong!)
} catch {
print("Ignoring errors for now")
}
audioPlayer.play()
}
This works fine whenever I set currentSong to a NSURL. I choose one song, it plays it.
My album function is as follows:
#IBAction func chooseAlbumAndPlay(sender: AnyObject) {
album = albumFromFile()
for song in album {
currentSong = song
playCurrent()
}
}
and here I have the problem. albumFromFile opens an NSOpenPanel, lets me choose a folder, and dumps paths to playable items into an array of NSURLs. This part works, I've verified it, so I really have an array with 12 or 20 or whatever correct, playable URLs. If I just run it as is, only the last song in any album gets played. If I set a breakpoint in the playCurrent() function, I can hear that it will actually play a tiny snippet - less than a note in most cases - of all songs but the last.
According to the documentation, play() returns a boolean - and it will happily report that it has finished playing every song in this loop.
My - human - opinion is that a song has only finished playing when I have heard all of it.
If I query the duration of the current AVAudioPlayer, they all report perfectly reasonable-sounding values.
I'm completely stumped here. PlayCurrent seems to completely fail to assert itself as a running function. The expected behaviour is that it will not exit until play() has finished playing; observed behaviour is that it will touch every song for the briefest time, go 'been there' and return to the enclosing loop.
How can I force AVAudioPlayer to play the whole of a file before exiting my playCurrent() function? And where would I have found that information? (The class documentation is unhelpful, the mentioned audio guides do not exist - right now, the Developer Library does not mention any basic audio guides for OSX.
The answer is, once you get around to it, very obvious indeed. Unfortunately, the documentation really is no help at all. I could, in theory, have found the solution by looking at the entry for AVAudioPlayer in the AVFoundationFramework reference, but I didn't. (I found it by wildly casting about and a chance mention).
There appear to be no working OSX examples; Apple's iOS example uses the MediaPlayer framework that is not available on OSX.
The solution is as follows:
An AVAudioPlayer plays a single sound.
If you want to play more than one sound, you need an AVQueuePlayer which can also play a single sound from a URL or a queue of AVPlayerItems (this class has an init(URL:) method).
If anyone has an idea why AVAudioPlayer behaves as it does, I'm still interested to hear it.

Close and launch with different command line parameters/arguments

I am running a .app and I need to "restart" it so to speak. Basically I need to tell it to close, then after closing, it should launch the path i tell (which is to itself) with some command line arguments. Is this possible with cocoa? Im getting stuck at the part where my app is closing then closed, then I cant get it back.
My code is in js-ctypes, but here is the objc pseudo code:
default_center = [NSDistributedNotificationCenter defaultCenter];
shared_workspace = [NSWorkspace sharedWorkspace];
notification_center = [[NSWorkspace sharedWorkspace] notificationCenter];
[notification_center addObserver:selector:name:object: ***, ***, NSWorkspaceDidLaunchApplicationNotification, NIL]
And in my observr when it responds with completion of quit it has code to launch. But as my app is closed it never gets to the observer responder.
Here
Thanks
You didn't mention any reason that you cannot launch a second instance of your app from the first instance, rather than the chicken & egg approach of trying to restart after you've quit... I have this code in my AppWillTerminate function where I have a situation like yours:
[[NSWorkspace sharedWorkspace] launchApplicationAtURL:appUrl options:NSWorkspaceLaunchNewInstance configuration:nil error:&error];
( To get the AppWillTerminate to be called in the first place, I had to disableSuddenTermination before calling [app quit] )
There's also some flag in the app's plist file like "allow multiple instance" or something.
Also, KNOW THIS: if your app is sandboxed, this will not work UNLESS it is code signed with an AppleStore given id, or with a Developer ID Application id. Also, it won't work on X.7 no matter what, when sandboxed.
METHOD TWO,
is to create a "Helper App". Your KillerApp goes through the Quit process, and right before it dies, it launches "HelperApp", which is a tiny command line tool which waits for KillerApp to really die, then relaunches it.
In XCode, the code for the HelperApp, a "Command line tool", is like this:
#import <Cocoa/Cocoa.h>
int main( int argc , char *argv[] ) {
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
pid_t parentPID = atoi(argv[2]);
ProcessSerialNumber psn;
while ( GetProcessForPID(parentPID, &psn) != procNotFound )
sleep(1);
NSString* appPath = [NSString stringWithCString:argv[1] encoding:NSUTF8StringEncoding];
BOOL success = [[NSWorkspace sharedWorkspace] openFile:[appPath stringByExpandingTildeInPath]];
if ( ! success )
NSLog(#"Error: could not relaunch application at %#", appPath);
[pool drain];
return (success) ? 0 : 1;
}
As you can see, you call the HelperApp with a couple parameters from your KillerApp... And in the case where you don't need to be sandboxed, that's about it.
If you DO need sandboxing, then it gets more complicated, of course. You need to create a "privileged helper tool", and thank goodness there is sample code for it.
"SMJobBless" is the Apple sample code project which outlines how to do this, but it's kind of weird-- it doesn't actually DO anything. Thankfully, somebody took that project and created "SMJobBlessXPC" from it, which really does finish the job, ( and when you get it working, your KillerApp can actually communicate with your HelperApp ). The downside is that you need to exactly maintain the plist files of the two apps in terms of code signing.

How can I be notified that a movie has been determined not to be a movie?

I'm writing a Mac app that plays MPEG-4 movies using AVPlayer.
My app supports both local movies and movies from the internet, via an “Open Location” dialog. In the latter case, when the user enters a URL and presses the OK button, my app opens the URL as a document.
Local files are easy—Launch Services will tell Finder, Dock, etc. not to light my app up for any local file that isn't a .mp4 or similar. If the user forces my app to open some random file (e.g., by holding down ⌘ when dragging it onto my app), that's their own fault.
Non-local (e.g., internet) URLs are the problem. If I supply a URL that doesn't lead to an MPEG-4, I need to show an alert and close the document.
Of course, I'm not downloading the URL myself; I just hand it to AVPlayer.
So I need a way to be notified by my AVPlayer that what I've given it is not a movie.
I've tried observing the player's status. That gets set to AVPlayerStatusReadyToPlay.
I've tried observing the player's currentItem's tracks. At least on Lion, for an invalid movie, the item's tracks never changes. (This stands to reason—the only reason it would change would be tracks coming in, which is, by definition, impossible for a non-movie.)
I can't just check tracks when status changes, because that may not be set yet (on Mavericks, status changes before tracks).
So, how can I be notified by my AVPlayer that it has conclusively determined that what the URL refers to is not anything it can play?
It's absolutely not definitive, but in my experience, when the player's status becomes AVPlayerStatusReadyToPlay, you can inspect the playable property of asset of the player's currentItem. If playable is YES, you can assume the URL led to a viable MP4. If not, you can assume it did not.
- (void) observeValueForKeyPath:(NSString*)keyPath
ofObject:(id)object
change:(NSDictionary*)change
context:(void*)context
{
if( [keyPath isEqualToString:#"status"] ) {
if( self.player.status == AVPlayerStatusReadyToPlay &&
self.player.currentItem.asset.playable ) {
[self.playerView.player play];
}
else {
// TODO: show an error dialog here.
[self.window close];
}
}
}
I've code similar to the above in one of my apps, and it seems to work as expected in every case I've tried. I can whip up a test app with it if you like.

Change the wallpaper on all desktops in OS X 10.7 Lion?

I would like to change the wallpaper of all desktops (formerly "spaces") on a screen. As of OS X 10.6 there is a category to NSWorkspace which allows the setting of the wallpaper, however, when I use this function only the wallpaper of the current desktop gets changed and all the other desktops remain unchanged.
I then looked at the desktop preferences plist and wrote a class that modifies it to reflect the changes I want (basically set a new image file path). After the new file was saved I sent the com.apple.desktop "BackgroundChanged" notification - Google if you don't know what I am talking about, this was how people changed wallpapers in pre 10.6 days. At first this didn't produce any result, so instead of "nil" as userInfo dictionary I sent the exact same userInfo dictionary along as Apple does when you change the wallpaper in your settings (subscribe to the notification in an app and change the wallpaper in the settings app and you will see what it looks like). Luck helped me here, when I sent the notification this way for some reason the Dock crashed and when it reloaded, it loaded the settings from the preferences file thus displaying my changes.
This works on 10.7.1, however, I would a) rather not have the bad user experience of the dock crashing and reloading, and b) use a path that is more or less guaranteed to work in future releases as well. Exploiting a bug doesn't seem like a stable path.
Any other ideas on how to change the wallpaper of all desktops? I am also unsure whether the current behaviour of the NSWorkspace wallpaper category is intended or a bug, however, judging from the behaviour of the wallpaper preferences pane it seems that the former is the case.
There is no api for setting the same wallpaper to all screens or all spaces, NSWorkspace setDesktopImageURL it is implemented as such that it only sets the wallpaper for the current space on the current screen, this is how System Preferences does it too.
Besides the volatile method of manually modifying the ~/Library/Preferences/com.apple.desktop.plist (format could change) and using notifications to reload it (crashes you experienced) what you can do is set the wallpaper to spaces as the user switches to it , e.g. look for NSWorkspaceActiveSpaceDidChangeNotification (if your application is not always running you could tell the user to switch to all spaces he wants the wallpaper to apply to) , arguably these methods are not ideal but at least they are not volatile.
-(void)setWallpaper
{
NSWorkspace *sws = [NSWorkspace sharedWorkspace];
NSURL *image = [NSURL fileURLWithPath:#"/Library/Desktop Pictures/Andromeda Galaxy.jpg"];
NSError *err = nil;
for (NSScreen *screen in [NSScreen screens]) {
NSDictionary *opt = [sws desktopImageOptionsForScreen:screen];
[sws setDesktopImageURL:image forScreen:screen options:opt error:&err];
if (err) {
NSLog(#"%#",[err localizedDescription]);
}else{
NSNumber *scr = [[screen deviceDescription] objectForKey:#"NSScreenNumber"];
NSLog(#"Set %# for space %i on screen %#",[image path],[self spaceNumber],scr);
}
}
}
-(int)spaceNumber
{
CFArrayRef windowsInSpace = CGWindowListCopyWindowInfo(kCGWindowListOptionAll | kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
for (NSMutableDictionary *thisWindow in (NSArray *)windowsInSpace) {
if ([thisWindow objectForKey:(id)kCGWindowWorkspace]){
return [[thisWindow objectForKey:(id)kCGWindowWorkspace] intValue];
}
}
return -1;
}

Resources