1. 程式人生 > >音訊播放的實現以及後臺播放音訊 iOS

音訊播放的實現以及後臺播放音訊 iOS

1、首先,音訊播放的實現,我這裡使用的是AVPlayer
AVAudioPlayer只能播放本地資源。當然還有別的播放方法這裡就不列舉了。

以下程式碼實現的是如下圖所示的效果,點選圖示可以暫停或者繼續播放:
效果圖

需要的屬性:

//
@property (nonatomic, strong) AVPlayer *player;
@property (nonatomic, strong) UIImageView *playerView;
@property (nonatomic, strong) UILabel *timeL;
@property (nonatomic, strong) UIImageView
*animationV;//載入動畫

初始化屬性

//
- (AVPlayer *)player
{
    if (_player == nil) {

        NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@",kVedioUrl,_playItemInfo[@"ypwj"]]];
        AVAsset *avset = [AVAsset assetWithURL:url] ;
        AVPlayerItem *item = [AVPlayerItem playerItemWithAsset:avset];
        _item = item;

        // 建立AVPlayer
_player = [AVPlayer playerWithPlayerItem:_item]; // 新增AVPlayerLayer AVPlayerLayer *layer = [AVPlayerLayer playerLayerWithPlayer:self.player]; layer.frame = CGRectMake(self.view.bounds.size.width - 55, self.view.bounds.size.height - 100, 50, 50); [self.view.layer addSublayer:layer]; //監聽是否可播放
[_item addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil]; //監聽快取狀態,可以新增載入動畫 [_item addObserver:self forKeyPath:@"playbackBufferEmpty" options:NSKeyValueObservingOptionNew context:nil]; //監聽是否播放完成 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playToTheEnd) name:AVPlayerItemDidPlayToEndTimeNotification object:_item]; //監聽播放狀態,播放還是暫停 [_player addObserver:self forKeyPath:@"timeControlStatus" options:NSKeyValueObservingOptionNew context:nil]; } return _player; } - (UIImageView *)playerView{ if (_playerView == nil) { _playerView = [[UIImageView alloc] init]; _playerView.image = [UIImage imageNamed:@"L0"]; //播放動畫 NSMutableArray *imgArr = [NSMutableArray array]; for (int i = 0; i<4; i ++) { UIImage *image = [UIImage imageNamed:[NSString stringWithFormat:@"L%d",i]]; [imgArr addObject:image]; } _playerView.animationImages = imgArr; _playerView.animationDuration = 2.0; _playerView.animationRepeatCount = 0; _playerView.userInteractionEnabled = YES; UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(playOrPause)]; [_playerView addGestureRecognizer:tap]; } return _playerView; } - (UIImageView *)animationV{ //旋轉動畫 if (_animationV == nil) { _animationV = [[UIImageView alloc] init]; _animationV.image = [UIImage imageNamed:@"loadcc"]; CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"]; //預設是順時針效果,若將fromValue和toValue的值互換,則為逆時針效果 animation.fromValue = [NSNumber numberWithFloat:0.f]; animation.toValue = [NSNumber numberWithFloat: M_PI *2]; animation.duration = 2; animation.autoreverses = NO; animation.fillMode = kCAFillModeForwards; animation.repeatCount = MAXFLOAT; //如果這裡想設定成一直自旋轉,可以設定為MAXFLOAT,否則設定具體的數值則代表執行多少次 [_animationV.layer addAnimation:animation forKey:nil]; } return _animationV; }

播放音訊

NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@",kVedioUrl,_mp3Url]];
    AVAsset *avset = [AVAsset assetWithURL:url] ;
    CMTime audioDuration = avset.duration; //獲取音訊時長
    _countTime = CMTimeGetSeconds(audioDuration);

    AVPlayerItem *item = [AVPlayerItem playerItemWithAsset:avset];
    [self.player replaceCurrentItemWithPlayerItem:item];//替換當前播放的音訊
    [self.player play];

注:如果需要獲取音訊的時長等資訊,必須使用AVAsset,不需要的話,可以直接使用[AVPlayerItem playerItemWithURL:url]就可以了

顯示音訊所剩的播放時間

//
__weak typeof(self) weakSelf = self;
    [self.player addPeriodicTimeObserverForInterval:CMTimeMake(1, 1) queue:nil usingBlock:^(CMTime time) {

        AVPlayerItem *item = weakSelf.item;
        //已播放時長
        NSInteger currentTime = item.currentTime.value/item.currentTime.timescale;
        //音訊總時長
        NSInteger allTime = CMTimeGetSeconds(weakSelf.player.currentItem.duration);

        weakSelf.timeL.text = [weakSelf showPlayerTime:allTime - currentTime];
    }];

監聽回撥

//
#pragma 監聽播放狀態回撥
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    if ([object isKindOfClass:[AVPlayerItem class]]) {
        if ([keyPath isEqualToString:@"status"]) {
            switch (_item.status) {
                case AVPlayerItemStatusReadyToPlay:
                    //推薦將視訊播放在這裡

                    break;

                case AVPlayerItemStatusUnknown:
                    NSLog(@"AVPlayerItemStatusUnknown");
                    break;

                case AVPlayerItemStatusFailed:
                    NSLog(@"AVPlayerItemStatusFailed");
                    break;

                default:
                    break;
            }
        }else if ([keyPath isEqualToString:@"playbackBufferEmpty"]){
            if (_item.playbackBufferEmpty) {
                _animationV.hidden = NO;
            }
        }
    }
    if ([object isKindOfClass:[AVPlayer class]]) {
        if ([keyPath isEqualToString:@"timeControlStatus"]){
            switch (_player.timeControlStatus) {
                case AVPlayerTimeControlStatusPlaying:
                    _isPlaying = YES;
                    _animationV.hidden = YES;
                    break;

                case AVPlayerTimeControlStatusPaused:
                    _isPlaying = NO;
                    _animationV.hidden = YES;
                    [_playerView stopAnimating];
                    _playerView.image = [UIImage imageNamed:@"L0"];
                    break;

                case AVPlayerTimeControlStatusWaitingToPlayAtSpecifiedRate:
                    _animationV.hidden = NO;
                default:
                    break;
            }
        }
    }
}

時間顯示

//
- (NSString *)showPlayerTime:(NSInteger)countTime{
    NSInteger minute = countTime / 60;
    NSInteger second = countTime % 60;
    NSString *str = @"";
    if (minute < 10) {
        if (second < 10) {
            str = [NSString stringWithFormat:@" 0%ld:0%ld",minute,second];

        }else{
            str = [NSString stringWithFormat:@" 0%ld:%ld",minute,second];
        }
    }else{
        if (second < 10) {
            str = [NSString stringWithFormat:@" %ld:0%ld",minute,second];
        }else{
            str = [NSString stringWithFormat:@" %ld:%ld",minute,second];
        }
    }
    return str;
}

注意:有的說是NSTimer 計時器計時應該寫在子執行緒中,但是寫在子執行緒中發現倒計時與音訊播放不同步,出現倒計時已經結束,但是音訊還沒播放完,所以這裡我就都寫在了主執行緒。(有見解的夥伴歡迎提點哦)

(2)然後,就是要實現後臺播放以及返回其他頁面,音訊繼續播放的過程
首先開啟,允許後臺執行模式
開啟後臺播放

然後在APPDelegate 中:

- (void)applicationWillResignActive:(UIApplication *)application {
    //開啟後臺處理多媒體事件
    [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
    AVAudioSession *session=[AVAudioSession sharedInstance];
    [session setActive:YES error:nil];
    //後臺播放
    [session setCategory:AVAudioSessionCategoryPlayback error:nil];
    //這樣做,可以在按home鍵進入後臺後 ,播放一段時間,幾分鐘吧。但是不能持續播放,若需要持續播放,還需要申請後臺任務id,具體做法是:
    _bgTaskId=[AppDelegate backgroundPlayerID:_bgTaskId];
    //其中的_bgTaskId是後臺任務UIBackgroundTaskIdentifier _bgTaskId;
}

+(UIBackgroundTaskIdentifier)backgroundPlayerID:(UIBackgroundTaskIdentifier)backTaskId
{
    //設定並激活音訊會話類別
    AVAudioSession *session=[AVAudioSession sharedInstance];
    [session setCategory:AVAudioSessionCategoryPlayback error:nil];
    [session setActive:YES error:nil];
    //允許應用程式接收遠端控制
    [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
    //設定後臺任務ID
    UIBackgroundTaskIdentifier newTaskId=UIBackgroundTaskInvalid;
    newTaskId=[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];
    if(newTaskId!=UIBackgroundTaskInvalid&&backTaskId!=UIBackgroundTaskInvalid)
    {
        [[UIApplication sharedApplication] endBackgroundTask:backTaskId];
    }
    return newTaskId;
}

But:我們會發現當實現當只實現以上的applicationWillResignActive方法,或者只開啟圖中的background modes ,做到這兩個中的一個,就可以實現後臺播放。。。

進入後臺播放可以了,但是當我們返回到其他的頁面,再次進入到這個頁面時,會發現,音訊又疊加了一個音訊,而不是我們想要的當前音訊正常的繼續播放。所以…我們想到了,對,揍死它——單例。

將當前控制器設定成單例,這樣每次進入這個頁面,音訊可以毫無影響的繼續播放啦~

static MyController *instance;

+(id)shareInstance{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        if(instance == nil)
            instance = [[MyController alloc] init];
    });
    return instance;
}

注:以上只是部分主要程式碼,並非完整程式碼。

附: