歌手圖片旋轉(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