I have this piece of code for playing audio, but once it is finished, I want to play the same audio again and again, I think I should use numberofloops=-1, but where I need to use this directly. Please help me.
#import "JetNapMusicPlayer.h"
#import <AVFoundation/AVFoundation.h>
#interface JetNapMusicPlayer()
#property(nonatomic,strong) AVQueuePlayer *avQueuePlayer;
#end
static JetNapMusicPlayer *sharedManager = nil;
#implementation JetNapMusicPlaye
#pragma mark Singleton Methods
+ (id)sharedManager {
#synchronized(self) {
if(sharedManager == nil)
sharedManager = [[super alloc] init];
}
return sharedManager;
}
- (id)init {
if (self = [super init]) {
// [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
MPRemoteCommandCenter *rcc = [MPRemoteCommandCenter sharedCommandCenter];
MPRemoteCommand *playCommand = rcc.playCommand;
[playCommand setEnabled:YES];
[playCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent *event) {
[(JetNapMusicPlayer *)[JetNapMusicPlayer sharedManager] play];
return MPRemoteCommandHandlerStatusSuccess;
}];
MPRemoteCommand *pauseCommand = rcc.pauseCommand;
[pauseCommand setEnabled:YES];
[pauseCommand addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent *event) {
[(JetNapMusicPlayer *)[JetNapMusicPlayer sharedManager] pause];
return MPRemoteCommandHandlerStatusSuccess;
}];
}
return self;
}
- (void)dealloc {
[super dealloc];
}
-(AVPlayer *)avQueuePlayer
{
if (!_avQueuePlayer) {
[self initSession];
_avQueuePlayer = [[AVQueuePlayer alloc] init];
}
return _avQueuePlayer;
}
-(void)initSession
{
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(audioSessionInterrupted:)
name: AVAudioSessionInterruptionNotification
object: [AVAudioSession sharedInstance]];
//set audio category with options - for this demo we'll do playback only
NSError *categoryError = nil;
[[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryPlayback error:&categoryError];
if (categoryError) {
NSLog(#"Error setting category! %#", [categoryError description]);
}
//activation of audio session
NSError *activationError = nil;
BOOL success = [[AVAudioSession sharedInstance] setActive: YES error: &activationError];
if (!success) {
if (activationError) {
NSLog(#"Could not activate audio session. %#", [activationError localizedDescription]);
} else {
NSLog(#"audio session could not be activated!");
}
}
}
#pragma mark - notifications
-(void)audioSessionInterrupted:(NSNotification*)interruptionNotification
{
NSLog(#"interruption received: %#", interruptionNotification);
}
#pragma mark - player actions
-(void) pause
{
[[self avQueuePlayer] pause];
}
-(void) play
{
[[self avQueuePlayer] play];
}
-(void) clear
{
[[self avQueuePlayer] removeAllItems];
}
#pragma mark - remote control events
#pragma mark - Kony FFI
+ (BOOL)playMusic:(NSString *)filename artistname:(NSString *)artistname songname:(NSString *)songname {
NSString *name = [filename stringByDeletingPathExtension];
NSString *ext = [filename pathExtension];
AVPlayerItem *avSongItem = [[AVPlayerItem alloc] initWithURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:[[NSString alloc] initWithFormat:name] ofType:ext]]];
if (avSongItem) {
[(JetNapMusicPlayer *)[JetNapMusicPlayer sharedManager] clear];
[[[JetNapMusicPlayer sharedManager] avQueuePlayer] insertItem:avSongItem afterItem:nil];
[(JetNapMusicPlayer *)[JetNapMusicPlayer sharedManager] play];
[MPNowPlayingInfoCenter defaultCenter].nowPlayingInfo = #{MPMediaItemPropertyTitle: songname, MPMediaItemPropertyArtist:artistname};
}
return YES;
}
+ (BOOL)stopMusic {
[(JetNapMusicPlayer *)[JetNapMusicPlayer sharedManager] pause];
[(JetNapMusicPlayer *)[JetNapMusicPlayer sharedManager] clear];
return YES;
}
#end
To loop a song use below code after alloc init of avSongItem.
avSongItem.actionAtItemEnd = AVPlayerActionAtItemEndNone;
More info : Looping a video with AVFoundation AVPlayer?
Also as mentioned in the link use notification.
avSongItem.actionAtItemEnd = AVPlayerActionAtItemEndNone;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:[avPlayer currentItem]];
this will prevent the player to pause at the end.
in the notification:
- (void)playerItemDidReachEnd:(NSNotification *)notification {
AVPlayerItem *p = [notification object];
[p seekToTime:kCMTimeZero];
}
Related
If the app is playing the audio and phone screen is locked then control screen is shown as below. I am not able to take any action on avplayer
In my appdelegate I implemented:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
MPRemoteCommandCenter *rcc = [MPRemoteCommandCenter sharedCommandCenter];
[[rcc skipForwardCommand] setEnabled:NO];
[[rcc skipBackwardCommand] setEnabled:NO];
[[rcc nextTrackCommand] setEnabled:NO];
[[rcc previousTrackCommand] setEnabled:NO];
[[rcc skipForwardCommand] setEnabled:NO];
[[rcc skipBackwardCommand] setEnabled:NO];
rcc.playCommand.enabled = YES;
rcc.pauseCommand.enabled = YES;
[[MPRemoteCommandCenter sharedCommandCenter].playCommand addTarget:self action:#selector(play)];
[[MPRemoteCommandCenter sharedCommandCenter].pauseCommand addTarget:self action:#selector(pause)];
}
- (void) play {
[[MyVideoController instance] play];
}
- (void) pause {
[[MyVideoController instance] pause];
}
class MyVideoController consists of:
- (void) pause {
[self.avPlayer pause];
}
- (void) play {
[self.avPlayer play];
}
Even though these methods are triggered (added breakpoints to check), no action on avplayer is taken. No matter what, avplayer doesn't pause.
Is there any way to pause the avplayer?
EDIT 1:
Adding the complete code
In my AppDelegate:
- (void) remoteControlReceivedWithEvent: (UIEvent *) event {
[[ZVideoPlayerController instance] eventReceived:event];
if (event.type == UIEventTypeRemoteControl) {
switch (event.subtype) {
case UIEventSubtypeRemoteControlTogglePlayPause: {
break;
}
case UIEventSubtypeRemoteControlPlay: {
[[ZVideoPlayerController instance] play];
break;
}
case UIEventSubtypeRemoteControlPause: {
[[ZVideoPlayerController instance] pause];
break;
}
default:
break;
}
}
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[self becomeFirstResponder];
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
}
I AM RECEIVING EVENTS BUT THE AUDIO DOESN'T PAUSE UPON CALLING PAUSE METHOD ON AVPLAYER.
EDIT 2:
instance declaration in PlayerController class
+ (instancetype)instance {
static id instance = nil;
if (instance == nil)
{
static dispatch_once_t onceToken = 0;
dispatch_once(&onceToken, ^(void) {
NSAssert(instance == nil, #"Singleton instance is already allocated.");
instance = [[super allocWithZone:NULL] init];
});
}
return instance;
}
initialising AVPlayer
AVURLAsset *avAsset = [AVURLAsset URLAssetWithURL:url options:nil];
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:avAsset];
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryPlayback error:nil];
NSError *activationError = nil;
BOOL success = [[AVAudioSession sharedInstance] setActive: YES error: &activationError];
NSMutableDictionary *songInfo = [[NSMutableDictionary alloc] init];
MPMediaItemArtwork *albumArt = [[MPMediaItemArtwork alloc] initWithImage: [UIImage imageNamed:#"Audio_Thumbnail_Play"]];
[songInfo setObject:title forKey:MPMediaItemPropertyTitle];
[songInfo setObject:#"100" forKey:MPMediaItemPropertyPlaybackDuration];
[songInfo setObject:albumArt forKey:MPMediaItemPropertyArtwork];
[[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:songInfo];
self.avPlayer = [AVPlayer playerWithPlayerItem:playerItem];
self.avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:self.avPlayer];
I found a solution to the problem. As I was getting nil value of avPlayer, I used my PageViewController class to get the instance of PlayerController. Then I used the instance of this playerController to play and pause my avplayer because this instance holds the reference to avPlayer.
- (PlayerController *)getVideoController {
NSArray *controllers = [UtiliyClass getNavigationController].viewControllers;
PageViewController *pageController = nil;
for (UIViewController *cont in controllers) {
if ([cont isKindOfClass:[PageViewController class]]) {
pageController = (PageViewController *)cont;
break;
}
}
if (pageController == nil) {
return nil;
}
NSArray *objectsController =pageController.pageController.viewControllers;
PlayerController *videoPlayerController = nil;
for (UIViewController *item in objectsController) {
if ([item isKindOfClass:[PlayerController class]]) {
videoPlayerController = (PlayerController *)item;
break;
}
}
return videoPlayerController;
}
- (void) pause {
PlayerController *controller = [self getVideoController];
[controller.avPlayer pause];
}
- (void) play {
PlayerController *controller = [self getVideoController];
[controller.avPlayer play];
}
You need to register for remote notification to update player state when application is locked.For that follow following:
Add this in your AppDelegate , Ideally in applicationDidEnterBackground:
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[self becomeFirstResponder];
And this in applicationDidBecomeActive:
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
Recieve remote notifcations by adding this in AppDelagate. This will listen all actions when phone is locked.
- (void)remoteControlReceivedWithEvent:(UIEvent *)event {
if (event.type == UIEventTypeRemoteControl){
// Call method of your player where you want to make change (Pause , Paly),
// I am calling a shared view for example, Its up to your logic how you want to deal it
[[AudioPlayerView sharedPlayerView] remoteControlReceivedWithEvent:event];
}
}
And in that get your desired event and update state accordingly
- (void)remoteControlReceivedWithEvent:(UIEvent *)event {
if (event.type == UIEventTypeRemoteControl){
switch (event.subtype){
case UIEventSubtypeRemoteControlPlay:
[[MyVideoController instance] play];
break;
case UIEventSubtypeRemoteControlPause:
[[MyVideoController instance] pause];
break;
case UIEventSubtypeRemoteControlTogglePlayPause:
// Check if state is playing , call pause else call play
break;
}
default:
break;
}
}
}
In iOS 7.1 and later, use the shared MPRemoteCommandCenter object to register for remote control events. You do not need to call this method when using the shared command center object.
This method starts the delivery of remote control events using the responder chain. Remote-control events originate as commands issued by headsets and external accessories that are intended to control multimedia presented by an app. To stop the reception of remote-control events, you must call endReceivingRemoteControlEvents().
Add this following code for in didfinishlunching for init audio season and get remote control event :
// Initialize the AVAudioSession here.
if (![[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:&myErr]) {
// Handle the error here.
NSLog(#"Audio Session error %#, %#", myErr, [myErr userInfo]);
}
else{
// Since there were no errors initializing the session, we'll allow begin receiving remote control events
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
}
for reciving commadn use this code :
- (void)remoteControlReceivedWithEvent:(UIEvent *)receivedEvent {
if (receivedEvent.type == UIEventTypeRemoteControl) {
switch (receivedEvent.subtype) {
case UIEventSubtypeRemoteControlPreviousTrack:
break;
case UIEventSubtypeRemoteControlNextTrack:
break;
case UIEventSubtypeRemoteControlPlay:
[[MyVideoController instance] play];
break;
case UIEventSubtypeRemoteControlPause:
[[MyVideoController instance] pause];
break;
default:
break;
}
}
}
I am trying to play a movie at the beginning of my game. I am using AVPlayer to do this. My problem is, when I register a KVO to check the status of my AVPlayer, my game proceeds as usual without waiting for the video to load and finish. As a result, I can only hear the audio from my .mov file and can't see any video (since my game has already started).
I would like the video to load and finish before proceeding with the game.
Here's the code:
#interface RMVideoView : NSView
{
NSURL* _videoURL;
AVPlayer* _player;
AVPlayerLayer* _playerLayer;
}
#property (nonatomic, readonly, strong) AVPlayer* player;
#property (nonatomic, readonly, strong) AVPlayerLayer* playerLayer;
#property (nonatomic, retain) NSURL* videoURL;
- (void) play;
#end
static void *RMVideoViewPlayerLayerReadyForDisplay = &RMVideoViewPlayerLayerReadyForDisplay;
static void *RMVideoViewPlayerItemStatusContext = &RMVideoViewPlayerItemStatusContext;
#interface RMVideoView()
- (void)onError:(NSError*)error;
- (void)onReadyToPlay;
- (void)setUpPlaybackOfAsset:(AVAsset *)asset withKeys:(NSArray *)keys;
#end
#implementation RMVideoView
#synthesize player = _player;
#synthesize playerLayer = _playerLayer;
#synthesize videoURL = _videoURL;
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.wantsLayer = YES;
_player = [[AVPlayer alloc] init];
[self addObserver:self forKeyPath:#"player.currentItem.status" options:NSKeyValueObservingOptionNew context:RMVideoViewPlayerItemStatusContext];
}
return self;
}
- (void) setVideoURL:(NSURL *)videoURL {
_videoURL = videoURL;
[self.player pause];
[self.playerLayer removeFromSuperlayer];
AVURLAsset *asset = [AVAsset assetWithURL:self.videoURL];
[asset retain];
NSArray *assetKeysToLoadAndTest = [NSArray arrayWithObjects:#"playable", #"hasProtectedContent", #"tracks", #"duration", nil];
[asset loadValuesAsynchronouslyForKeys:assetKeysToLoadAndTest completionHandler:^{
dispatch_async(dispatch_get_main_queue(),^{
[self setUpPlaybackOfAsset:asset withKeys:assetKeysToLoadAndTest];
});
}];
}
#pragma mark - KVO
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (context == RMVideoViewPlayerItemStatusContext)
{
AVPlayerStatus status = [[change objectForKey:NSKeyValueChangeNewKey] integerValue];
switch (status)
{
case AVPlayerItemStatusUnknown:
break;
case AVPlayerItemStatusReadyToPlay:
[self onReadyToPlay];
break;
case AVPlayerItemStatusFailed:
[self onError:nil];
break;
}
}
else if (context == RMVideoViewPlayerLayerReadyForDisplay)
{
if ([[change objectForKey:NSKeyValueChangeNewKey] boolValue])
{
self.playerLayer.hidden = NO;
}
}
else
{
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
#pragma mark - Private
- (void)onError:(NSError*)error {
// Notify delegate
}
- (void)onReadyToPlay {
// Notify delegate
[self.player play];
}
- (void)setUpPlaybackOfAsset:(AVAsset *)asset withKeys:(NSArray *)keys {
for (NSString *key in keys) {
NSError *error = nil;
if ([asset statusOfValueForKey:key error:&error] == AVKeyValueStatusFailed) {
[self onError:error];
return;
}
}
if (!asset.isPlayable || asset.hasProtectedContent) {
[self onError:nil];
return;
}
if ([[asset tracksWithMediaType:AVMediaTypeVideo] count] != 0)
{ // Asset has video tracks
_playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
self.playerLayer.frame = self.layer.bounds;
self.playerLayer.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
self.playerLayer.hidden = NO;
[self.layer addSublayer:self.playerLayer];
[self addObserver:self forKeyPath:#"playerLayer.readyForDisplay" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:RMVideoViewPlayerLayerReadyForDisplay];
}
// Create a new AVPlayerItem and make it our player's current item.
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:asset];
[self.player replaceCurrentItemWithPlayerItem:playerItem];
}
#pragma mark - Public
- (void) play {
[self.player play];
}
#end
I am calling the above code from my entry function's -(void)drawView method this way:
-(void)drawView
{
if(playVideo)
{
RMVideoView *rmVid = [[RMVideoView alloc]init];
NSURL* MovieURL;
NSBundle *bundle = [NSBundle mainBundle];
if(bundle != nil)
{
NSString *moviePath = [bundle pathForResource:#"MyVideoResource" ofType:#"mov"];
if (moviePath)
{
MovieURL = [NSURL fileURLWithPath:moviePath];
[MovieURL retain];
[rmVid setVideoURL:MovieURL];
}
}
playVideo = kFalse;
}
}
The call made to [rmVid setVideoURL:MovieURL] returns when KVO is setup and the game runs forward.
Please help!
You can listen for a notification, when the video playback reaches the end like this:
AVPlayerItem *avPlayerItem =[[AVPlayerItem alloc]initWithAsset:avAsset];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerItemDidReachedEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:avPlayerItem];
How do i pass the song info such as song name and track duration to control center.
Player plays music fine and the play and pause control works fine though.
Playing with apple's music app
Playing with my app with the code below, how to pass song information in order to display it ?
//AppDelegate
-(void)setupAudio
{
// Set AVAudioSession
NSError *sessionError = nil;
[[AVAudioSession sharedInstance] setDelegate:self];
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:&sessionError];
// Change the default output audio route
UInt32 doChangeDefaultRoute = 1;
AudioSessionSetProperty(kAudioSessionProperty_OverrideCategoryDefaultToSpeaker,
sizeof(doChangeDefaultRoute), &doChangeDefaultRoute);
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[self becomeFirstResponder];
}
//Make sure we can recieve remote control events
- (BOOL)canBecomeFirstResponder {
return YES;
}
- (void)remoteControlReceivedWithEvent:(UIEvent *)event
{
//if it is a remote control event handle it correctly
if (event.type == UIEventTypeRemoteControl) {
if (event.subtype == UIEventSubtypeRemoteControlPlay)
{
NSLog(#"UIEventSubtypeRemoteControlPlay");
[[AppMusicPlayer sharedService]playAudio];
}
else if (event.subtype == UIEventSubtypeRemoteControlPause)
{
NSLog(#"UIEventSubtypeRemoteControlPause");
[[AppMusicPlayer sharedService]pauseAudio];
}
else if (event.subtype == UIEventSubtypeRemoteControlTogglePlayPause)
{
NSLog(#"UIEventSubtypeRemoteControlTogglePlayPause");
}
}
}
AppMusicPlayer.m
+ (id) sharedService
{
static dispatch_once_t _singletonPredicate;
static AppMusicPlayer *_sharedObject = nil;
dispatch_once(&_singletonPredicate, ^{
_sharedObject = [[AppMusicPlayer alloc]init];
});
return _sharedObject;
}
- (id)init
{
self = [super init];
if (self) {
// Work your initialising here as you normally would
}
return self;
}
- (void)playAudio
{
[self.audioPlayer play];
}
- (void)pauseAudio
{
NSLog(#"pause");
[self.audioPlayer pause];
}
- (void)playAudioFromURL:(NSURL *)songURL
{
[self.audioPlayer stop];
//Declare the audio file location and settup the player
NSError *error;
self.audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:songURL error:&error];
[self.audioPlayer setNumberOfLoops:-1];
if (error)
{
NSLog(#"%#", [error localizedDescription]);
}
else
{
//Load the audio into memory
[self.audioPlayer prepareToPlay];
[self.audioPlayer play];
}
}
-(void)setUpRemoteControl
{
NSDictionary *nowPlaying = #{MPMediaItemPropertyArtist: songItem.artistName,
MPMediaItemPropertyAlbumTitle: songItem.albumTitle,
MPMediaItemPropertyPlaybackDuration:songItem.playbackDuration,
MPNowPlayingInfoPropertyPlaybackRate:#1.0f,
MPMediaItemPropertyArtwork:[songMedia valueForProperty:MPMediaItemPropertyArtwork]
};
[[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:nowPlaying];
}
Are you playing songs from iPods music library ?
If yes than You should you MPMusicPlayerViewController, it provides in-build functionality for seek bar properties as well as Info center.
I can see here in your code you have used AVAudioPlayer,
We can set only below details using AVAudioPlayer class, but can't access seekbar change event in our app.
NSMutableDictionary *albumInfo = [[NSMutableDictionary alloc] init];
UIImage *artWork = [UIImage imageNamed:album.imageUrl];
[albumInfo setObject:album.title forKey:MPMediaItemPropertyTitle];
[albumInfo setObject:album.auther forKey:MPMediaItemPropertyArtist];
[albumInfo setObject:album.title forKey:MPMediaItemPropertyAlbumTitle];
[albumInfo setObject:artworkP forKey:MPMediaItemPropertyArtwork];
[albumInfo setObject:album.playrDur forKey:MPMediaItemPropertyPlaybackDuration]
[albumInfo setObject:album.elapsedTime forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime]
[[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:albumInfo]
Here we need to count album.playrDur and album.elapsedTime as per required format.
You are looking for this:
- (IBAction)playButtonTouched:(id)sender {
Class playingInfoCenter = NSClassFromString(#"MPNowPlayingInfoCenter");
if (playingInfoCenter) {
NSMutableDictionary *songInfo = [[NSMutableDictionary alloc] init];
MPMediaItemArtwork *albumArt = [[MPMediaItemArtwork alloc] initWithImage: [UIImage imagedNamed:#"AlbumArt"]];
[songInfo setObject:#"Audio Title" forKey:MPMediaItemPropertyTitle];
[songInfo setObject:#"Audio Author" forKey:MPMediaItemPropertyArtist];
[songInfo setObject:#"Audio Album" forKey:MPMediaItemPropertyAlbumTitle];
[songInfo setObject:albumArt forKey:MPMediaItemPropertyArtwork];
[[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:songInfo];
}
}
I happen to be working on the same thing today and don't know the answer, but in my app the Control Center does show the app name. I want to be able to show the title of the thing I'm playing. I even have a picture I'd like to show, like an album cover. I don't think I'm doing anything different than you, I didn't code anything to send it the app name, for example.
Wait a second... I see there's something called MPNowPlayingInfoCenter that we should look into..
Not sure but may be this code help you little bit..
- (void) handle_NowPlayingItemChanged: (id) notification
{
MPMediaItem *currentItem = [musicPlayer nowPlayingItem];
NSString *titleString = [currentItem valueForProperty:MPMediaItemPropertyTitle];
if (titleString)
{
titleLabel.text = [NSString stringWithFormat:#"Title: %#",titleString];
}
else
{
titleLabel.text = #"Title: Unknown title";
}
}
I am using this code to start playing local video chunks referenced to form a play list.
The very same code works on one project, but not on another.
On the project I am working on right now, I can see how the first chunk gets loaded, and the first frame also show up. But the AVPlayer never start playing because it never gets the AVPlayerStatusReadyToPlay notification:
- (void)loadAssetAsync
{
NSLog(#"loadAssetAsync for URL: %#", videoURL);
/**
* Create an asset for inspection of a resource referenced by a given URL.
* Load the values for the asset keys "tracks", "playable".
*/
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:videoURL options:nil];
NSArray *requestedKeys = [NSArray arrayWithObjects:kTracksKey, kPlayableKey, nil];
// Tells the asset to load the values of any of the specified keys that are not already loaded.
[asset loadValuesAsynchronouslyForKeys:requestedKeys completionHandler:
^{
dispatch_async( dispatch_get_main_queue(),
^{
// IMPORTANT: Must dispatch to main queue in order to operate on the AVPlayer and AVPlayerItem.
[self prepareToPlayAsset:asset withKeys:requestedKeys];
});
}];
}
/**
* Invoked at the completion of the loading of the values for all keys on the asset that required.
*/
- (void)prepareToPlayAsset:(AVURLAsset *)asset withKeys:(NSArray *)requestedKeys
{
//assert([NSThread isMainThread]);
// Make sure that the value of each key has loaded successfully.
for (NSString *thisKey in requestedKeys)
{
NSError *error = nil;
AVKeyValueStatus keyStatus = [asset statusOfValueForKey:thisKey error:&error];
if (keyStatus == AVKeyValueStatusFailed)
{
BVLogWarn(#"%#: %#", THIS_FILE, error.localizedDescription);
[self handleErrorForProxy:error];
[self assetFailedToPrepareForPlayback];
return;
}
}
if (!asset.playable)
{
BVLogWarn(#"%#: Item cannot be played", THIS_FILE);
[self handleErrorForProxy:nil];
[self assetFailedToPrepareForPlayback];
return;
}
// Create a new instance of AVPlayerItem from the now successfully loaded AVAsset.
playerItem = [[AVPlayerItem alloc] initWithAsset:asset];
// Observe the player item "status" key to determine when it is ready to play.
[playerItem addObserver:self
forKeyPath:kStatusKey
options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
context:BVPlayerItemStatusObserverContext];
[playerItem addObserver:self
forKeyPath:kBufferEmpty
options:NSKeyValueObservingOptionNew
context:BVPLayerBufferEmptyObserverContext];
[playerItem addObserver:self
forKeyPath:kLikelyToKeepUp
options:NSKeyValueObservingOptionNew
context:BVPlayerLikelyToKeepUpObserverContext];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:playerItem];
// Get a new AVPlayer initialized to play the specified player item.
player = [[AVPlayer alloc] initWithPlayerItem:playerItem];
// Do nothing if the item has finished playing
[player setActionAtItemEnd:AVPlayerActionAtItemEndNone];
/* Observe the AVPlayer "currentItem" property to find out when any
AVPlayer replaceCurrentItemWithPlayerItem: replacement will/did
occur.*/
[player addObserver:self
forKeyPath:kCurrentItemKey
options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
context:BVCurrentItemObserverContext];
// Observe the AVPlayer "rate" property to update the scrubber control.
[player addObserver:self
forKeyPath:kRateKey
options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
context:BVRateObserverContext];
[player replaceCurrentItemWithPlayerItem:playerItem];
}
- (void)observeValueForKeyPath:(NSString*) keyPath
ofObject:(id)object
change:(NSDictionary*)change
context:(void*)context
{
// AVPlayerItem "status" property value observer.
if (context == BVPlayerItemStatusObserverContext)
{
AVPlayerStatus status = [[change objectForKey:NSKeyValueChangeNewKey] integerValue];
switch (status)
{
case AVPlayerStatusUnknown:
{
[self removeTimeObserver];
[self syncTimeScrubber];
[timeControl setEnabled:NO];
[playButton setEnabled:NO];
[fullscreenButton setEnabled:NO];
[loadingIndicator startAnimating];
}
break;
case AVPlayerStatusReadyToPlay:
{
if (firstPlayback|becomeActive)
{
[timeControl setEnabled:YES];
[playButton setEnabled:YES];
[fullscreenButton setEnabled:YES];
[upperControls setHidden:NO];
[lowerControls setHidden:NO];
[loadingIndicator stopAnimating];
if (firstPlayback) {
[playbackView setNeedsDisplay];
}
if (self.shouldAutoplay)
[player play];
if (firstPlayback) {
timeRemaining.text = [NSString stringWithFormat:#"-%#", timeStringForSeconds(CMTimeGetSeconds(playerItem.duration) )];
}
firstPlayback = NO;
controlsHidden = NO;
if (!isSeeking)
[self startHideControlsTimer];
}
if (becomeActive) {
dispatch_async(dispatch_get_main_queue(), ^{
[player seekToTime:CMTimeMakeWithSeconds(lastTimeStop, NSEC_PER_SEC)
toleranceBefore:kCMTimeZero
toleranceAfter:kCMTimeZero
completionHandler:^(BOOL finished) {
if (finished && rateToRestoreAfterScrubbing)
{
[player setRate:rateToRestoreAfterScrubbing];
rateToRestoreAfterScrubbing = 0.f;
}
[self addTimeObserver];
[playbackView setPlayer:player];
becomeActive = NO;
}];
});
}else{
[self addTimeObserver];
}
}
break;
case AVPlayerStatusFailed:
{
AVPlayerItem *thePlayerItem = (AVPlayerItem *)object;
BVLogWarn(#"%#: %#", THIS_FILE, thePlayerItem.error.localizedDescription);
[self handleErrorForProxy:thePlayerItem.error];
[self assetFailedToPrepareForPlayback];
}
break;
}
}
// AVPlayer "rate" property value observer.
else if (context == BVRateObserverContext)
{
[self updatePlayPauseButton];
}
// AVPlayer "currentItem" buffer is empty observer
else if (context == BVPLayerBufferEmptyObserverContext)
{
[loadingIndicator startAnimating];
}
// AVPlayer "currentItem" is likely to keep up observer
else if (context == BVPlayerLikelyToKeepUpObserverContext)
{
[loadingIndicator stopAnimating];
}
// AVPlayer "currentItem" property observer.
else if (context == BVCurrentItemObserverContext)
{
AVPlayerItem *newPlayerItem = [change objectForKey:NSKeyValueChangeNewKey];
// New player item null?
if (newPlayerItem == (id)[NSNull null])
{
[playButton setEnabled:NO];
[timeControl setEnabled:NO];
} else // Replacement of player currentItem has occurred
{
if (!becomeActive) {
[playbackView setPlayer:player];
}else{
}
[playbackView setVideoFillMode:[self scalingMode]];
[self updatePlayPauseButton];
}
}
else
{
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
return;
}
Did you try to put some log before your if ? Maybe the notification is working but it's stucked because your if?
case AVPlayerStatusReadyToPlay:
{
NSLog(#"NOTIFICATION TEST PASSED");
if (firstPlayback|becomeActive) {}
}
I am creating this app were there is background music playing, but I want it so the user can stop the music with a UISwitch if they dont want background music. I already have the code working for the music to play and stop (Code below) with the switch bu my question is this. When i switch to a different view (one that the switch isnt on) and the music is playing, then go back to the view. The switch is off, when i turn it back on (even thought the music is already playing), it will play it again and they will overlap each other (same music file).
Code for the switch and music player...
-(IBAction)play:(id)sender {
if (audioControlSwitch.on) {
[sound setTextColor:[UIColor blueColor]];
[sound setText:#"Sound On"];
NSURL *url = [NSURL fileURLWithPath:[NSString stringWithFormat:#"%#/Tone 2.m4a", [[NSBundle mainBundle] resourcePath]]];
NSError *error;
audioPlayer1 = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
audioPlayer1.numberOfLoops = 100000000000000000;
[audioPlayer1 play];
} else {
[sound setTextColor:[UIColor darkGrayColor]];
[sound setText:#"Sound Off"];
[audioPlayer1 stop];
}
}
in yourViewController.h
#interface yourViewController : NSObject <AVAudioPlayerDelegate> {
BOOL inBackground;
}
- (void)registerForBackgroundNotifications;
in yourViewController.m
#synthesize inBackground;
#pragma mark background notifications
- (void)registerForBackgroundNotifications
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(setInBackgroundFlag)
name:UIApplicationWillResignActiveNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(clearInBackgroundFlag)
name:UIApplicationWillEnterForegroundNotification
object:nil];
}
- (void)setInBackgroundFlag
{
inBackground = true;
}
- (void)clearInBackgroundFlag
{
inBackground = false;
}
- (void)updateViewForPlayerStateInBackground:(AVAudioPlayer *)p
{
if (p.playing)
{
// Do something
}
else
{
// Do something else
}
}
#pragma mark AVAudioPlayer delegate methods
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)p successfully:(BOOL)flag
{
if (flag == NO)
NSLog(#"Playback finished unsuccessfully");
[p setCurrentTime:0.];
if (inBackground)
{
[self updateViewForPlayerStateInBackground:p];
}
else
{
}
}
- (void)playerDecodeErrorDidOccur:(AVAudioPlayer *)p error:(NSError *)error
{
NSLog(#"ERROR IN DECODE: %#\n", error);
}
// we will only get these notifications if playback was interrupted
- (void)audioPlayerBeginInterruption:(AVAudioPlayer *)p
{
NSLog(#"Interruption begin. Updating UI for new state");
// the object has already been paused, we just need to update UI
if (inBackground)
{
[self updateViewForPlayerStateInBackground:p];
}
else
{
}
}
- (void)audioPlayerEndInterruption:(AVAudioPlayer *)p
{
NSLog(#"Interruption ended. Resuming playback");
[self startPlaybackForPlayer:p];
}
-(void)startPlaybackForPlayer:(AVAudioPlayer*)p
{
if ([p play])
{
}
else
NSLog(#"Could not play %#\n", p.url);
}
#end