MPMediaItemPropertyAssetURL returning nil - ios

I am working on a project where I need to play songs from iTunes Library in AVPlayer. For that, I am taking URL "ipod-library://item/item.mp3?id=1577682869916034242" of selected songs from iTunes Library and playing same in AVPlayer. Almost all songs get play, but for few songs MPMediaItemPropertyAssetURL returns nil URL. Also receive following error
-[AVAssetReader initWithAsset:error:] invalid parameter not satisfying: asset != ((void *)0)
Any suggestion on this? and why I am getting nil value from
MPMediaItemPropertyAssetURL
Also any idea how to stream or convert DRM Protected Media track into NSData?.
Please advice.

MPMediaItemPropertyAssetURL can returns null for two possible reason.
The music is not downloaded to your device but added in music
library only.
The music is loaded but its DRM-protected.
DRM-protected asset is not possible to play using AVPlayer, its only able to play using MPMusicPlayer. So you must need to check two things before proceed with AVPlayer.
MPMediaItemPropertyAssetURL is nil ?
MPMediaItem is protected ?
Please see the code below….
MPMediaItem *theChosenSong = [[mediaItemCollection items] firstObject];
NSURL *assetURL = [theChosenSong valueForProperty:MPMediaItemPropertyAssetURL];
if(assetURL) {
BOOL bIsProtected = theChosenSong.protectedAsset;
if(!bIsProtected) {
// Do whatever you want to do
NSLog(#"Its not protected");
}
else {
NSLog(#"Its DRM protected");
}
}
else {
NSLog(#"DRM protected or not downloaded locally");
}

I found out that the problem was the song I was trying to get the MPMediaItemPropertyAssetURL property for was actually not on my device. It was listed in the media library, but was actually still in iCloud. Once I downloaded the song to my device then the problem was solved.

Leaving this answer for other people like me.
Even though the music is on downloaded on the device, if it is DRM protected, MPMediaItem.value(forProperty: MPMediaItemPropertyAssetURL) will return nil as mentioned in the comment.
My app kept crashing and I confirmed this with my beta tester.
It seems like MPMusicPlayerController still supports playback so according to this answer.

Related

Why can't I play certain .m3u8 items with AVPlayer?

I'm working on a project where I have an instance of AVPlayer capable of playing different audio content that I retrieve from a backend, from podcast to music and streamings. Every content has two types of urls: one with mp3 and another with a m3u8 file. All the mp3 files work good. However some m3u8 files work fine and others don't. In particular, those who don't work cause the AVPlayer to crash with the error:
Error Domain=AVFoundationErrorDomain Code=-11819 "Cannot Complete Action"
UserInfo={NSLocalizedRecoverySuggestion=Try again later.,
NSLocalizedDescription=Cannot Complete Action.}
I don't understand what the problem is. According to this answer it is a wrong Manifest file, which in my case is - for example - the following:
#EXTM3U
#EXT-X-MEDIA:TYPE=AUDIO,URI="_64/index.m3u8",GROUP-ID="2#48000-64000",NAME="AAC 64",DEFAULT=NO,AUTOSELECT=NO
#EXT-X-MEDIA:TYPE=AUDIO,URI="_80/index.m3u8",GROUP-ID="2#48000-80000",NAME="AAC 80",DEFAULT=NO,AUTOSELECT=NO
#EXT-X-MEDIA:TYPE=AUDIO,URI="_96/index.m3u8",GROUP-ID="2#48000-96000",NAME="AAC 96",DEFAULT=NO,AUTOSELECT=NO
#EXT-X-STREAM-INF:BANDWIDTH=133336,CODECS="mp4a.40.2",AUDIO="2#48000-96000"
_96/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=100641,CODECS="mp4a.40.2",AUDIO="2#48000-64000"
_64/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=116989,CODECS="mp4a.40.2",AUDIO="2#48000-80000"
_80/index.m3u8
On the Apple forum, I found this answer which says iOS 14+ is on fault. Unfortunately I cannot test with an iOS 13 physical device.
Do you have any suggestion?
Tested on Xcode 13.1 with iPhone 7plus with iOS 15.0.2.
Finally I found a solution for this issue. What worked for me was this. I believe the problem was that my manifest files were structured like the following:
#EXT-X-MEDIA:TYPE=AUDIO,URI="_64/index.m3u8", GROUP-ID="1#48000-64000",NAME="Audio 64",DEFAULT=NO,AUTOSELECT=NO
In particular they had DEFAULT=NO,AUTOSELECT=NO. Therefore before calling replaceCurrentItem I now do the following:
let asset = AVAsset(url: url)
let playerItem = AVPlayerItem(asset: asset)
for characteristic in asset.availableMediaCharacteristicsWithMediaSelectionOptions {
if let group = asset.mediaSelectionGroup(forMediaCharacteristic: AVMediaCharacteristic.audible) {
if let option = group.options.first {
playerItem.select(option, in: group)
}
}
}
This makes all my HLS audio playable by the AVPlayer.
I dont see version in your .m3u8. Try adding #EXT-X-VERSION:03 into your playlist. AVPlayer does need to have version included in playlist (Android EXO player does not need it). Here is example of playlist that might work:
#EXTM3U
#EXT-X-VERSION:03
#EXT-X-MEDIA:TYPE=AUDIO,URI="_64/index.m3u8",GROUP-ID="2#48000-64000",NAME="AAC 64",DEFAULT=NO,AUTOSELECT=NO
#EXT-X-MEDIA:TYPE=AUDIO,URI="_80/index.m3u8",GROUP-ID="2#48000-80000",NAME="AAC 80",DEFAULT=NO,AUTOSELECT=NO
#EXT-X-MEDIA:TYPE=AUDIO,URI="_96/index.m3u8",GROUP-ID="2#48000-96000",NAME="AAC 96",DEFAULT=NO,AUTOSELECT=NO
#EXT-X-STREAM-INF:BANDWIDTH=133336,CODECS="mp4a.40.2",AUDIO="2#48000-96000"
_96/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=100641,CODECS="mp4a.40.2",AUDIO="2#48000-64000"
_64/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=116989,CODECS="mp4a.40.2",AUDIO="2#48000-80000"
_80/index.m3u8

Play IOS Music Library Content Using AVFoundation

I'm attempting to write an IOS app that will play my iPhone music library content, showing artwork, and "announcing" title, artist, etc.
I have it working nicely using Apple's Media Player framework. I can display Playlist names and queue the songs in a selected Playlist.
I use the "MPMusicPlayerControllerNowPlayingItemDidChange" observer notification to pause playback, retrieve metadata, and do the announcements via AVSpeechSynthesizer.
I was a happy camper until I ran into the dreaded "Media Player framwork doesn't respond to observer notifications in background" issue.
So, I started looking at the AVFoundation Framework. I found a sample that plays local song files via URLs in the background and.
I'm failing miserably in attempting to retrieve Music Library content via the AVFoundation.
I have also failed in supplying content retrieved via the Media Player framework to the AVFoundation player.
(Note: The URLs retrieved from MPMediaItem are of a bogus "ipod-library://item/item.m4a?id=#########################" format. Creating AVPlayerItem with this "URL" doesn't work.)
Has anyone managed to accomplish this? I'm developing for my own usage. I have no intention of posting the app in Apple's App Store, so I'm willing to use hidden APIs or un-Apple approved methodology.
A Swift code example would be great. (Objective-C not so much)
Having fetched an MPMediaItem from the user's library, obtain its assetURL. Creating an AVPlayer from the resulting URL does work.
Actual code from one of my example apps:
func oneSong () -> (URL?, String?) {
let query = MPMediaQuery.songs()
// always need to filter out songs that aren't present
let isPresent = MPMediaPropertyPredicate(value:false,
forProperty:MPMediaItemPropertyIsCloudItem,
comparisonType:.equalTo)
query.addFilterPredicate(isPresent)
let item = query.items?[0]
return (item?.assetURL, item?.title)
}
#IBAction func doPlayOneSongAVPlayer (_ sender: Any) {
let (url, title) = self.oneSong()
if let url = url, let title = title {
self.avplayer = AVPlayer(url:url)
self.avplayer.play()
MPNowPlayingInfoCenter.default().nowPlayingInfo = [
MPMediaItemPropertyTitle : title
]
}
}

Unable to cast music to Chromecast

I'm trying to establish a simple audio stream to a Chromecast device. This is just a POC for me to familiarize myself with the API. What I want to do is load up the user's library, select a song, and have it cast over. I've been following the integration guide quite closely but to no avail.
Please find the full project on Github here.
An overview of my code, AppDelegate.m:
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
GCKCastOptions *options = [GCKCastOptions.alloc initWithReceiverApplicationID:kGCKMediaDefaultReceiverApplicationID];
[GCKCastContext setSharedInstanceWithOptions:options];
[GCKCastContext.sharedInstance.sessionManager addListener:self];
}
UITableViewDelegate:
- (void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath
{
MPMediaItem *mediaItem = MusicManager.sharedManager[index];
NSString *path = [MPMediaLibrary.defaultMediaLibrary pathForAssetURL:mediaItem.assetURL];
GCKMediaMetadata *metadata = [GCKMediaMetadata.alloc initWithMetadataType:GCKMediaMetadataTypeMusicTrack];
[metadata setString:mediaItem.title forKey:kGCKMetadataKeyTitle];
[metadata setString:mediaItem.artist forKey:kGCKMetadataKeyArtist];
[metadata setString:mediaItem.albumTitle forKey:kGCKMetadataKeyAlbumTitle];
GCKMediaInformation *mediaInfo = [GCKMediaInformation.alloc initWithContentID:path
streamType:GCKMediaStreamTypeBuffered
contentType:[self fileMIMEType:path]
metadata:metadata
streamDuration:mediaItem.playbackDuration
customData:nil];
[GCKCastContext.sharedInstance.sessionManager.currentCastSession.remoteMediaClient loadMedia:mediaInfo autoplay:YES];
}
I initiate the casting session by tapping the GCKCastButton and Start Session, my TV shows the Cast logo, then when I tap on a specific song, my TV briefly shows the metadata (i.e. song title, artist name, etc.), and then reverts back to the Chromecast logo. On the device, if I remain on the screen presented by GCKCastButton, I can also see the details of the song that is supposed to be currently casting, but quickly changes to "No Media Selected" after a few seconds.
I've checked the file path, the MIME type, everything is correct and playable. I've even tried bundling a short MP3 and trying to cast that, but to no avail.
Can't help but feel like I'm missing something here, the integration guide doesn't really give much more info.
Any insight is appreciated. Thanks!
It seems that I was under the wrong impression that the cast library initiates a stream from the provided file itself. But instead it required an actual URL for the Chromecast device to perform a GET operation on.
I imagine this will be solved by running a server locally on the iOS device.

URLs of Media Bought on iTunes

I have created an application that gets all of the songs on a device, puts some of their information in a UITableView, then plays a song of a selected cell using an AVAudioPlayer. For some reason however, it seems as though three of the songs on my device do not have urls. These songs were more recently bought, perhaps in the last two months. See the code below.
//get all the songs
var songsList: [MPMediaItem] = MPMediaQuery.songsQuery().items!
if let url = song.valueForProperty(MPMediaItemPropertyAssetURL) as! NSURL? {
self.player = try! AVAudioPlayer(contentsOfURL: url)
let playing: Bool = player.play()
print("The audio play status: " + String(playing))
}
else {
print("Failed to cast to URL")
}
For some reason, these three songs fail the type cast from AnyObject? to NSURL? meaning that the url must not exist on the device? I do not understand. My only guess is that I bought them from iTunes on another Macintosh device and they were stored on there via some cloud transfer through my iTunes account. Any explanation would be a great help.
You can't get an asset URL if the track is
A) Not on your device -- at least not completely -- but available to download/stream on demand. This can arise by not being completely played; being purchased on other devices/via Family Sharing; being an Apple Match track; yadayadayada.
B) DRM-protected. That's getting pretty rare these days I thought, but it was a big thing back a few years. If the file has an .m4p extension, that is definitely the problem. There's reports that it arises with .m4a files as well, and showing up in iTunes as "Purchased AAC audio file" indicates that it figures you're not allowed to get at said audio.

Problem playing sound on iPad device with MonoTouch

I am using the following code to play a .CAF file in an iPad application via Monotouch:
public void PlayClick()
{
PlaySound("Media/Click.caf");
}
private void PlaySound(string soundFile)
{
//var mediaFile = NSUrl.FromFilename(soundFile);
//var audioPlayer = new SystemSound(mediaFile);
//audioPlayer.PlaySystemSound();
var audioPlayer = new SystemSound(soundFile);
if (audioPlayer != null)
{
audioPlayer.PlaySystemSound();
}
}
It works find in the simulator - in fact, all variations I've tried (SystemSound, AVAudioPlayer, etc.) appear to work ok in the simulator, but I've not gotten a version to play on a real device yet. The sound files are all marked as Content and I checked the bundle uploaded to the iPad and the files are definitely there in a subfolder named "Media". If I change the code to use SystemSound (via the constructor with Url), I get an InvalidOperationException with the details:
Could not create system sound ID for url file://localhost/private/var/mobile/Applications/AC24496E-12E9-4690-B154-BA1AD1123EDC/Sample.app/Media/Click.caf; error=SystemSoundUnspecified
Anyone know what am I doing wrong? Thanks for any pointers to get me past this issue!
The simulator is case-aware, but the actual device is case-sensitive. This has tripped up many people.

Resources