Objective-C, Need help creating an AVAudioPlayer singleton

前端 未结 2 1192
囚心锁ツ
囚心锁ツ 2020-12-09 14:04

I\'m working on a soundboard app, that has several pages of buttons to play sound effects with a stop button on every page should the user wish to manually interrupt the cli

相关标签:
2条回答
  • 2020-12-09 14:37

    My solution, as used in one of my own projects, is posted beneath. Feel free to copy-and-paste, I intend to open-source this project once it's finished :)

    A preview of the player can be seen on YouTube: http://www.youtube.com/watch?v=Q98DQ6iNTYM

    AudioPlayer.h

    @protocol AudioPlayerDelegate;
    
    @interface AudioPlayer : NSObject
    
    @property (nonatomic, assign, readonly) BOOL isPlaying;
    @property (nonatomic, assign) id <AudioPlayerDelegate> delegate;
    
    + (AudioPlayer *)sharedAudioPlayer;
    
    - (void)playAudioAtURL:(NSURL *)URL;
    - (void)play;
    - (void)pause;
    
    @end
    
    
    
    @protocol AudioPlayerDelegate <NSObject>
    @optional
    - (void)audioPlayerDidStartPlaying;
    - (void)audioPlayerDidStartBuffering;
    - (void)audioPlayerDidPause;
    - (void)audioPlayerDidFinishPlaying;
    @end
    

    AudioPlayer.m

    // import AVPlayer.h & AVPlayerItem.h
    
    
    @interface AudioPlayer ()
    - (void)playerItemDidFinishPlaying:(id)sender;
    @end
    
    
    @implementation AudioPlayer
    {
        AVPlayer *player;
    }
    
    @synthesize isPlaying, delegate;
    
    + (AudioPlayer *)sharedAudioPlayer
    {
        static dispatch_once_t pred;
        static AudioPlayer *sharedAudioPlayer = nil;
        dispatch_once(&pred, ^
        { 
            sharedAudioPlayer = [[self alloc] init]; 
    
            [[NSNotificationCenter defaultCenter] addObserver:sharedAudioPlayer selector:@selector(playerItemDidFinishPlaying:) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
        });
        return sharedAudioPlayer;
    }
    
    - (void)playAudioAtURL:(NSURL *)URL
    {
        if (player)
        {
            [player removeObserver:self forKeyPath:@"status"];
            [player pause];
        }
    
        player = [AVPlayer playerWithURL:URL];
        [player addObserver:self forKeyPath:@"status" options:0 context:nil];
    
        if (delegate && [delegate respondsToSelector:@selector(audioPlayerDidStartBuffering)])
            [delegate audioPlayerDidStartBuffering];
    }
    
    - (void)play
    {
        if (player) 
        {
            [player play];
    
            if (delegate && [delegate respondsToSelector:@selector(audioPlayerDidStartPlaying)])
                [delegate audioPlayerDidStartPlaying];
        }
    }
    
    - (void)pause
    {
        if (player) 
        {
            [player pause];
    
            if (delegate && [delegate respondsToSelector:@selector(audioPlayerDidPause)])
                [delegate audioPlayerDidPause];
        }
    }
    
    - (BOOL)isPlaying
    {
        DLog(@"%f", player.rate);
    
        return (player.rate > 0);
    }
    
    #pragma mark - AV player 
    
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 
    {
        if (object == player && [keyPath isEqualToString:@"status"]) 
        {
            if (player.status == AVPlayerStatusReadyToPlay) 
            {
                [self play];
            }
        }
    }
    
    #pragma mark - Private methods
    
    - (void)playerItemDidFinishPlaying:(id)sender
    {
        DLog(@"%@", sender);
    
        if (delegate && [delegate respondsToSelector:@selector(audioPlayerDidFinishPlaying)])
            [delegate audioPlayerDidFinishPlaying];
    }
    
    @end
    

    AudioPlayerViewController.h

    extern NSString *const kAudioPlayerWillShowNotification;
    extern NSString *const kAudioPlayerWillHideNotification;
    
    
    @interface AudioPlayerViewController : UIViewController
    
    @property (nonatomic, assign, readonly) BOOL isPlaying;
    @property (nonatomic, assign, readonly) BOOL isPlayerVisible;
    
    - (void)playAudioAtURL:(NSURL *)URL withTitle:(NSString *)title;
    - (void)pause;
    
    @end
    

    AudioPlayerViewController.m

    NSString *const kAudioPlayerWillShowNotification = @"kAudioPlayerWillShowNotification";
    NSString *const kAudioPlayerWillHideNotification = @"kAudioPlayerWillHideNotification";
    
    
    @interface AudioPlayerViewController () <AudioPlayerDelegate>
    
    @property (nonatomic, strong) AudioPlayerView *playerView;
    
    - (void)playButtonTouched:(id)sender;
    - (void)closeButtonTouched:(id)sender;
    - (void)hidePlayer;
    
    @end
    
    
    @implementation AudioPlayerViewController
    
    @synthesize playerView, isPlaying, isPlayerVisible;
    
    - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
    {
        self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
        if (self) 
        {
            playerView = [[AudioPlayerView alloc] initWithFrame:CGRectZero];
    
            [AudioPlayer sharedAudioPlayer].delegate = self;
        }
        return self;
    }
    
    - (void)didReceiveMemoryWarning
    {
        [super didReceiveMemoryWarning];
    }
    
    #pragma mark - View lifecycle
    
    // Implement loadView to create a view hierarchy programmatically, without using a nib.
    - (void)loadView
    {
        self.view = playerView;
    }
    
    
    // Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
    - (void)viewDidLoad
    {
        [super viewDidLoad];
    
        [playerView.playButton addTarget:self action:@selector(playButtonTouched:) forControlEvents:UIControlEventTouchUpInside];
        [playerView.closeButton addTarget:self action:@selector(closeButtonTouched:) forControlEvents:UIControlEventTouchUpInside];
    }
    
    - (void)viewDidUnload
    {
        [super viewDidUnload];
    }
    
    - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
    {
        // Return YES for supported orientations
        return (interfaceOrientation == UIInterfaceOrientationPortrait);
    }
    
    #pragma mark - Private methods
    
    - (AudioPlayerView *)playerView
    {
        return (AudioPlayerView *)self.view;
    }
    
    - (void)hidePlayer
    {
        [[NSNotificationCenter defaultCenter] postNotificationName:kAudioPlayerWillHideNotification object:nil];
        [self.playerView hidePlayer];
    }
    
    - (void)playButtonTouched:(id)sender
    {
        DLog(@"play / pause");
    
        if ([AudioPlayer sharedAudioPlayer].isPlaying) 
        {
            [[AudioPlayer sharedAudioPlayer] pause];
        }
        else
        {
            [[AudioPlayer sharedAudioPlayer] play];
        }
    
        [self.playerView showPlayer];
    }
    
    - (void)closeButtonTouched:(id)sender
    {
        DLog(@"close");
    
        if ([AudioPlayer sharedAudioPlayer].isPlaying)
            [[AudioPlayer sharedAudioPlayer] pause];
    
        [self hidePlayer];
    }
    
    #pragma mark - Instance methods
    
    - (void)playAudioAtURL:(NSURL *)URL withTitle:(NSString *)title
    {
        playerView.titleLabel.text = title;
        [[AudioPlayer sharedAudioPlayer] playAudioAtURL:URL];
    
        [[NSNotificationCenter defaultCenter] postNotificationName:kAudioPlayerWillShowNotification object:nil];
        [playerView showPlayer];
    }
    
    - (void)pause
    {
        [[AudioPlayer sharedAudioPlayer] pause];
    
        [[NSNotificationCenter defaultCenter] postNotificationName:kAudioPlayerWillHideNotification object:nil];
        [playerView hidePlayer];
    }
    
    #pragma mark - Audio player delegate
    
    - (void)audioPlayerDidStartPlaying
    {
        DLog(@"did start playing");
    
        playerView.playButtonStyle = PlayButtonStylePause;    
    }
    
    - (void)audioPlayerDidStartBuffering
    {
        DLog(@"did start buffering");
    
        playerView.playButtonStyle = PlayButtonStyleActivity;
    }
    
    - (void)audioPlayerDidPause
    {
        DLog(@"did pause");
    
        playerView.playButtonStyle = PlayButtonStylePlay;
    }
    
    - (void)audioPlayerDidFinishPlaying
    {
        [self hidePlayer];
    }
    
    #pragma mark - Properties
    
    - (BOOL)isPlaying
    {
        return [AudioPlayer sharedAudioPlayer].isPlaying;
    }
    
    - (BOOL)isPlayerVisible
    {
        return !playerView.isPlayerHidden;
    }
    
    @end
    

    AudioPlayerView.h

    typedef enum 
    {
        PlayButtonStylePlay = 0,
        PlayButtonStylePause,
        PlayButtonStyleActivity,
    } PlayButtonStyle;
    
    
    @interface AudioPlayerView : UIView
    
    @property (nonatomic, strong) UIButton                *playButton;
    @property (nonatomic, strong) UIButton                *closeButton;
    @property (nonatomic, strong) UILabel                 *titleLabel;
    @property (nonatomic, strong) UIActivityIndicatorView *activityView;
    @property (nonatomic, assign) PlayButtonStyle         playButtonStyle;
    @property (nonatomic, assign, readonly) BOOL          isPlayerHidden;
    
    - (void)showPlayer;
    - (void)hidePlayer;
    
    @end
    

    AudioPlayerView.m

    @implementation AudioPlayerView
    {
        BOOL _isAnimating;
    }
    
    @synthesize playButton, closeButton, titleLabel, playButtonStyle, activityView, isPlayerHidden = _playerHidden;
    
    - (id)initWithFrame:(CGRect)frame
    {
        self = [super initWithFrame:frame];
        if (self) 
        {
            self.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"musicplayer_background.png"]];
    
            _playerHidden = YES;
    
            activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];        
            activityView.frame = CGRectMake(0.0f, 0.0f, 30.0f, 30.0f);
            [self addSubview:activityView];
    
            playButton = [[UIButton alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 30.0f, 30.0f)];
            [playButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
            [playButton setBackgroundImage:[UIImage imageNamed:@"button_pause.png"] forState:UIControlStateNormal];
            playButton.titleLabel.textAlignment = UITextAlignmentCenter;
            [self addSubview:playButton];
    
            closeButton = [[UIButton alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 30.0f, 30.0f)];
            [closeButton setBackgroundImage:[UIImage imageNamed:@"button_close.png"] forState:UIControlStateNormal];
            [closeButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
            closeButton.titleLabel.textAlignment = UITextAlignmentCenter;
            [self addSubview:closeButton];        
    
            titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 240.0f, 30.0f)];
            titleLabel.text = nil;
            titleLabel.textAlignment = UITextAlignmentCenter;
            titleLabel.font = [UIFont boldSystemFontOfSize:13.0f];
            titleLabel.numberOfLines = 2;
            titleLabel.textColor = [UIColor whiteColor];
            titleLabel.backgroundColor = [UIColor clearColor];
            [self addSubview:titleLabel];
        }
        return self;
    }
    
    - (void)layoutSubviews
    {    
    
    #define PADDING 5.0f
    
        DLog(@"%@", NSStringFromCGRect(self.bounds));
        CGRect frame = self.bounds;
        CGFloat y = frame.size.height / 2;
    
        titleLabel.center = CGPointMake(frame.size.width / 2, y);
    
        CGFloat x = titleLabel.frame.origin.x - (playButton.frame.size.width / 2) - PADDING;
        playButton.center = CGPointMake(x, y);
        activityView.center = CGPointMake(x, y);
    
        x = titleLabel.frame.origin.x + titleLabel.frame.size.width + (closeButton.frame.size.width / 2) + PADDING;
        closeButton.center = CGPointMake(x, y);
    }
    
    #pragma mark - Instance methods
    
    - (void)showPlayer
    {
        if (_isAnimating || _playerHidden == NO)
            return;
    
        _isAnimating = YES;
    
        [UIView 
         animateWithDuration:0.5f 
         animations:^ 
         {
             CGRect frame = self.frame;
             frame.origin.y -= 40.0f;
             self.frame = frame;         
         } 
         completion:^ (BOOL finished) 
         {
             _isAnimating = NO;
             _playerHidden = NO;    
         }];
    }
    
    - (void)hidePlayer
    {
        if (_isAnimating || _playerHidden)
            return;
    
        _isAnimating = YES;
    
        [UIView 
         animateWithDuration:0.5f 
         animations:^ 
         {        
             CGRect frame = self.frame;
             frame.origin.y += 40.0f;
             self.frame = frame;
         }
         completion:^ (BOOL finished) 
         {
             _isAnimating = NO;
             _playerHidden = YES;    
         }];
    }
    
    - (void)setPlayButtonStyle:(PlayButtonStyle)style
    {
        playButton.hidden = (style == PlayButtonStyleActivity);
        activityView.hidden = (style != PlayButtonStyleActivity);
    
        switch (style) 
        {
            case PlayButtonStyleActivity:
            {
                [activityView startAnimating];
            }
                break;
            case PlayButtonStylePause:
            {
                [activityView stopAnimating];
    
                [playButton setBackgroundImage:[UIImage imageNamed:@"button_pause.png"] forState:UIControlStateNormal];
            }
                break;
            case PlayButtonStylePlay:
            default:
            {
                [activityView stopAnimating];
    
                [playButton setBackgroundImage:[UIImage imageNamed:@"button_play.png"] forState:UIControlStateNormal];
            }
                break;
        }
    
        [self setNeedsLayout];
    }
    
    @end
    

    AppDelegate - didFinishLaunching

    // setup audio player
    
    audioPlayer = [[AudioPlayerViewController alloc] init]; // public property ...
    CGRect frame = self.window.rootViewController.view.frame;
    UITabBarController *tabBarController = (UITabBarController *)self.window.rootViewController;
    CGFloat tabBarHeight = tabBarController.tabBar.frame.size.height;
    audioPlayer.view.frame = CGRectMake(0.0f, frame.size.height - tabBarHeight, 320.0f, 40.0f);
    [self.window.rootViewController.view insertSubview:audioPlayer.view belowSubview:tabBarController.tabBar];
    

    From any view controller inside the app I start audio with the following code:

    - (void)playAudioWithURL:(NSURL *)URL title:(NSString *)title
    {
        OnsNieuwsAppDelegate *appDelegate = (OnsNieuwsAppDelegate *)[[UIApplication sharedApplication] delegate];
        [appDelegate.audioPlayer playAudioAtURL:URL withTitle:title];
    }
    

    Assets

    For the above example, the following assets can be used (button images are white, so hard to see against background):

    Buttons: Close Pause Play

    Background: Background

    0 讨论(0)
  • 2020-12-09 14:46

    There's a lot of discussion (and links to blogs, etc.) about singletons over at What should my Objective-C singleton look like?, and I see a fair number of tutorials as a result of this Google search: http://www.google.com/search?q=+cocoa+touch+singleton+tutorial, but the real answer to your question, I believe, is that you should do one of two things:

    If you do want the sound for a particular view to continue playing when the user switches, create the player as you're doing now, but when the view (re)appears, check that a player exists, and don't make a new one.

    If you want the sound to stop, then stop the sound when the view changes (i.e., in viewWillDisappear:).

    0 讨论(0)
提交回复
热议问题