I play an audio online with AVPlayer,and want to save the data/stream of audio to local when avplayer finish loading stream.
I implementt it as the following:
let fileUrl = NSURL(string: strUrl)!
let asset = AVURLAsset(URL: fileUrl)
asset.resourceLoader.setDelegate(self, queue:dispatch_queue_create("AVARLDelegateDemo loader", nil))
self.pendingRequests = [AVAssetResourceLoadingRequest]()
asset.loadValuesAsynchronouslyForKeys(["playable"]){
dispatch_async( dispatch_get_main_queue()){
self.prepareToPlayAsset(asset, requestedKeys: ["playable"])
}
}
func resourceLoader(resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool {
.......
return false
}
When url is http/https, it does not call resourceLoader(resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource.... -, when url is customize (eg.:'test'), it call resourceLoader(resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource...
Who know the reason, Does resourceLoader not support http/https?
When player doesn't know how to load a file, it calls resourceLoader(resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource... method. So since it knows how to load URL, it wont call this method, so you have to pass custom URL.
This sample project and tutorial seems to detail what you're looking for.
the link above details setting a custom URL scheme and setting an AVAssetResourceLoaderDelegate to handle the resource as it loads. This is initialised as:
NSURL *url = [NSURL URLWithString:#"customscheme://host/audio.mp3"];
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:url options:nil];
[asset.resourceLoader setDelegate:self queue:dispatch_get_main_queue()];
AVPlayerItem *item = [AVPlayerItem playerItemWithAsset:asset];
[self addObserversForPlayerItem:item];
self.player = [AVPlayer playerWithPlayerItem:playerItem];
[self addObserversForPlayer];
The implementation of the delegate methods would then look similar to the following:
- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest*)loadingRequest{
NSURL *resourceURL = [loadingRequest.request URL];
if([resourceURL.scheme isEqualToString:#"customscheme"]){
LSFilePlayerResourceLoader *loader = [self resourceLoaderForRequest:loadingRequest];
if(loader==nil){
loader = [[LSFilePlayerResourceLoader alloc] initWithResourceURL:resourceURL session:self.session];
loader.delegate = self;
[self.resourceLoaders setObject:loader forKey:[self keyForResourceLoaderWithURL:resourceURL]];
}
[loader addRequest:loadingRequest];
return YES;
}
return NO;
}
- (void)resourceLoader:(AVAssetResourceLoader *)resourceLoader didCancelLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest{
LSFilePlayerResourceLoader *loader = [self resourceLoaderForRequest:loadingRequest];
[loader removeRequest:loadingRequest];
}
Where LSFilePlayerResourceLoader and LSFilePlayerResourceLoader are custom objects for handling the data being received (detailed in the link).
Here you can find full Swift solution based on native AVPlayerItem with custom loader.
Related
While testing my app on iOS 14, I found some mp3-format files not working properly. There's no error from start to end, but sounds only heard in the very beginning and muted in the following seconds.
Here are sample codes to reproduce.
NSURL *url = [NSURL URLWithString:urlString];
BOOL preferPreciseDuration = YES;
NSDictionary *options = #{AVURLAssetPreferPreciseDurationAndTimingKey: #(preferPreciseDuration)};
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:url options:options];
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:asset];
[playerItem addObserver:self forKeyPath:#"status" options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNew context:nil];
player = [AVPlayer playerWithPlayerItem:playerItem];
[player play];
I have looked into it for some time and found:
This problem only occurs on iOS 14+.
If I omit AVURLAssetPreferPreciseDurationAndTimingKey option, everything works fine.
It seems to be server-related, I have tested two URLs hosting exact same file, but sample1 works while sample1 doesn't.
On the same server, file1 works while file2 doesn't.
I have recently implement audio features in my application it's working in iOS 14+ too.
var player: AVAudioPlayer?
let url = Bundle.main.url(forResource: "zapsplat_emergency_nuclear_power_station_meltdown_alarm_42849", withExtension: "mp3")!
do {
player = try AVAudioPlayer(contentsOf: url)
guard let player = player else { return }
player.prepareToPlay()
player.play()
} catch let error as NSError {
print(error.description)
}
I am uploading music to server by converting it to NSData. When i upload the file to server then i also save it to document directory.So when i pass the url of any other music file other than i uploaded then it plays music.But when i try to play music file which i uploaded then it does not play the music.Below is the code for that.
Code for play music
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithURL:fileURL];
AVPlayer *player = [AVPlayer playerWithPlayerItem:playerItem];
player = [AVPlayer playerWithURL:fileURL];
[player play];
it may help you
AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:fileURL options:nil];
NSArray *keys = [NSArray arrayWithObject:[NSString stringWithFormat:#"%d",self.indexValue]];
[asset loadValuesAsynchronouslyForKeys:keys completionHandler:
^{
dispatch_async( dispatch_get_main_queue(),
^{
/* IMPORTANT: Must dispatch to main queue in order to operate on the AVPlayer and AVPlayerItem. */
if (![[keys objectAtIndex:0] isEqual:[NSString stringWithFormat:#"%d",self.indexValue]])
{
}
// NSLog(#"&______________________________________________NOreturned");
playerItem = [AVPlayerItem playerItemWithAsset:asset];
if (!player)
{
player=nil;
}
player=[AVPlayer playerWithPlayerItem:playerItem];
[player play];//: Playground - noun: a place where people can play
});
}];
}
There is a redundant reinitialization for the player object.
Do not forget to subscribe for notifications, for instance :
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:nil];
As it was said in comments, download via browser the file using its URL and try to play by regular player.
Or create asset, and asks about it's tracks, duration, playable state.
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:_videoURL options:nil];
NSArray *requestedKeys = #[#"tracks",#"playable",#"duration"];
__weak typeof(self) wSelf = self;
[asset loadValuesAsynchronouslyForKeys:requestedKeys completionHandler:
^{
for (NSString *thisKey in requestedKeys) {
NSError *error = nil;
AVKeyValueStatus keyStatus = [asset statusOfValueForKey:thisKey error:&error];
if (keyStatus == AVKeyValueStatusFailed) {
//Failed here
//return
}
}
if (!asset.isPlayable) {
//not playable....
}
}
I advice you use AVAudioPlayer for playing mp3. And I show you my two swift examples.
The first example when AVAudioPlayer plays mp3 file from NSData
var audioPlayer = AVAudioPlayer()
func playFromCore() {
let object = fetchedResultsController.fetchedObjects!.first as! Song
let file = object.file // This is NSData
AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, error: nil)
AVAudioSession.sharedInstance().setActive(true, error: nil)
audioPlayer = AVAudioPlayer(data: file, fileTypeHint: AVFileTypeMPEGLayer3, error: nil)
audioPlayer.prepareToPlay()
audioPlayer.play()
}
The second example when AVAudioPlayer plays NSURL
func playMusic() {
let url = NSBundle.mainBundle().URLForResource("89", withExtension: "mp3")!
AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, error: nil)
AVAudioSession.sharedInstance().setActive(true, error: nil)
audioPlayer = AVAudioPlayer(contentsOfURL: url, error: nil)
audioPlayer.prepareToPlay()
audioPlayer.play()
}
how dose MPMoviePlayerController play an audio (video) with a url which is untrusted certificate?
there is an error log like this ===>
NSURLConnection/CFURLConnection HTTP load failed
(kCFStreamErrorDomainSSL, -9813)
If only ask a request by using NSURLConnection/NSURLSession, I know use its delegate to accept the untrusted certificate.(like loading a image)
But if only playing a url with untrusted certificate, how to handle this part?
I have tried to add a NSURLConnection and have already run the delegate (connection:canAuthenticateAgainstProtectionSpace: connection:canAuthenticateAgainstProtectionSpace:)
before playing an audio, and I found out the error message:
NSURLConnection/CFURLConnection HTTP load failed
(kCFStreamErrorDomainSSL, -9813)
is gone, but still can't playing the audio successfully.
anybody know??
I also posted the question in Apple, and I got the answer.
MPMoviePlayerController can't play untrusted certificate.
Instead, we can use AVPlayer to play audio, video with untrusted certificate.
here is my code, works fine
- (IBAction)clickPlayBtn:(id)sender
{
NSURL *sourceMovieURL = [[NSURL alloc]initWithString:#"https//:xxxxxxx"];
AVURLAsset *movieAsset = [AVURLAsset URLAssetWithURL:sourceMovieURL options:nil];
[movieAsset.resourceLoader setDelegate:self queue:dispatch_get_main_queue()];
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:movieAsset];
AVPlayer *player = [AVPlayer playerWithPlayerItem:playerItem];
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];
playerLayer.frame = self.view.layer.bounds;
playerLayer.videoGravity = AVLayerVideoGravityResizeAspect;
[self.view.layer addSublayer:playerLayer];
[player play];
}
// AVAssetResourceLoaderDelegate
- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader
shouldWaitForResponseToAuthenticationChallenge:(NSURLAuthenticationChallenge *)authenticationChallenge
{
//server trust
NSURLProtectionSpace *protectionSpace = authenticationChallenge.protectionSpace;
if ([protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
{
[authenticationChallenge.sender useCredential:[NSURLCredential credentialForTrust:authenticationChallenge.protectionSpace.serverTrust] forAuthenticationChallenge:authenticationChallenge];
[authenticationChallenge.sender continueWithoutCredentialForAuthenticationChallenge:authenticationChallenge];
}
else{ // other type: username password, client trust..
}
return YES;
}
For Swift it looks something like that:
let sourceMovieURL = NSURL(string: "https://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4")!
let movieAsset:AVURLAsset = AVURLAsset(URL: sourceMovieURL, options: nil)
movieAsset.resourceLoader.setDelegate(self, queue: dispatch_get_main_queue())
let playerItem:AVPlayerItem = AVPlayerItem(asset: movieAsset)
let player:AVPlayer = AVPlayer(playerItem: playerItem)
let playerLayer:AVPlayerLayer = AVPlayerLayer(player: player)
playerLayer.frame = self.layer.bounds; // or self.view.layer.bounds when placed in a controller
playerLayer.videoGravity = AVLayerVideoGravityResizeAspect
self.layer.addSublayer(playerLayer) // or self.view.layer...
player.play()
...and don't forget to add AVAssetResourceLoaderDelegate to your View(Controller) which look like this:
func resourceLoader(resourceLoader: AVAssetResourceLoader, shouldWaitForResponseToAuthenticationChallenge authenticationChallenge: NSURLAuthenticationChallenge) -> Bool {
//server trust
let protectionSpace:NSURLProtectionSpace = authenticationChallenge.protectionSpace;
if (protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust)
{
authenticationChallenge.sender!.useCredential(NSURLCredential(forTrust: authenticationChallenge.protectionSpace.serverTrust!), forAuthenticationChallenge: authenticationChallenge)
authenticationChallenge.sender!.continueWithoutCredentialForAuthenticationChallenge(authenticationChallenge)
}
else{ // other type: username password, client trust..
}
return true
}
I want to load video in AVPlayer using YouTube URL but it is not showing anything.Whenever i am loading from a local storage using NSBundle it is working fine.Is there is any alternative to load video or we can do something in AVPlayer.
This is my code:
- (void)viewDidLoad
{
[super viewDidLoad];
NSError *setCategoryError = nil;
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error: &setCategoryError];
AVAsset *asset = [AVAsset assetWithURL:[NSURL fileURLWithPath:#"http://www.youtube.com/watch?v=zPP6lXaL7KA&feature=youtube_gdata_player"]];
avPlayerItem = [[AVPlayerItem alloc]initWithAsset:asset];
self.songPlayer = [AVPlayer playerWithPlayerItem:avPlayerItem];
self.avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer: self.songPlayer];
self.avPlayerLayer.frame = self.view.layer.bounds;
UIView *newView = [[UIView alloc] initWithFrame:self.view.bounds];
[newView.layer addSublayer:avPlayerLayer];
[self.view addSubview:newView];
[ self.songPlayer play];
}
You should use the iOS Youtube Helper library for playing youtube videos.
https://developers.google.com/youtube/v3/guides/ios_youtube_helper
I don't know if you can use the AVPlayer. I've seen some examples using MPMoviePlayerController on CocoaControls, like this one: https://www.cocoacontrols.com/controls/hcyoutubeparser or this one: https://www.cocoacontrols.com/controls/xcdyoutubevideoplayerviewcontroller
But I don't think using youtube's url directly in your player fits the ToS of the platform. So I will recommend you tu use the Youtube Helper Library if you are planning to publish your app.
Use XCDYouTubeKit pod
Get youtube video id from url like https://www.youtube.com/watch?v=dLEATulyCdw
you can use this code:
extension URL {
func youtubeVideoId() -> String? {
let pattern = #"(?<=(youtu\.be\/)|(v=)).+?(?=\?|\&|$)"#
let testString = absoluteString
if let matchRange = testString.range(of: pattern, options: .regularExpression) {
let subStr = testString[matchRange]
return String(subStr)
} else {
return .none
}
} }
Then call XCDYouTubeClient.default().getVideoWithIdentifier(videoId)
In the completion you can get url. video?.streamURLs contains urls with a different quality, choose desired.
Finally just pass this url to AVPlayer...
Update
Visual explanation
Use first instead of youtubeMaxAvailableQuality
I don't think if you can use this now because i just used this and encountered the same case.
I read the apple document-,it definitely refers to that (You cannot directly create an AVAsset instance to represent the media in an HTTP Live Stream.).
Instead here is apple's example:
NSURL *url = [NSURL URLWithString:#"<#Live stream URL#>];
// You may find a test stream at
http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8.
self.playerItem = [AVPlayerItem playerItemWithURL:url];
see,if not local url,should be playerItemWithURL:^_^
I use AVQueuePlayer to play a sequence of movies which are loaded from URLs.
I tried to initialize player instance with array of all AVPlayerItems that I need to play.
player = [[AVQueuePlayer queuePlayerWithItems:playerItemsArray]
But in this case AVQueuePlayer loads some initial part of each AVPlayerItem before starting playback. It causes frustrating freeze and application doesn't respond for some seconds.
There is possibility to add only first AVPLayerItem to player's queue, observe its state and add second item in queue only when first will reach end, but in this case there will be a gap between playback of two items caused by initializing and buffering of second AVPlayerItem.
Is there any way to organize gapless playback of several videos without a freeze?
Should I use some other player for this purposes?
Thanks in advance.
The solution is found.
When adding new AVPlayerItem in queue of AVQueuePlayer player will synchronously wait till initial part of player item will be buffered.
So in this case player item should be buffered asynchronously and after that it can be added in the queue. It can be done using [AVURLAsset loadValuesAsynchronouslyForKeys: completionHandler:]
For example:
AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:url options:nil];
NSArray *keys = [NSArray arrayWithObject:#"playable"];
[asset loadValuesAsynchronouslyForKeys:keys completionHandler:^()
{
dispatch_async(dispatch_get_main_queue(), ^
{
AVPlayerItem *playerItem = [[[AVPlayerItem alloc] initWithAsset:asset] autorelease];
[player insertItem:playerItem afterItem:nil];
});
}];
Using this solution queue of AVQueuePlayer can be populated with items without any gaps and freezes.
in Swift 2, working here:
func load() {
let player = AVQueuePlayer()
for url in urls {
makeItem(url)
}
}
func makeItem(url: String) {
let avAsset = AVURLAsset(URL: NSURL(string: url)!)
avAsset.loadValuesAsynchronouslyForKeys(["playable", "tracks", "duration"], completionHandler: {
dispatch_async(dispatch_get_main_queue(), {
self.enqueue(avAsset: avAsset)
})
})
}
func enqueue(avAsset: AVURLAsset) {
let item = AVPlayerItem(asset: avAsset)
self.player.insertItem(item, afterItem: nil)
}
Here is solution.
- (void)_makePlayer{
_player = [[AVQueuePlayer alloc] initWithPlayerItem:[AVPlayerItem playerItemWithAsset:[SSMoviePreviewItemMaker generateAVMovieItem]]];
}
+ (AVAsset *)generateAVMovieItem{
NSArray * array = [SSMovieFileManager getAllMovieResourceURL];
AVMutableComposition *composition = [[AVMutableComposition alloc] init];
for (int i = 0; i < array.count; i++) {
AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:array[i] options:nil];
[composition insertTimeRange:CMTimeRangeMake(kCMTimeZero, asset.duration)
ofAsset:asset
atTime:composition.duration error:nil];
}
return composition;
}