音訊播放的實現以及後臺播放音訊 iOS
阿新 • • 發佈:2019-01-26
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;
}
注:以上只是部分主要程式碼,並非完整程式碼。
附: