2012-03-26 4 views
3

Ich arbeite an einer Soundboard-App, die mehrere Seiten mit Schaltflächen zum Abspielen von Soundeffekten mit einem Stop-Taste auf jeder Seite hat, sollte der Benutzer den Clip manuell unterbrechen möchten. Ich benutze avaudioplayer in jeder Ansicht, um den Sound beim Drücken der Taste für diesen Clip abzuspielen. Es funktioniert gut, bis die Ansicht geändert wird. Wenn ein Benutzer zu einer neuen Seite springt, wird der Sound weiter abgespielt, und die Stopp-Taste hört auf zu arbeiten, selbst wenn sie zur ursprünglichen Ansicht zurückkehren. Durch Drücken einer Sound-Taste wird der laufende Sound nicht mehr unterbrochen, sodass sich zwei Sounds überlagern.Objective-C, Brauchen Sie Hilfe beim Erstellen eines AVAudioPlayer singleton

Beim Durchsuchen und Durchsuchen dieser Website weiß ich, dass jede Änderung der Ansicht eine neue Instanz des Players erstellt und die Abhilfe darin besteht, eine Singleton-Klasse zu erstellen. Leider habe ich noch keine weiteren Beispiele dafür gefunden, wie man das tatsächlich macht. Wenn jemand einen Anfängerleitfaden für die Erstellung eines Aviedrioplayer-Singletons bereitstellen oder aufzeigen könnte, würde ich das sehr schätzen. Alles, was ich tun kann, ist, den Dateinamen an den freigegebenen Player zu übergeben und die Wiedergabe mit einer Soundclip-Taste zu starten und die Stopptaste anzuhalten, unabhängig davon, in welcher Ansicht sich der Benutzer gerade befindet. Ich benutze die iOS 5.1 SDK mit Storyboards und ARC aktiviert.

Antwort

2

Es gibt eine Menge Diskussion (und Links zu Blogs usw.) über Singletons über What should my Objective-C singleton look like?, und ich sehe eine Reihe von Tutorials als Ergebnis dieser Google-Suche: http://www.google.com/search?q=+cocoa+touch+singleton+tutorial, aber die echte Antwort auf Ihre Frage, Ich glaube, ist, dass Sie eines von zwei Dingen tun sollten:

Wenn Sie tun möchten, dass der Sound für eine bestimmte Ansicht weiterspielen, wenn der Benutzer wechselt, erstellen Sie den Player, wie Sie jetzt tun, aber wenn die Anzeige (re) erscheint, überprüfe, ob ein Spieler existiert und erstelle keinen neuen.

Wenn Sie den Ton stoppen wollen, dann den Ton stoppen, wenn die Ansicht ändert (das heißt, in viewWillDisappear:).

+0

Ich möchte, dass der Sound fortgesetzt wird, wenn sich die Ansichten ändern, aber selbst wenn ich nach der Rückkehr zur Ansicht überprüfe, ob ein Player bereits existiert, der Stop Der Knopf stoppt den laufenden Sound nicht mehr. Der Code für die Stopp-Taste lautet nur [Audioplayer stop]:. Es scheint, dass nach der Rückkehr zur ursprünglichen Ansicht [Audioplayer stop] nicht mehr mit dieser bestimmten Instanz verknüpft ist. –

10

Meine Lösung, wie sie in einem meiner eigenen Projekte verwendet wird, finden Sie unter. Fühlen Sie sich frei zu kopieren und einfügen, beabsichtige ich, dieses Projekt zu Open-Source, sobald es fertig ist :)

Eine Vorschau des Spielers auf YouTube zu sehen ist: 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]; 

Von jedem View-Controller in der App I mit dem folgenden Code Audio-Start:

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

Assets

Für das obige Beispiel können die folgenden Vermögenswerte verwendet werden (Button Bilder sind weiß, so hart gegen Hintergrund zu sehen):

Buttons: Close Pause Play

Hintergrund: Background

+0

Perfekt! Ich liebe dich! – duci9y

+0

Super! Es war eine große Hilfe. Ich habe ohne Erfolg mit meinem Code herumgespielt und konnte endlich diesen Link finden und Ihr Code funktioniert großartig. Allerdings muss ich den Rest meines Spielers neu implementieren, aber das sollte in Ordnung sein. Danke vielmals! P.S. Ich habe 'UITextAlignmentCenter' auf' NSTextAlignmentCenter' umgestellt, da die erste jetzt veraltet ist. – Neeku

+0

@Wolfgang Ich weiß, dass dies zu viel ist, aber haben Sie die Methoden implementiert, um einen Suchschieberegler zu haben, der den Audiofortschritt zeigt und es dem Benutzer ermöglicht, ihn zu aktualisieren? Wenn ja, könnten Sie das teilen? Ich hatte einen schönen 'AVAudioPlayer' gemacht, aber da ich kein Singleton dafür bekommen konnte, habe ich nach einigen Tagen den Code implementiert. Jetzt funktioniert Ihr Code perfekt, aber ich kann nicht den Slider dafür arbeiten. – Neeku