iOS 類似于音樂播放器的FM

本人不才备图,在項目中寫了個FM,也類似于播放器吧愉镰!為了減少代碼,我封裝了幾個類來解決這些問題髓窜。寫音樂播放器首先你要想你需要那幾個類。音樂播放器你得需要,向客戶展示播放暫停等View你的需要吧,在此我又封裝了一個解析數(shù)據(jù)的類寄纵。廢話不多說開始吧鳖敷!
向大家展示一下寫項目之前調(diào)研的界面吧!程拭!

1.gif

一.首先我們先寫音樂播放器類(JYJMusicPlayerTools)
1..h中

class JYJProgramModel;
@protocol JYJMusicPlayerToolsDelegate <NSObject>
// 通過代理返回當前歌曲播放的進度
- (void)getCurTime:(NSString *)curTime totle:(NSString *)totle progress:(CGFloat)progress;
//  播放結(jié)束后定踱,如何操作由外部決定
- (void)endPlayAction;
//播放失敗后
- (void)playFail;
@end
@interface JYJMusicPlayerTools : NSObject
@property (nonatomic, strong) AVPlayer *player;
@property (nonatomic, strong) AVPlayerItem *playerItem;
@property (nonatomic, strong) JYJProgramModel *programModel;

@property (nonatomic, weak) id<JYJMusicPlayerToolsDelegate>delegate;
//單例方法
+ (instancetype)shareMusicPlayerTools;
// 播放音樂
- (void)playMusic;
//暫停音樂
- (void)pauseMusic;
// 準備播放
- (void)preparePlayMusic;

// 跳轉(zhuǎn)
- (void)seekToTimeWithValue:(CGFloat)value;


2..m中

@property (nonatomic, strong) NSTimer *timer;// 滑動條定時器l;
@end
@implementation JYJMusicPlayerTools
//單例
+ (instancetype)shareMusicPlayerTools {
    static JYJMusicPlayerTools *mpTools = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        mpTools = [[JYJMusicPlayerTools alloc]init];
    });
    return mpTools;
}
- (instancetype)init {
    self = [super init];
    if (self) {
        self.player = [[AVPlayer alloc]init];
        //為播放完畢添加觀察者   播放完畢的操作  有外邊決定
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(endPlay:) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
    }
    return self;
}
// 播放完畢
- (void)endPlay:(NSNotification *)nati {
    //播放完畢先讓音樂停止
    [self pauseMusic];
    if ([self.delegate respondsToSelector:@selector(endPlayAction)]) {
        [self.delegate endPlayAction];
    }
}
// 準備播放
- (void)preparePlayMusic {
    // 只要AVPlayer有currentTime,那么一定有觀察者
    // 所以直接移除
    if (self.player.currentItem) {
        [self.player.currentItem removeObserver:self forKeyPath:@"status"];
    }
        
    
    self.playerItem= [[AVPlayerItem alloc]initWithURL:[NSURL URLWithString:self.programModel.music_media]];
    
    //為item的status添加觀察者
    [self.playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld  context:nil];
    //用新建的item,替換AVPlayer之前的item,新的item添加了觀察者
    [self.player replaceCurrentItemWithPlayerItem:self.playerItem];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
    if ([keyPath isEqualToString:@"status"]) {
        switch ([[change valueForKey:@"new"] integerValue]) {
            case AVPlayerItemStatusUnknown:
                NSLog(@"不知道什么錯誤");
                break;
            case AVPlayerItemStatusReadyToPlay:
                // 只有觀察到status變?yōu)檫@種狀態(tài),才會真正的播放.
                [self playMusic];
                break;
            case AVPlayerItemStatusFailed:
                // mini設(shè)備不插耳機或者某些耳機會導致準備失敗.
                NSLog(@"準備失敗");
                if ([self.delegate respondsToSelector:@selector(playFail)]) {
                    [self.delegate playFail];
                    
                }
                break;
            default:
                break;
        }
    }
    
    

}
// 播放
- (void)playMusic {
    //如果定時器已經(jīng)存在,說明已經(jīng)在播放中恃鞋,直接返回
    // 對于已經(jīng)存在的定時器崖媚,只有pauseMusic方法才會使之停止和銷毀
    if (self.timer != nil) {
        return;
    }
    // 播放后開啟定時器
    self.timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(timerAction:) userInfo:nil repeats:YES];
    [self.player play];
}
- (void)timerAction:(NSTimer *)timer {
    //!!!!!!!!計時器的處理方法中,不斷的調(diào)用代理方法恤浪,將音樂播放器的進度返回出去
    [self.delegate getCurTime:[self valueToString:[self getCurrentTime]] totle:[self valueToString:[self getTotalTime]] progress:[self getProgress]];
}
// 獲取當前時間 (3┭啤!K伞\拧!就是這個算法 別問我為什么   規(guī)定的)
- (NSInteger)getCurrentTime {
    if (self.player.currentItem) {
        // 當前的時間value / 當前時間的時間比例
        return self.player.currentTime.value / self.player.currentTime.timescale;
    }
    return 0;
}
- (NSInteger)getTotalTime {
    CMTime totalTime = [self.player.currentItem duration];
    if (totalTime.timescale == 0) {
        return 1;
    }else {
        return totalTime.value / totalTime.timescale;
    }
}

// 獲取當前播放進度
- (CGFloat)getProgress {
    return (CGFloat) [self getCurrentTime] / (CGFloat)[self getTotalTime];
}
// 將整數(shù)轉(zhuǎn)化為00:00格式
- (NSString *)valueToString:(NSInteger)value {
    return [NSString stringWithFormat:@"%.2ld:%.2ld",value / 60,value % 60];
}


- (void)pauseMusic {
    [self.timer invalidate];
    self.timer = nil;
    [self.player pause];
}
// 跳轉(zhuǎn)方法
- (void)seekToTimeWithValue:(CGFloat)value {
    // 跳轉(zhuǎn)之前先停止
    [self.player pause];
    //跳轉(zhuǎn)
    [self.player seekToTime:CMTimeMake(value * [self getTotalTime], 1) completionHandler:^(BOOL finished) {
        if (finished == YES) {
            [self.player play];
        }
    }];
}

二. 我們將我們需要的音樂播放器類已經(jīng)建完 我們講一下數(shù)據(jù)解析類吧I翱汀(JYJDataTools)
1.h 中

@class JYJProgramModel;
typedef void(^PassValue)(NSArray *array);

@interface JYJDataTools : NSObject
@property (nonatomic, strong) NSMutableArray *dataArray;// 接受數(shù)據(jù)的數(shù)組
// 單例
+ (instancetype)shareDataTools;
- (void)postDataWithURL:(NSString *)URL andBody:(id)body passValue:(PassValue)passvalue;



- (JYJProgramModel *)getModelWithIndex:(NSInteger)index;// 第幾個歌曲
@property (nonatomic, strong) JYJProgramModel *programModel;


2.m中

// 單例創(chuàng)建
+ (instancetype)shareDataTools {
    static JYJDataTools *dataTools = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        dataTools = [[JYJDataTools alloc]init];
    });
    return dataTools;
}
// 因為我的數(shù)據(jù)是post請求的 網(wǎng)址的url和body 體是不一樣的而我的數(shù)據(jù)頭是一樣的泥张,所以我只需要將網(wǎng)址的url和body和需要的數(shù)據(jù)封裝在一起。
- (void)postDataWithURL:(NSString *)URL andBody:(id)body passValue:(PassValue)passvalue {
    [JYJNetWorkParser POST:URL andBody:body andBodyStyle:JYJBodyString andHeader:HEADDIC andResponse:JYJJSON andSuccessBlock:^(id result) {
        self.dataArray = [NSMutableArray array];
        NSArray *dataArray = [result objectForKey:@"data"];
        for (NSDictionary *dic in dataArray) {
            JYJProgramModel*model = [JYJProgramModel modelWithDic:dic];
            [self.dataArray addObject:model];
        }
        // >现怠;帷!3菟摺筝野!block回調(diào)
        passvalue(self.dataArray);
        
    } andFailureBlock:^(NSError *error) {
        
    }];
    

}
- (JYJProgramModel *)getModelWithIndex:(NSInteger)index {
    return self.dataArray[index];
}

三.大體需要的類已經(jīng)準備完畢,第三個類就需要自己來完成了粤剧,也就是鋪帶有暫停歇竟、播放、上一曲抵恋、下一曲焕议、進度條的頁面啦。

四.這時候我就當你已經(jīng)鋪好頁面了弧关。我們還的需要一個最重要的類了盅安,也就是音樂播放控制器。當然前面也只是做一下鋪墊世囊,沒有前面的他們我們什么也完不成1鸩t。?br> 我們需要從有TableView的VC跳轉(zhuǎn)到這個播放VC(JYJMusicController)

1.TableViewVC
.m 中

- (void)viewDidLoad {
    [super viewDidLoad];
    self.navigationController.navigationBar.translucent = NO;
    [self createView];
   [self handleData];
 
    // Do any additional setup after loading the view.
}

- (void)handleData {
  NSString *string = @"json=%7B%22session%22%3A%7B%22uid%22%3A%2233368%22%2C%22sid%22%3A%22d0f457f15aab43579fc6c37a4dce426c%22%7D%2C%22item_id%22%3A%2213241%22%2C%22keyword%22%3A%22%22%2C%22orderBy%22%3A2%2C%22recomm%22%3A0%2C%22pagination%22%3A%7B%22count%22%3A25%2C%22page%22%3A1%7D%7D";
        [[JYJDataTools shareDataTools]postDataWithURL:@"http://www.aiyinsitanfm.com/mobile/channel/musiclist.jhtml" andBody:string passValue:^(NSArray __kindof *array) {
            
            self.marrOfModel = array;
            dispatch_async(dispatch_get_main_queue(), ^{
                [self.tableView reloadData];
               
            });
       }];
}
- (void)createView {
    self.tableView = [[UITableView alloc]initWithFrame:self.view.bounds style:UITableViewStylePlain];
    [self.view addSubview:_tableView];
    self.tableView.delegate = self;
    self.tableView.dataSource = self;
    [self.tableView registerClass:[CustomTableViewCell class] forCellReuseIdentifier:@"pool"];
    
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
   
    return self.modelOfArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    CustomTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"pool" forIndexPath:indexPath];
    ModelOfFirst *model = self.modelOfArray[indexPath.row];
    [cell setModel:model];
    return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
   JYJMusicController*vc = [JYJMusicController shareVC];
    vc.index = indexPath.row;
    vc.model = self.modelOfArray[indexPath.row];
    [self.navigationController pushViewController:vc animated:YES];
}

2.JYJMusicController
.h中

@property (nonatomic, assign) NSInteger index;
@property (nonatomic, strong) ModelOfFirst *model;
+ (instancetype)shareVC;

.m中

@interface ViewController ()<MusicPlayToolsDelegate>


@property (nonatomic, strong) MusicPlayView *viewContairner;
@property (nonatomic, strong) UIImageView *backImageView;

@property (nonatomic, strong) MusicPlayTools *aa;
@property (nonatomic, strong) UILabel *labelOfPaoma;
@property (nonatomic, strong) LSPaoMaView *paoma;
@end

@implementation ViewController
+ (instancetype)shareVC {
    static ViewController *vc = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        vc = [[ViewController alloc]init];
    });
    return vc;
}
- (void)viewDidLoad {
    [super viewDidLoad];
    self.aa = [MusicPlayTools shareMusicPlayTools];
    self.aa.delegate = self;
    self.viewContairner = [[MusicPlayView alloc]initWithFrame:self.view.bounds];
    [self.view addSubview:self.viewContairner];
    [self.viewContairner play:^(MyButton *play) {
       [[MusicPlayTools shareMusicPlayTools] musicPause];
    } pause:^(MyButton *play) {
        [[MusicPlayTools shareMusicPlayTools]musicPlay];

    } lastSong:^(MyButton *play) {
        if (self.index > 0) {
            self.index --;
            
        }else{
            self.index = [GetDataTools shareGetData].dataArray.count - 1;
            
            
        }
        [self p_play];
    } nextSong:^(MyButton *play) {
        if (self.index == [GetDataTools shareGetData].dataArray.count -1) {
            self.index = 0;
            
        }else
        {
            self.index ++;
        
            
        }
        [self p_play];
    }];
   [self.viewContairner slider:^(UISlider *slider) {
       [[MusicPlayTools shareMusicPlayTools] seekToTimeWithValue:slider.value];
   } ];
    
    // Do any additional setup after loading the view, typically from a nib.
}
- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    self.backImageView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, 300)];
    [self.view addSubview:_backImageView];
    [_backImageView sd_setImageWithURL:[NSURL URLWithString:self.model.music_img] placeholderImage:[UIImage imageNamed:@"zhanwei"]];

    [self p_play];
}
- (void)p_play {
    // 判斷當前播放器的model 和 點擊cell的index對應(yīng)的model,是不是同一個.
    // 如果是同一個,說明正在播放的和我們點擊的是同一個, 這個時候不需要重新播放.直接返回就行了.
    if ([[MusicPlayTools shareMusicPlayTools].model isEqual:[[GetDataTools shareGetData] getModelWithIndex:self.index]]) {
        return;
    }
    
    // 如果播放中和我們點擊的不是同一個,那么替換當前播放器的model.
    // 然后重新準備播放.
    [MusicPlayTools shareMusicPlayTools].model = [[GetDataTools shareGetData] getModelWithIndex:self.index];
    //背景圖片
    [self.backImageView sd_setImageWithURL:[NSURL URLWithString:[MusicPlayTools shareMusicPlayTools].model.music_img] placeholderImage:[UIImage imageNamed:@"zhanwei"]];
    //self.labelOfPaoma.text = [MusicPlayTools shareMusicPlayTools].model.title;
     self.paoma = [LSPaoMaView shareLsinitWithFrame:CGRectZero title:[MusicPlayTools shareMusicPlayTools].model.music_title BackColor:[UIColor redColor] textColor:[UIColor whiteColor] textFont:25];
    NSLog(@"%@",[MusicPlayTools shareMusicPlayTools].model.music_title);
    // 注意這里準備播放 不是播放!!!
    [[MusicPlayTools shareMusicPlayTools] musicPrePlay];
}


-(void)getCurTime:(NSString *)curTime Totle:(NSString *)totle Progress:(CGFloat)progress {
    self.viewContairner.leftLabelTime.text = curTime;
    self.viewContairner.rightLabelTime.text = totle;
    self.viewContairner.slider.value = progress;
}
- (void)endOfPlayAction {
    if (self.index == [GetDataTools shareGetData].dataArray.count -1) {
        self.index = 0;
    }else
    {
        self.index ++;
    }
    [self p_play];
}
- (void)playFail {
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"提示" message:@"節(jié)目加載失敗" preferredStyle:UIAlertControllerStyleAlert];
    UIAlertAction *action = [UIAlertAction actionWithTitle:@"確認" style: UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        
    }];
    [alert addAction:action];
    [self presentViewController:alert animated:YES completion:nil];
    
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末株憾,一起剝皮案震驚了整個濱河市蝙寨,隨后出現(xiàn)的幾起案子晒衩,更是在濱河造成了極大的恐慌,老刑警劉巖墙歪,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件听系,死亡現(xiàn)場離奇詭異,居然都是意外死亡虹菲,警方通過查閱死者的電腦和手機靠胜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進店門弓摘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來嫉拐,“玉大人昼钻,你說我怎么就攤上這事宁否。” “怎么了码撰?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我瘩欺,道長,這世上最難降的妖魔是什么拌牲? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任俱饿,我火速辦了婚禮,結(jié)果婚禮上塌忽,老公的妹妹穿的比我還像新娘拍埠。我一直安慰自己,他們只是感情好土居,可當我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布枣购。 她就那樣靜靜地躺著,像睡著了一般擦耀。 火紅的嫁衣襯著肌膚如雪棉圈。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天眷蜓,我揣著相機與錄音分瘾,去河邊找鬼。 笑死吁系,一個胖子當著我的面吹牛德召,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播汽纤,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼上岗,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蕴坪?” 一聲冷哼從身側(cè)響起液茎,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后捆等,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體滞造,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年栋烤,在試婚紗的時候發(fā)現(xiàn)自己被綠了谒养。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡明郭,死狀恐怖买窟,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情薯定,我是刑警寧澤始绍,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站话侄,受9級特大地震影響亏推,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜年堆,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一吞杭、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧变丧,春花似錦芽狗、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至攻晒,卻和暖如春柔昼,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背炎辨。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工捕透, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人碴萧。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓乙嘀,卻偏偏與公主長得像,于是被迫代替她去往敵國和親破喻。 傳聞我的和親對象是個殘疾皇子虎谢,可洞房花燭夜當晚...
    茶點故事閱讀 44,619評論 2 354

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