【问题标题】:Objective-C, Need help creating an AVAudioPlayer singletonObjective-C,需要帮助创建一个 AVAudioPlayer 单例
【发布时间】:2012-04-10 06:01:09
【问题描述】:

我正在开发一个音板应用程序,如果用户希望手动中断剪辑,它有几页按钮可以播放声音效果,每页上都有一个停止按钮。我在每个视图中使用 avaudioplayer 在按下该剪辑的按钮时播放声音。在视图更改之前它工作正常。如果用户跳转到新页面,声音会继续播放,并且即使他们返回到原始视图,停止按钮也会停止工作。按下声音按钮不再中断运行声音,从而导致两个声音相互重叠。

通过谷歌搜索和搜索该站点,我知道问题在于每次视图更改都会创建一个新的播放器实例,而解决方法是创建一个单例类。不幸的是,我还没有找到任何进一步的例子来说明如何实际做到这一点。如果有人可以提供或指出创建 avaudioplayer 单例的初学者指南,我将不胜感激。我需要做的就是将文件名传递给共享播放器,然后使用声音剪辑按钮开始播放,并让停止按钮停止声音,无论用户在什么视图上。我正在使用带有情节提要和 ARC 的 ios 5.1 sdk。

【问题讨论】:

    标签: objective-c singleton xcode4.2 avaudioplayer


    【解决方案1】:

    我在自己的一个项目中使用的解决方案发布在下方。随意复制粘贴,我打算在这个项目完成后开源:)

    可以在 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];
    

    从应用程序内的任何视图控制器,我使用以下代码开始音频:

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

    资产

    对于上面的示例,可以使用以下资源(按钮图像是白色的,因此很难在背景中看到):

    按钮:

    背景:

    【讨论】:

    • 超级!这是一个很大的帮助。我一直在搞乱我的代码,但没有任何成功,终于可以找到这个链接,你的代码效果很好。但是我需要重新实现我的播放器的其余部分,但这应该没问题。非常感谢!附言我将UITextAlignmentCenter 编辑为NSTextAlignmentCenter,因为前者现在已被弃用。
    • @Wolfgang 我知道这可能太多了,但是您是否已经实现了让搜索滑块显示音频进度并允许用户更新它的方法?如果是这样,你能分享一下吗?我做了一个很好的AVAudioPlayer,但是因为我无法让一个单身人士为它工作,所以我在花了几天时间之后实现了你的代码。现在您的代码可以完美运行,但我无法让滑块为它工作。
    • @Neeku:我还没有实现滑块,但它很容易实现。如果我今天有时间,我会添加滑动代码。同时,您可以通过查看以下主题自己找出解决方案:stackoverflow.com/questions/2654849/…
    • @WolfgangSchreurs 非常感谢您的回复!我今天早些时候看到了该链接,但无法弄清楚。我试图实现 Apple 的示例代码:developer.apple.com/library/ios/samplecode/AVPlayerDemo/… 但我所做的只是在没有成功的情况下在您的代码中造成了巨大的混乱。部分原因是我在以编程方式实现 UI 方面并不是那么出色,并且也很困惑在哪里使用每种方法。我唯一能做的就是快进和快退 15 秒(不与滑块同步!)
    • @WolfgangSchreurs 我知道你可能很忙,但你能用搜索滑块做些什么吗? :-S 我现在还在纠结别的事情!
    【解决方案2】:

    What should my Objective-C singleton look like? 上有很多关于单身人士的讨论(以及博客链接等),我在 Google 搜索结果中看到了相当多的教程:http://www.google.com/search?q=+cocoa+touch+singleton+tutorial,但真正的答案是我相信你的问题是你应该做以下两件事之一:

    如果您确实希望在用户切换时继续播放特定视图的声音,请像现在一样创建播放器,但是当视图(重新)出现时,请检查播放器已存在,请勿制作新的。

    如果您希望声音停止,则在视图更改时停止声音(即在viewWillDisappear: 中)。

    【讨论】:

    • 我确实希望声音在视图发生变化时继续播放,但即使我在返回视图时检查播放器是否已经存在,停止按钮也不再停止正在播放的声音。停止按钮的代码就是 [audioplayer stop]: 。似乎在返回原始视图后,[audioplayer stop] 不再与该特定实例相关联。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-06
    • 1970-01-01
    相关资源
    最近更新 更多