Play specific title in iTunes via ScriptingBridge - objective-c

I'm trying to write an application that interacts with iTunes via ScriptingBridge. I works well so far, but the options of this method seem to be very limited.
I want to play song with a given name, but it looks like there's no way to do this. I haven't found anything similar in iTunes.h…
In AppleScript it's just three lines of code:
tell application "iTunes"
play (some file track whose name is "Yesterday")
end tell
And then iTunes starts to play a classic Beatles song.
Is there any was I can do this with ScriptingBridge or do I have to run this AppleScript from my app?

It's not as simple as the AppleScript version, but it's certainly possible.
Method one
Get a pointer to the iTunes library:
iTunesApplication *iTunesApp = [SBApplication applicationWithBundleIdentifier:#"com.apple.iTunes"];
SBElementArray *iTunesSources = [iTunesApp sources];
iTunesSource *library;
for (iTunesSource *thisSource in iTunesSources) {
if ([thisSource kind] == iTunesESrcLibrary) {
library = thisSource;
break;
}
}
Get an array containing all the audio file tracks in the library:
SBElementArray *libraryPlaylists = [library libraryPlaylists];
iTunesLibraryPlaylist *libraryPlaylist = [libraryPlaylists objectAtIndex:0];
SBElementArray *musicTracks = [self.libraryPlaylist fileTracks];
Then filter the array to find tracks with the title you're looking for.
NSArray *tracksWithOurTitle = [musicTracks filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"%K == %#", #"name", #"Yesterday"]];
// Remember, there might be several tracks with that title; you need to figure out how to find the one you want.
iTunesTrack *rightTrack = [tracksWithOurTitle objectAtIndex:0];
[rightTrack playOnce:YES];
Method two
Get a pointer to the iTunes library as above. Then use the Scripting Bridge searchFor: only: method:
SBElementArray *tracksWithOurTitle = [library searchFor:#"Yesterday" only:kSrS];
// This returns every song whose title *contains* "Yesterday" ...
// You'll need a better way to than this to pick the one you want.
iTunesTrack *rightTrack = [tracksWithOurTitle objectAtIndex:0];
[rightTrack playOnce:YES];
Caveat to method two: The iTunes.h file incorrectly claims that the searchFor: only: method returns an iTunesTrack*, when in fact (for obvious reasons) it returns an SBElementArray*. You can edit the header file to get rid of the resulting compiler warning.

Related

Apple Music API - Create a Playlist

I have been exploring the Apple Music API to see what kind of functionality I can expect to be able to use in an iOS app. I have created a little test app that gains permission from the user and outputs the playlists I have (and songs) to NSLog.
MPMediaQuery *myPlaylistsQuery = [MPMediaQuery playlistsQuery];
[myPlaylistsQuery setGroupingType:MPMediaGroupingPlaylist];
NSArray *playlists = [myPlaylistsQuery collections];
for (MPMediaPlaylist *playlist in playlists) {
NSLog (#"%#", [playlist valueForProperty: MPMediaPlaylistPropertyName]);
NSArray *songs = [playlist items];
for (MPMediaItem *song in songs) {
NSString *songTitle =
[song valueForProperty: MPMediaItemPropertyTitle];
NSLog (#"\t\t%#", songTitle);
}
}
From this, I have been able to deduce the following (but I'm not 100% certain):
the playlist (basic info: name, id) is stored locally on the device
the playlist songs are also pulled from local storage but if the playlist hasn't been downloaded to the device it goes off to Apple to grab the song list.
So far, so good. What I want to know is:
is there a way of creating a playlist from my app (via the API)?
I know there is an MPMediaPlaylist addItem and add method but can't seem to find a way of creating the new playlist itself.
According to this page it should be possible: https://affiliate.itunes.apple.com/resources/blog/apple-music-api-faq/
Can a developer create brand new playlists on the user’s device with the Apple Music API?
Yes. The API allows develops to new create playlists on the user’s device.
I've figured this out. If you use the following code you can generate a new playlist and perform an action on it.
NSUUID *uuid = [NSUUID UUID]; //uuid for the playlist
[[MPMediaLibrary defaultMediaLibrary] getPlaylistWithUUID:uuid creationMetadata:[[MPMediaPlaylistCreationMetadata alloc] initWithName:#"YOUR PLAYLIST NAME"] completionHandler:^(MPMediaPlaylist * _Nullable playlist, NSError * _Nullable error) {
NSLog(#"%#", error);
if (!error) {
NSLog(#"All ok let's do some stuff with the playlist!");
}
}];
Apple's documentation on the whole API is severely lacking in terms of sample code and practical examples!

Create a custom MediaPickerController

I was tasked to create a music player application that is sort of like a DJ app, and when the add music button is clicked, it needs to show the list of all the songs just like the native MediaPickerController but with added functionalities like sorting, search and not fullscreen which is not available in the native one.
I tried scouring the internet for an answer but I can't find anything about this, it's all about creating the native media picker controller.
I found about MPMediaQuery which allows you to get the songs list in the phone, but I can't use it in a for-in
Sample:
MPMediaQuery *songs = [MPMediaQuery songsQuery];
for (MPMediaItem *item in songs) {
}
But I get this:
*Collection expression type 'MPMediaQuery ' may not respond to 'countByEnumeratingWithState:objects:count:'
Any suggestions?
About the mediaquery, you need to convert it to nsarray first, try this:
MPMediaQuery *songs = [MPMediaQuery songsQuery];
NSArray *songList = [songs items];
for (MPMediaItem *item in songList) {
}

Drag Files come across Sandbox(__CFPasteboardIssueSandboxExtensionForPath)

I processed drag operation from browser view to custom view.It work well in snow lepoard,but not in Mountain Lion with sandbox.
in browser view:
NSMutableArray* urls = [[[NSMutableArray alloc] init] autorelease];
..............put some NSUrl to urls array....................
[pasteboard writeObjects:[NSArray arrayWithArray:urls]];
in my receive custom view:
NSArray* pasteboardItems = [pasteboard readObjectsForClasses:[NSArray arrayWithObject:[NSString class]] options:nil];
NSArray* pasteboardItems2 = [pasteboard readObjectsForClasses:[NSArray arrayWithObject:[NSURL class]] options:nil];
NSArray* pasteboardItems3 = [pasteboard readObjectsForClasses:[NSArray arrayWithObject:[NSImage class]] options:nil];
NSLog(#"%#",pasteboardItems);
NSLog(#"%#",pasteboardItems2);
NSLog(#"%#",pasteboardItems3);
my log is:
2012-08-09 18:33:43.886 iCollage[6885:303] __CFPasteboardIssueSandboxExtensionForPath: error for [/Users/xxxx/Library/Containers/xxxxxxxxxxxx/Data/Downloads/1343902069.jpg]
2012-08-09 18:33:44.546 iCollage[6885:303] ( "file://localhost/Users/xxx/Library/Containers/xxxxxxxx/Data/Downloads/1343902069.jpg")
2012-08-09 18:33:44.547 iCollage[6885:303] ( "file://localhost/Users/xxxxx/Library/Containers/xxxxxx/Data/Downloads/1343902069.jpg")
2012-08-09 18:33:44.547 iCollage[6885:303] ()
my question is:
1.how to fix this error __CFPasteboardIssueSandboxExtensionForPath;I refer the docs and found nothing about that.I am ensuer that i have the permission to access the file!google says, may be "startAccessingSecurityScopedResource" will help me, then i try and failed
2.why pasteboardItems2 have value?i write to pasteboard only url but not string.It disgusted me that I can get the url both from NSString type and NSUrl type! (I try drag a file from iFinder, the url will only exist in pasteboardItems but not pasteboardItems2).Anybody know why? I think the first problem will auto fixed when some one help me fix this problem.
I believe Apple answer question 1:
Important: Although you can support dragging file paths, in general,
you should avoid doing so unless you are certain that the destination
app will never be run in an app sandbox. If you use an NSString, OS X
has no way to know whether that string should be interpreted as a
path; thus, OS X does not expand the destination app’s sandbox to
allow access to the file or directory at that location. Instead, use
an NSURL, a bookmark, or a filename pasteboard type.
WRT to question 2, it looks like you have pasted URLs so reading NSURL objects would seem to be correct. However I think you should implement the dragging using the following code (also from the link above):
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
{
NSPasteboard *pboard = [sender draggingPasteboard];
if ( [[pboard types] containsObject:NSFilenamesPboardType] ) {
NSArray *files = [pboard propertyListForType:NSFilenamesPboardType];
int numberOfFiles = [files count];
// Perform operation using the list of files
}
return YES;
}
You need to generate security-scoped URL bookmark data on the sender side, and turn that data back into a URL on the receiver side. There's some other stuff you have to do after that when you want to actually access the URL; the documentation elaborates.
The receiving application, when running in a sandbox, will not be able to handle bare paths. This is a core part of being sandboxed; you are not allowed to use bare paths or their corresponding URLs to access files that aren't in your sandbox container and haven't been explicitly handed to you by the user.
Your pasteboardItems read object of NSString type, but you dragged a file(with jpg extension), you should register for NSString type in your init method:
[self registerForDraggedTypes:[NSArray arrayWithObject:NSPasteboardTypeString]];
You need to have Document Types defined in your application so that the sandboxing mechanism knows your application should be opening files with those extensions. You can do this by clicking the project on the left in Xcode, and in the Info tab, under Document Types add a new document type for each extension.
You just need to fill in the name and extensions field.
Also if you want to persist your permission to access the files dragged onto your application, you can use this class to wrap up all that logic. https://github.com/leighmcculloch/AppSandboxFileAccess

Get list of installed apps on iPhone

Is there a way (some API) to get the list of installed apps on an iPhone device.
While searching for similar questions, I found some thing related to url registration, but I think there must be some API to do this, as I don't want to do any thing with the app, I just want the list.
No, apps are sandboxed and Apple-accepted APIs do not include anything that would let you do that.
You can, however, test whether a certain app is installed:
if the app is known to handle URLs of a certain type
by using [[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:#"thisapp://foo"]
You can get a list of apps and URL schemes from here.
For jailbroken devices you can use next snipped of code:
-(void)appInstalledList
{
static NSString* const path = #"/private/var/mobile/Library/Caches/com.apple.mobile.installation.plist";
NSDictionary *cacheDict = nil;
BOOL isDir = NO;
if ([[NSFileManager defaultManager] fileExistsAtPath: path isDirectory: &isDir] && !isDir)
{
cacheDict = [NSDictionary dictionaryWithContentsOfFile: path];
NSDictionary *system = [cacheDict objectForKey: #"System"]; // First check all system (jailbroken) apps
for (NSString *key in system)
{
NSLog(#"%#",key);
}
NSDictionary *user = [cacheDict objectForKey: #"User"]; // Then all the user (App Store /var/mobile/Applications) apps
for (NSString *key in user)
{
NSLog(#"%#",key);
}
return;
}
NSLog(#"can not find installed app plist");
}
for non jailbroken device, we can use third party framework which is called "ihaspp", also its free and apple accepted. Also they given good documentation how to integrate and how to use. May be this would be helpful to you. Good luck!!
https://github.com/danielamitay/iHasApp
You could do this by using the following:
Class LSApplicationWorkspace_class = objc_getClass("LSApplicationWorkspace");
SEL selector = NSSelectorFromString(#"defaultWorkspace");
NSObject* workspace = [LSApplicationWorkspace_class performSelector:selector];
SEL selectorALL = NSSelectorFromString(#"allApplications");
NSMutableArray *Allapps = [workspace performSelector:selectorALL];
NSLog(#"apps: %#", Allapps);
And then by accessing each element and splitting it you can get your app name, and even the Bundle Identifier, too.
Well, not sure if this was available back when the last answer was given or not (Prior to iOS 6)
Also this one is time intensive, yet simple:
Go into settings > Gen. >usage. The first category under usage at least right now is Storage.
It will show a partial list of apps. At the bottom of this partial list is a button that says "show all apps".
Tap that and you'll have to go through screen by screen, and take screenshots (Quick lock button and home button takes a screenshot).
I'm doing this now and I have hundreds of apps on my iPhone. So it's going to take me a while. But at least at the end of the process I'll have Images of all my apps.

How to make QTMovie play file from URL with forced (MP3) type?

I'm using QTKit to progressively download and play an MP3 from a URL. According to this documentation, this is the code I should use to accomplish that:
NSURL *mp3URL = [NSURL URLWithString:#"http://foo.com/bar.mp3"];
NSError *error = nil;
QTMovie *sound = [[QTMovie alloc] initWithURL:mp3URL error:&error];
[sound play];
This works, and does exactly what I want — the MP3 URL is lazily downloaded and starts playing immediately. However, if the URL does not have the ".mp3" path extension, it fails:
NSURL *mp3URL = [NSURL URLWithString:#"http://foo.com/bar"];
NSError *error = nil;
QTMovie *sound = [[QTMovie alloc] initWithURL:mp3URL error:&error];
[sound play];
No error is given, no exception is raised; the duration of the sound is just set to zero, and nothing plays.
The only way I have found to work around this is to force a type by loading the data manually and using a QTDataReference:
NSURL *mp3URL = [NSURL URLWithString:#"http://foo.com/bar"];
NSData *mp3Data = [NSData dataWithContentsOfURL:mp3URL];
QTDataReference *dataReference =
[QTDataReference dataReferenceWithReferenceToData:mp3Data
name:#"bar.mp3"
MIMEType:nil];
NSError *error = nil;
QTMovie *sound = [[QTMovie alloc] initWithDataReference:dataReference error:&error];
[sound play];
However, this forces me to completely download ALL of the MP3 synchronously before I can start playing it, which is obviously undesirable. Is there any way around this?
Thanks.
Edit
Actually, it seems that the path extension has nothing to do with it; the Content-Type is simply not being set in the HTTP header. Even so, the latter code works and the former does not. Anyone know of a way to fix this, without having access to the server?
Edit 2
Anyone? I can't find information about this anywhere, and Google frustratingly now shows this page as the top result for most of my queries...
Two ideas. (The first one being a bit hacky):
To work around the missing content type, you could embed a small Cocoa webserver that supplements the missing header field and route your NSURL over that "proxy".
Some Cocoa http server implementations:
http://code.google.com/p/cocoahttpserver/
http://cocoawithlove.com/2009/07/simple-extensible-http-server-in-cocoa.html
http://culturedcode.com/cocoa/
The second one would be, to switch to a lower level framework (From QTKit to AudioToolbox).
You'd need more code, but there are some very good resources out there on how to stream mp3 using AudioToolbox.
e.g.:
http://cocoawithlove.com/2008/09/streaming-and-playing-live-mp3-stream.html
Personally I'd go with the second option. AudioToolbox isn't as straightforward as QTKit but it offers a clean solution to your problem. It's also available on both - iOS and Mac OS - so you will find plenty of information.
Update:
Did you try to use another initializer? e.g.
+ (id)movieWithAttributes:(NSDictionary *)attributes error:(NSError **)errorPtr
You can insert your URL for the key QTMovieURLAttribute and maybe you can compensate the missing content type by providing other attributes in that dictionary.
This open source project has a QTMovie category that contains methods to accomplish similar things:
http://vidnik.googlecode.com/svn-history/r63/trunk/Source/Categories/QTMovie+Async.m
If you thought weichsel's first solution was hacky, you're going to love this one:
The culprit is the Content-Type header, as you have determined. Had QTKit.framework used Objective-C internally, this would be a trivial matter of overriding -[NSHTTPURLResponse allHeaderFields] with a category of your choosing. However, QTKit.framework (for better or worse) uses Core Foundation (and Core Services) internally. These are both C-based frameworks and there is no elegant way of overriding functions in C.
That said, there is a method, just not a pretty one. Function interposition is even documented by Apple, but seems to be a bit behind the times, compared to the remainder of their documentation.
In essence, you want something along the following lines:
typedef struct interpose_s {
void *new_func;
void *orig_func;
} interpose_t;
CFStringRef myCFHTTPMessageCopyHeaderFieldValue (
CFHTTPMessageRef message,
CFStringRef headerField
);
static const interpose_t interposers[] __attribute__ ((section("__DATA, __interpose"))) = {
{ (void *)myCFHTTPMessageCopyHeaderFieldValue, (void *)CFHTTPMessageCopyHeaderFieldValue }
};
CFStringRef myCFHTTPMessageCopyHeaderFieldValue (
CFHTTPMessageRef message,
CFStringRef headerField
) {
if (CFStringCompare(headerField, CFSTR("Content-Type"), 0) == kCFCompareEqualTo) {
return CFSTR("audio/x-mpeg");
} else {
return CFHTTPMessageCopyHeaderFieldValue(message, headerField);
}
}
You might want to add logic specific to your application in terms of handling the Content-Type field lest your application break in weird and wonderful ways when every HTTP request is determined to be an audio file.
Try replacing http:// with icy://.
Just create an instance like this...
QTMovie *aPlayer = [QTMovie movieWithAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
fileUrl, QTMovieURLAttribute,
[NSNumber numberWithBool:YES], QTMovieOpenForPlaybackAttribute,
/*[NSNumber numberWithBool:YES], QTMovieOpenAsyncOKAttribute,*/
nil] error:error];