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.
Related
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
I'm new to xCode/swift so please forgive my inexperience. I have a ViewController where my viewers can listen to some audio playback. The playback is accessed like this when the player clicks a play button:
#IBAction func buttonClicked(_ sender: RoundButton)
{
self.clickedButton = sender
guard let url = sender.url else {
return
}
let player = AVPlayer(url: url)
let controller = AVPlayerViewController()
controller.player = player
present(controller, animated: true) {
player.play()
}
}
I got this code from another StackOverflow question, so I don't completely understand it. My goal is to be able to save the URL and the last played time so that the user can minimize the app, or navigate to a different screen, and then be able to click a "continue listening" button which will pull up another AVPlayer with the last used URL. This "continue listening" AVPlayer will then seek to the last played time.
I know that I need to observe the first AVPlayer somehow, so that when it is paused, stopped, or put in the background, I save the currentTime to a NSUserDefault (I think?). I also need to save the URL, because there are many different URLs that the user could click on.
I tried doing this, and besides not being able to figure out the observation, I also couldn't figure out the type inconsistencies present with NSUserDefault. I tried to retrieve URL NSDefault value as a String after setting it, but when I went to cast the String to a URL using URL(string: lastPlayedURL), xCode complained about "Cannot convert type Data? to expected type String".
My issue with using other StackOverflow questions to solve my problem is that I don't understand where to put the code blocks. Where do I create the observer? Because xCode did not seem happy when I created it inside the body of "buttonClicked".
Thank you for listening to my rambling.
Yes, UserDefaults seem appropriate to store the URL in. Use this method to do that.
To observe the player you need to use an AVPlayerItem. Here's some code that shows that.
About your general issues with Xcode (note the capitalisation) and Swift language, I'm afraid these are things you need to work through yourself by reading/watching tutorials/documentation. Then when you find detailed issues, post your code here and ask.
Good luck and have fun!
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
]
}
}
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.
I have an app that records video and displays them in a certain order. The videos recorded in my app has the correct date, but the times were all the same. So, all of the video recorded today show: 2015-07-31 13:15:51 +0000
I'm not setting any properties related to time in my capture session or movie output. I can't seem to find any documentation on how to do this properly. Does anyone have an idea?
Thanks!
Update: I recorded more video within the app. Turns out that the date is also wrong. It’s creation date reads the same as all the other videos created previously.
For kicks, I delete the app from my phone, recorded a new video. It has the correct day and time. But after recording a second video, the date and time is the same as the previous recorded video.
It turns out that I was writing over the same file path. Creating a unique string each time solved my problem.
let uuid = NSUUID().UUIDString
let outputPath: NSString = "\(NSTemporaryDirectory()) + \(uuid) + output.mov"
let outputURL = NSURL(fileURLWithPath: outputPath as String)
movieFileOutput?.startRecordingToOutputFileURL(outputURL, recordingDelegate: self)