AVPlayer 制作一個簡單的在線音樂播放器

0AC7D6DB-AC44-45E6-A457-2D1517F1DF3F.png

歌手圖片旋轉(zhuǎn):
先設(shè)置一個全局變量動畫:

{
    CABasicAnimation* _rotationAnimation; /**< 歌手圖片旋轉(zhuǎn)動畫 */
}

初始化動畫操作,歌手圖片旋轉(zhuǎn)動畫

   // 歌手圖片動畫效果
    _rotationAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
    _rotationAnimation.toValue = [NSNumber numberWithFloat: M_PI * 2];
    _rotationAnimation.duration = kTimerInterval;
    _rotationAnimation.cumulative = YES;
    _rotationAnimation.repeatCount = CGFLOAT_MAX; // 設(shè)置旋轉(zhuǎn)次數(shù)

當(dāng)歌曲開始播放的時候局蚀,將動畫操作添加到歌手圖片ImageView

[self.playerImageView.layer addAnimation:_rotationAnimation forKey:@"rotationAnimation"];

這樣俄周,類似網(wǎng)易云音樂歌手圖片旋轉(zhuǎn)的效果出來了

接下來就是播放器的實(shí)現(xiàn)了亲怠,在這里使用的是AVPlayer來實(shí)現(xiàn)網(wǎng)絡(luò)音樂播放
AVPlayer中含有一個屬性:AVPlayerItem乘客,這個是媒體資源,包含媒體的總時間享潜、緩沖情況等

1.初始化一個AVPlayer

   NSURL *url = [NSURLURLWithString:@"http://mr7.doubanio.com/3fd082ae3370d22e48e300ab1d5d6590/1/fm/song/p190415_128k.mp4"];
   self.songItem = [AVPlayerItemplayerItemWithURL:url];
   self.player = [AVPlayerplayerWithPlayerItem:self.songItem];

2.播放音樂

[self.playerplay];

3.暫停音樂

[self.playerpause];

4.一個簡單的在線播放網(wǎng)絡(luò)音樂就完成了瓦堵,接下來需要顯示歌曲的時間基协,首先添加一個監(jiān)聽媒體資源的加載情況

// 監(jiān)聽媒體資源緩沖情況
[self.songItem addObserver:selfforKeyPath:@"loadedTimeRanges"options:NSKeyValueObservingOptionNewcontext:nil];

5.在媒體加載時響應(yīng)監(jiān)聽方法時獲取到歌曲的總時間,在這里菇用,只需要獲取一次澜驮,由于只有一首歌,所以使用GCD的方式去獲取歌曲總時間

/**
 *  KVO監(jiān)聽方法
 *
 *  @param keyPath
 *  @param object
 *  @param change
 *  @param context
 */
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
    AVPlayerItem *songItem = object;
    if ([keyPath isEqualToString:@"loadedTimeRanges"]) {
        // 媒體總時長
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            self.songTime = CMTimeGetSeconds(songItem.duration); // 獲取到媒體的總時長
        });
        // 媒體緩沖
        NSArray *array=songItem.loadedTimeRanges;
        CMTimeRange timeRange = [array.firstObject CMTimeRangeValue];//本次緩沖時間的范圍
        float startSeconds = CMTimeGetSeconds(timeRange.start);
        float durationSeconds = CMTimeGetSeconds(timeRange.duration);
        NSTimeInterval totalBuffer = startSeconds + durationSeconds;//緩沖的總長度
        NSLog(@"共緩沖:%.3f, %lf",totalBuffer, CMTimeGetSeconds(songItem.duration));
    }
}

獲取到了歌曲的時間刨疼,我們還需要顯示當(dāng)前歌曲播放的時間泉唁,根據(jù)獲取到的時間去實(shí)現(xiàn)進(jìn)度條的改變

__weak typeof(self) weakSelf = self;
// 監(jiān)聽播放進(jìn)度
self.timeObserve = [self.player addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 1.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
        CGFloat current = CMTimeGetSeconds(time);
        weakSelf.currentTime = current;
 }];

到了這里,可以根據(jù)獲得到的時間來計(jì)算進(jìn)度條的value揩慕,接下來是根據(jù)通過改變進(jìn)度條的value來實(shí)現(xiàn)播放器播放進(jìn)度亭畜,給進(jìn)度條添加一個target事件

// 監(jiān)聽進(jìn)度條的手動改變
[self.progress addTarget:self action:@selector(progressValueChage) forControlEvents:UIControlEventValueChanged];

當(dāng)拖動進(jìn)度條的監(jiān)聽方法去實(shí)現(xiàn)控制播放器的播放進(jìn)度

/**
 *  進(jìn)度條改變操作
 */
- (void)progressValueChage {
    CGFloat currentTime = self.songTimes * self.progress.value;
    [self.player changeProgress:currentTime];
}
/**
 *  手動改變進(jìn)度
 *
 *  @param second 當(dāng)前播放時間
 */
- (void)changeProgress:(CGFloat)second {
    [self.player pause];
    [self.player seekToTime:CMTimeMakeWithSeconds(second, NSEC_PER_SEC) completionHandler:^(BOOL finished) {
        [self.player play];
    }];
}

這樣一個簡單的播放器網(wǎng)絡(luò)音樂就完成了
最后附上完整的代碼
ViewController.m文件

#import "ViewController.h"
#import "GPPlayer.h"
#import "UIView+Category.h"

#define kImageViewWidth 230
#define kTimerInterval 20

@interface ViewController ()

@property (nonatomic, strong) GPPlayer *player; /**< 播放器 */
@property (weak, nonatomic) IBOutlet UILabel *songTimeLabel;
@property (weak, nonatomic) IBOutlet UILabel *currentTime;
@property (nonatomic, weak) UIImageView *playerImageView; /**< 歌手圖片 */
@property (weak, nonatomic) IBOutlet UISlider *progress; /**< 進(jìn)度條 */
@property (nonatomic, assign) CGFloat songTimes; /**< 歌曲總時間 */

@end

@implementation ViewController

{
    CABasicAnimation* _rotationAnimation; /**< 歌手圖片旋轉(zhuǎn)動畫 */
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // 歌手圖片
    UIImageView *playerImageView = [[UIImageView alloc] init];
    playerImageView.y = 50;
    playerImageView.size = CGSizeMake(kImageViewWidth, kImageViewWidth);
    playerImageView.centerX = self.view.centerX;
    playerImageView.image = [UIImage imageNamed:@"xufei.jpg"];
    playerImageView.layer.cornerRadius = kImageViewWidth * 0.5;
    playerImageView.layer.masksToBounds = YES;
    [self.view addSubview:playerImageView];
    self.playerImageView = playerImageView;
    
    // 歌手圖片動畫效果
    _rotationAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
    _rotationAnimation.toValue = [NSNumber numberWithFloat: M_PI * 2];
    _rotationAnimation.duration = kTimerInterval;
    _rotationAnimation.cumulative = YES;
    _rotationAnimation.repeatCount = CGFLOAT_MAX; // 設(shè)置旋轉(zhuǎn)次數(shù)
    
    // 監(jiān)聽進(jìn)度條的手動改變
    [self.progress addTarget:self action:@selector(progressValueChage) forControlEvents:UIControlEventValueChanged];
}

/**
 *  播放
 *
 *  @param sender
 */
- (IBAction)play:(UIButton *)sender {
    sender.showsTouchWhenHighlighted = YES;
    [self.player play];
    // 旋轉(zhuǎn)歌手圖片
    [self imageViewRotate];
}

/**
 *  播放
 *
 *  @param sender
 */
- (IBAction)pause:(UIButton *)sender {
    [self.player pause];
    [self.playerImageView.layer removeAnimationForKey:@"rotationAnimation"];
}

#pragma mark - 自定義方法

/**
 *  歌手圖片旋轉(zhuǎn)動畫
 */
- (void)imageViewRotate {
    [self.playerImageView.layer addAnimation:_rotationAnimation forKey:@"rotationAnimation"];
}

/**
 *  時間轉(zhuǎn)換
 *
 *  @param time 秒數(shù)
 *
 *  @return
 */
- (NSString *)convertStringWithTime:(float)time {
    if (isnan(time)) time = 0.f;
    int min = time / 60.0;
    int sec = time - min * 60;
    NSString * minStr = min > 9 ? [NSString stringWithFormat:@"%d",min] : [NSString stringWithFormat:@"0%d",min];
    NSString * secStr = sec > 9 ? [NSString stringWithFormat:@"%d",sec] : [NSString stringWithFormat:@"0%d",sec];
    NSString * timeStr = [NSString stringWithFormat:@"%@:%@",minStr, secStr];
    return timeStr;
}

#pragma mark - KVO
/**
 *  觀察者監(jiān)聽方法
 *
 *  @param keyPath
 *  @param object
 *  @param change
 *  @param context  
 */
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
    if ([keyPath isEqualToString:@"songTime"]) {
        self.songTimes = self.player.songTime;
        self.songTimeLabel.text = [self convertStringWithTime:self.player.songTime];
    } else if ([keyPath isEqualToString:@"currentTime"]) {
        self.currentTime.text = [self convertStringWithTime:self.player.currentTime];
        CGFloat pro = self.player.currentTime / self.songTimes; /**< 播放進(jìn)度 */
        self.progress.value = pro;
    }
}

/**
 *  進(jìn)度條改變操作
 */
- (void)progressValueChage {
    CGFloat currentTime = self.songTimes * self.progress.value;
    [self.player changeProgress:currentTime];
}

#pragma mark - 懶加載

/**
 *  播放器
 *
 *  @return
 */
- (GPPlayer *)player {
    if (_player == nil) {
        _player = [[GPPlayer alloc] init];
        [_player addObserver:self forKeyPath:@"songTime" options:NSKeyValueObservingOptionNew context:nil];
        [_player addObserver:self forKeyPath:@"currentTime" options:NSKeyValueObservingOptionNew context:nil];
    }
    
    return _player;
}

@end

GPPlayer.h

@interface GPPlayer : NSObject

@property (nonatomic, assign) CGFloat songTime; /**< 歌曲總時間 */
@property (nonatomic, assign) CGFloat currentTime; /**< 當(dāng)前播放時間 */

/**
 *  播放音樂
 */
- (void)play;

/**
 *  暫停播放
 */
- (void)pause;

/**
 *  手動改變播放進(jìn)度
 */
- (void)changeProgress:(CGFloat)second;

@end

GPPlayer.m

#import "GPPlayer.h"
#import <AVFoundation/AVFoundation.h>

@interface GPPlayer ()

@property (nonatomic, strong) AVPlayer *player; /**< 播放器 */
@property (nonatomic, strong) AVPlayerItem *songItem;
@property (nonatomic, strong) id timeObserve;

@end

@implementation GPPlayer

- (instancetype)init {
    if (self = [super init]) {
        [self setup];
    }
    
    return self;
}

/**
 *  初始化播放器
 */
- (void)setup {
    NSURL *url = [NSURL URLWithString:@"http://mr7.doubanio.com/3fd082ae3370d22e48e300ab1d5d6590/1/fm/song/p190415_128k.mp4"];
    self.songItem = [AVPlayerItem playerItemWithURL:url];
    self.player = [AVPlayer playerWithPlayerItem:self.songItem];
    [self addObserver];
}

/**
 *  添加觀察者
 */
- (void)addObserver {
    // 監(jiān)聽媒體資源的狀態(tài)
    [self.songItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
    // 監(jiān)聽媒體資源緩沖情況
    [self.songItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
    __weak typeof(self) weakSelf = self;
    // 監(jiān)聽播放進(jìn)度
    self.timeObserve = [self.player addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 1.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
        CGFloat current = CMTimeGetSeconds(time);
        weakSelf.currentTime = current;
    }];
    // 監(jiān)聽播放是否完成
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playerFinish:) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
}

/**
 *  KVO監(jiān)聽方法
 *
 *  @param keyPath
 *  @param object
 *  @param change
 *  @param context
 */
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
    AVPlayerItem *songItem = object;
    if ([keyPath isEqualToString:@"status"]) {
        // 媒體加載
        switch (self.player.status) {
            case AVPlayerStatusFailed:
                NSLog(@"KVO:加載失敗,網(wǎng)絡(luò)或者服務(wù)器出現(xiàn)問題");
                break;
                case AVPlayerStatusReadyToPlay:
                NSLog(@"KVO:準(zhǔn)備完畢,可以播放");
                break;
                case AVPlayerStatusUnknown:
                NSLog(@"KVO:未知狀態(tài)");
                break;
        }
    } else if ([keyPath isEqualToString:@"loadedTimeRanges"]) {
        // 媒體總時長
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            self.songTime = CMTimeGetSeconds(songItem.duration); // 獲取到媒體的總時長
        });
        // 媒體緩沖
        NSArray *array=songItem.loadedTimeRanges;
        CMTimeRange timeRange = [array.firstObject CMTimeRangeValue];//本次緩沖時間范圍
        float startSeconds = CMTimeGetSeconds(timeRange.start);
        float durationSeconds = CMTimeGetSeconds(timeRange.duration);
        NSTimeInterval totalBuffer = startSeconds + durationSeconds;//緩沖總長度
        NSLog(@"共緩沖:%.2f, %lf",totalBuffer, CMTimeGetSeconds(songItem.duration));
    }
}

/**
 *  監(jiān)聽播放器播放完成
 *
 *  @param sender 
 */
- (void)playerFinish:(NSNotification *)sender {
    NSLog(@"播放完成");
}

/**
 *  手動改變進(jìn)度
 *
 *  @param second 當(dāng)前播放時間
 */
- (void)changeProgress:(CGFloat)second {
    [self.player pause];
    [self.player seekToTime:CMTimeMakeWithSeconds(second, NSEC_PER_SEC) completionHandler:^(BOOL finished) {
        [self.player play];
    }];
}

#pragma mark - 播放器操作

/**
 *  播放音樂
 */
- (void)play {
    [self.player play];
}

/**
 *  暫停播放
 */
- (void)pause {
    [self.player pause];
}

@end
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末迎卤,一起剝皮案震驚了整個濱河市拴鸵,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖劲藐,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件八堡,死亡現(xiàn)場離奇詭異,居然都是意外死亡聘芜,警方通過查閱死者的電腦和手機(jī)兄渺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來汰现,“玉大人挂谍,你說我怎么就攤上這事∠顾牵” “怎么了口叙?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長嗅战。 經(jīng)常有香客問我妄田,道長,這世上最難降的妖魔是什么驮捍? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任疟呐,我火速辦了婚禮,結(jié)果婚禮上东且,老公的妹妹穿的比我還像新娘萨醒。我一直安慰自己,他們只是感情好苇倡,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著囤踩,像睡著了一般旨椒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上堵漱,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天综慎,我揣著相機(jī)與錄音,去河邊找鬼勤庐。 笑死示惊,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的愉镰。 我是一名探鬼主播米罚,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼丈探!你這毒婦竟也來了录择?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎隘竭,沒想到半個月后塘秦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡动看,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年尊剔,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片菱皆。...
    茶點(diǎn)故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡须误,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出搔预,到底是詐尸還是另有隱情霹期,我是刑警寧澤,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布拯田,位于F島的核電站历造,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏船庇。R本人自食惡果不足惜吭产,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望鸭轮。 院中可真熱鬧臣淤,春花似錦、人聲如沸窃爷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽按厘。三九已至医吊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間逮京,已是汗流浹背卿堂。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留懒棉,地道東北人草描。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像策严,于是被迫代替她去往敵國和親穗慕。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評論 2 355

推薦閱讀更多精彩內(nèi)容