Play songs by album/artist on iTunes with Scripting Bridge/Obj-C - objective-c

Given a name of an album or artist I would like to tell iTunes to play all songs by that artist or on that album. How can I do this? I know how to get all the songs to play by filtering an array with all the tracks, but how do I tell iTunes to play only those I want?
EDIT
I know I can use this code to get all the tracks I want to play, but I have no idea how to tell iTunes to play them in sucession. Any ideas?
// Get the app
iTunesApplication* iTunes = [SBApplication applicationWithBundleIdentifier: #"com.apple.iTunes"];
// Get the library
iTunesSource* library;
for (iTunesSource* thisSource in [iTunes sources]) {
if ([thisSource kind] == iTunesESrcLibrary) {
library = thisSource;
break;
}
}
SBElementArray* tracks;
for (iTunesPlaylist* playlist in [library playlists]) {
if ([playlist specialKind] == iTunesESpKMusic) {
tracks = [playlist searchFor: name only: type == 0 ? iTunesESrAAlbums : iTunesESrAArtists];
}
}
// There. Now what? how do I play all the tracks in 'tracks'?

You can play one songs.
iTunesTrack *track = [tracks objectAtIndex:0];
[track playOnce:YES];
If you want play several songs, you should create or use a playlist:
for (iTunesUserPlaylist *thisList in playlists) {
if ([[thisList name] isEqualToString:playlistName]) {
playlist = thisList;
break;
}
}
[track duplicateTo:playlist];
[playlist playOnce:YES];

So it seems iTunes does not have an AppleScript/Scripting Bridge command to play songs by a certain artist/on a certain album. And according to this question, it's not possible to tell iTunes to play a custom list of tracks without creating a new playlist (which I don't want to do). In short, there's really no way to solve this problem...

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) {
}

Brief stop/start of Now Playing item when updating a media collection queue MPMediaItemCollection

Im playing about with apples addMusic sample app in xcode, as im looking to be able to add similar functionality (playing a queue of music selected by the user in the background of the app).
It does this annoying thing where the music player briefly stops before playing again when the user hits the 'Done' button after picking their song selections in the built in MediaPicker.
- I'm assuming this is because of the method apple used, which applies a new array as the queue, and resets the now playing state as to where it left off (like so:)
// apply the new media item collection as a playback queue for the music player
[self setUserMediaItemCollection: mediaItemCollection];
[musicPlayer setQueueWithItemCollection: userMediaItemCollection];
[self setPlayedMusicOnce: YES];
[musicPlayer play];
// Obtain the music player's state so it can then be
// restored after updating the playback queue.
} else {
// Take note of whether or not the music player is playing. If it is
// it needs to be started again at the end of this method.
BOOL wasPlaying = NO;
if (musicPlayer.playbackState == MPMusicPlaybackStatePlaying) {
wasPlaying = YES;
}
// Save the now-playing item and its current playback time.
MPMediaItem *nowPlayingItem = musicPlayer.nowPlayingItem;
NSTimeInterval currentPlaybackTime = musicPlayer.currentPlaybackTime;
// Combine the previously-existing media item collection with the new one
NSMutableArray *combinedMediaItems = [[userMediaItemCollection items] mutableCopy];
NSArray *newMediaItems = [mediaItemCollection items];
[combinedMediaItems addObjectsFromArray: newMediaItems];
[self setUserMediaItemCollection: [MPMediaItemCollection collectionWithItems: (NSArray *) combinedMediaItems]];
// Apply the new media item collection as a playback queue for the music player.
[musicPlayer setQueueWithItemCollection: userMediaItemCollection];
// Restore the now-playing item and its current playback time.
musicPlayer.nowPlayingItem = nowPlayingItem;
musicPlayer.currentPlaybackTime = currentPlaybackTime;
// If the music player was playing, get it playing again.
if (wasPlaying) {
[musicPlayer play];
}
}
}
}
// If the music player was paused, leave it paused. If it was playing, it will continue to
// play on its own. The music player state is "stopped" only if the previous list of songs
// had finished or if this is the first time the user has chosen songs after app
// launch--in which case, invoke play.
- (void) restorePlaybackState {
if (musicPlayer.playbackState == MPMusicPlaybackStateStopped && userMediaItemCollection) {
if (playedMusicOnce == NO) {
[self setPlayedMusicOnce: YES];
[musicPlayer play];
}
}
}
Anyone experienced this issue? Is there any alternative/more efficient methods that anyone can think of?
Thanks in advance for sharing :)
Simply apply the changes to MPMediaItemCollection at the point of Music Player changed notification.

Play specific title in iTunes via ScriptingBridge

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.

Getting name of track that is currently being played

Is there a way to get the name of the track that is currently being played in iTunes on an iOS device?
I haven't found anything too useful inMPMusicPlayerController nor AVAudioPlayer.
Thanks!
Alright, after some more searching, I found the answer hidden in this semi-related question: Get album artwork from MP3 file/ID3 tag
There are two properties that exist in MPMusicPlayerController that provides us with the Track Name and Track Artist. They are MPMediaItemPropertyTitle and MPMediaItemPropertyArtist, respectively.
The accepted answer's answer contains 5x the code you need to get what you're after. Here's a simpler answer:
if ([MPMusicPlayerController iPodMusicPlayer].playbackState == MPMusicPlaybackStatePlaying) {
MPMediaItem *item = [[MPMusicPlayerController iPodMusicPlayer] nowPlayingItem];
NSString *title = [item valueForProperty:MPMediaItemPropertyTitle];
NSString *artist = [item valueForProperty:MPMediaItemPropertyArtist]; // common
// do something with these
} else {
// nothing is playing!
}