iOS VLC-音乐播放(列表式播放封装)

iOS 音乐播放

已经颓废了好几个月了,感觉在不写点什么就GG了。情绪这东西真的是毒药,控制不了容易一蹶不振,感觉做什么都没意思,以往都是我来开导别人,但是发生在自己身上时自己却看不开了。也许经历的还不够,相信时间会给我们答案。

说了这么多题外话,很久没有写东西,也没提前做准备,那就拿最近的项目需求来做题材写一篇关于iOS音乐播放。

需求

这个项目很蛋疼,需求什么都不明确,。导致后期改动颇大,做了很多无用工,当然也有我自己的原因,用的一些第三方框架之前没考虑太多。就拿音乐播放来说吧。我事先没考虑太多,只是觉得一个简单的播放音乐功能而已,用一些大家常见的第三方就OK了。一开始我用的是比较受推荐的“FreeStreamer”这个库,它里面有一个专门用来做列表控制播放的类“FSAudioController”比较适合用来做列表播放,那么问题来了,当项目大概好了后,打包给那边需求方测试,反馈下来就是让我一阵懵逼,这格式不支持,那格式不支持的(flac,wma,acc,wav,caf,ape),当时心里是有点不爽的,你需求方当初又没明确说过需要支持什么格式。再说看到这么一堆音频格式,说实话一般听音乐也就那么几种,什么APE无损格式的我都很少见过,一般常见就是mp3、m4a等一些,不过FreeStreamer这个库支持的格式说实话确实是少,连wav格式播放都有些怪,感觉播放这种格式音频有概率性的出现无声。不死心的我去找FreeStreamer论坛,发现作者自己说的原话,除了mp3和m4a常见格式之外,暂时没打算做支持其他格式的打算。那么就此扑街。

其实我第二个选择是用FFmpeg,因为在早先做MFI项目的时候,专门针对通过MFI传输过来的音频流用FFMpeg封装了一套用来解码播放音频的接口,这个可是支持市面上大多数格式了,如果有朋友对FFmpeg感兴趣,我会挑一个时间段专门写一些我对FFmpeg认知,然后想换上去的时候我发现这个wifi存储项目的视频播放功能我是用VLC写的,大家都知道VLC内部包含了FFmpeg这个库,然后在iOS中VLC库封的比较死,单独集成FFmpeg上去肯定是会报很多重复性的错误。那么索性就干脆用VLC写一个音乐播放得了。

前面已经有过一篇介绍VLC的简单使用,音乐播放也是差不多的,视频都能支持大部分格式播放,
那么音频肯定也是,因为视频内部可是包含音频的。

VLC 音乐播放

说到音乐,有必要说一下有些音乐文件有的是包含歌曲信息(歌手,专辑,专辑图),当然并不是所有音乐文件就一定会有。这得看制作音乐的时候有没有往里面写入歌曲相关的信息,下面我主要说一些用VLC做音乐播放的时候遇到的一些坑以及解决办法。

  • 解码mp3总时间问题

    VLC在解码某些mp3歌曲的时候有时候总时间会跳动的改变,这应该是MobileVLCKit的一个小bug。
    我直接使用iOS原生自带的AVFoundation框架中的AVURLAsset获取mp3的总时间。这里简单写一下实现。
    // 系统自带API解析MP3信息
    - (NSDictionary *)getMusicInfo:(NSURL *)url
    {
        NSMutableDictionary *dictInfo = [NSMutableDictionary dictionary];
        AVURLAsset *avURLAsset = [[AVURLAsset alloc] initWithURL:url options:nil];
        // 获取音频总时长
        int time = avURLAsset.duration.value / avURLAsset.duration.timescale;
        for (NSString *format in [avURLAsset availableMetadataFormats]){
            for (AVMetadataItem *metadata in [avURLAsset metadataForFormat:format]){
                if([metadata.commonKey isEqualToString:@"title"]){
                    dictInfo[@"title"] = (NSString *)metadata.value;
                }else if ([metadata.commonKey isEqualToString:@"artist"]) { // 歌手
                    dictInfo[@"artist"] = (NSString *)metadata.value;
                }else  if ([metadata.commonKey isEqualToString:@"albumName"]) { // 专辑名
                    dictInfo[@"albumName"] = (NSString *)metadata.value;
                }else  if([metadata.commonKey isEqualToString:@"artwork"]){ // 专辑图片
                    NSData *imageData = (NSData *)metadata.value;
                    dictInfo[@"artwork"] = imageData;
                }
            }
        }
    
        return dictInfo;
    }
    
  • 网络流解码专辑图问题

    我在使用的过程中发现同一个音乐文件在本地是可以解出专辑图片,说明这个文件本身包含专辑图片。
    但是网络流的时候,就是解析不到图片,确实是MobileVLCKit的一个bug。
    不知道最新版本解决这个问题没有,因为我看官网demo中用上了最新的接口,
    偏偏接口增加了VLCMediaParseNetwork和VLCMediaFetchNetwork一些枚举解析参数。
    猜测应该很大可能会解决这个问题。
    不过我pod search并没发现新版本,可能repo太旧了,因为更新慢所以就没去更新SDK和测试这个问题。
    MobileVLCKit新版本增加的接口:(你们可以试一下看看问题解决没)
    enum {
      VLCMediaParseLocal          = 0x00,
      VLCMediaParseNetwork        = 0x01,
      VLCMediaFetchLocal          = 0x02,
      VLCMediaFetchNetwork        = 0x04,
    

};
/**

  • enum of available options for use with parseWithOptions
  • \note you may pipe multiple values for the single parameter
    /
    typedef int VLCMediaParsingOptions;
    /
    *
  • triggers an asynchronous parse of the media item
  • using the given options
  • \param options the option mask based on VLCMediaParsingOptions
  • \see VLCMediaParsingOptions
  • \return an int. 0 on success, -1 in case of error
  • \note listen to the "parsed" key value or the mediaDidFinishParsing:
  • delegate method to be notified about parsing results. Those triggers
  • will NOT be raised if parsing fails and this method returns an error.
    */
  • (int)parseWithOptions:(VLCMediaParsingOptions)options;

- VLCMediaPlayer做列表切换播放问题

```objc
也不知道是我思考路子不对还是确实MobileVLCKit的问题。
我原先做列表播放的时候是想创建多个VLCMediaPlayer对象,通过对VLCMediaPlayer的控制达到切换播放的效果,
当然效果是做出来了,可是在后面测试的时候,在音频的切换的过程中会有几率造成音频无声的状况,
然后就只好播放完后销毁一个VLCMediaPlayer在重新创建达到切换的效果,
并且在销毁VLCMediaPlayer对象前也不能调用stop方法,不然有时候也会造成音频切换无声情况。
  • 个人小封装

好了,废话我就不再说了,抛砖引玉的直接上个人接口封装的源码了。喜欢的朋友麻烦点一波关注和赞呐。

  • VLCMusicViewController.h
    //
    //  VLCMusicViewController.h
    //  TedStorage
    //
    //  Created by  tedcall on 2016/11/18.
    //  Copyright © 2016年 pocket. All rights reserved.
    //
    #import <Foundation/Foundation.h>
    #import <MobileVLCKit/MobileVLCKit.h>
    
    typedef NS_ENUM(NSInteger,VLCMusicModelType) {
        VLCMusicModelTypeListLoop = 0, //!< 列表循环
        VLCMusicModelTypeSingleLoop, //!< 单曲循环
        VLCMusicModelTypeRandomPlay //!< 随机播放
    };
    
    typedef NS_ENUM(NSInteger, VLCMusicState) {
        VLCMusicStatePlaying = 0, //!< 正在播放
        VLCMusicStateStopped, //!< 停止
        VLCMusicStatePaused, //!< 暂停
        VLCMusicStateSeeking, //!< seeking
        VLCMusicStateBuffering, //!< 缓冲中
        VLCMusicStateFailed, //!< 错误
        VLCMusicStateCompleted, //!< 一首歌播放完成
        VLCMusicStateUnknown //!< Unknown
    };
    
    // 歌曲列表信息
    @interface VLCMusicPlaylistItem : NSObject
    
    @property (nonatomic, copy) NSString *title; //!< 歌曲名
    @property (nonatomic, copy) NSURL *url; //!< 歌曲资源URL
    
    @end
    
    // 某些音频文件可能并不存在这些信息
    @interface VLCMusicInfo : NSObject
    
    @property (nonatomic, copy) NSString *title; //!< 标题
    @property (nonatomic, copy) NSString *artist; //!< 艺术家
    @property (nonatomic, copy) NSString *albumName; //!< 专辑名
    @property (nonatomic, copy) NSData *imageData; //!< 专辑图片
    
    @end
    
    @interface VLCMusicTime : NSObject
    
    @property (nonatomic, copy) NSString *allTime; //!< 总时间
    @property (nonatomic, copy) NSString *currentTime;
    @property (nonatomic, assign) float progress; //!< 当前进度
    
    @end
    
    @interface VLCMusicViewController : NSObject
    
    @property (nonatomic, strong, readonly) NSMutableArray <VLCMusicPlaylistItem *> *musicPlaylist; //!< 音乐列表
    @property (nonatomic, assign, readwrite) VLCMusicModelType type; //!< 播放类型
    @property (nonatomic, strong, readonly) VLCMediaPlayer *activeStream; //!< 当前激活的音频
    @property (nonatomic, strong, readonly) VLCMusicPlaylistItem *currentPlaylistItem; //!< 当前播放的歌曲
    
    @property (nonatomic, assign) BOOL preloadNextPlaylistItemAutomatically; //!< 是否自动播放下一首,默认开始,为NO时播放类型将失效
    
    @property (nonatomic, assign) float volume; //!< 音量大小
    @property (nonatomic, assign, readonly) int allTime; //!< 音乐总时间
    @property (nonatomic, assign, readonly) int currentTime; //!< 当前时间
    @property (nonatomic, assign, readonly) float currentProgress; //!< 当前进度
    
    @property (nonatomic, copy) void (^onStateChange)(VLCMusicState state); //!< 播放状态回调
    
    @property (nonatomic, copy) void (^onMetaDataAvailable)(VLCMusicInfo *metadata); //!< 歌曲信息回调
    
    @property (nonatomic, copy) void (^onFailure)(void); //!< 播放失败回调
    
    @property (nonatomic, copy) void (^onTimeChange)(VLCMusicTime *musicTime); //!< 时间和进度状态回调
    
    @property (nonatomic, copy) void (^onPlayType)(VLCMusicModelType type); //!< 播放顺序回调
      /**
     如果是暂停则会触发播放,如果是播放则会触发暂停
     */
    - (void)pause;
      /**
     是否在播放
    
     @return YES:正在播放  NO:没有在播放
     */
    - (BOOL)isPlaying;
    
    /**
     是否有下一曲
    
     @return YES: 有  NO:无
     */
    - (BOOL)hasNextItem;
    
    /**
     是否有上一曲
    
     @return YES: 有  NO:无
     */
    - (BOOL)hasPreviousItem;
      /**
     下一首
     */
    - (void)playNextItem;
      /**
     上一首
     */
    - (void)playPreviousItem;
      /**
     跳转到指定位置播放
    
     @param progress 百分比
     */
    - (void)seekToPosition:(float)progress;
      /**
     添加歌曲
    
     @param item 歌曲资源
     */
    - (void)addPlaylistItem:(VLCMusicPlaylistItem *)item;
      /**
     插入歌曲
    
     @param item  歌曲资源
     @param index 索引
     */
    - (void)insertItem:(VLCMusicPlaylistItem *)item atIndex:(NSInteger)index;
      /**
     播放列表中的指定的歌曲
    
     @param playlist 传递的歌曲列表
     @param index    索引
     */
    - (void)playFormPlaylist:(NSArray <VLCMusicPlaylistItem *> *)playlist itemIndex:(NSUInteger)index;
    
    /**
     播放指定索引歌曲
    
     @param index 索引
     */
    - (void)playItemAtIndex:(NSUInteger)index;
      /**
     播放列表中的歌曲
    
     @param playlist 传递的歌曲列表
     */
    - (void)playFormPlaylist:(NSArray <VLCMusicPlaylistItem *> *)playlist;
    @end
    
  • VLCMusicViewController.m
    //
    //  VLCMusicViewController.m
    //  TedStorage
    //
    //  Created by  tedcall on 2016/11/18.
    //  Copyright © 2016年 pocket. All rights reserved.
    //
    #import "VLCMusicViewController.h"
    #import <AVFoundation/AVFoundation.h>
    #import <CommonCrypto/CommonDigest.h>
    @implementation VLCMusicPlaylistItem
    
    @end
    
    @implementation VLCMusicInfo
    
    @end
    
    @implementation VLCMusicTime
    
    @end
    
    @interface VLCMusicViewController () <VLCMediaDelegate>
    
    @property (nonatomic, assign) NSInteger index; //!< 当前播放索引
    @property (nonatomic, strong) VLCMediaPlayer *activeStream;
    @property (nonatomic, strong) VLCMusicPlaylistItem *currentPlaylistItem;
    @property (nonatomic, strong) NSMutableArray <VLCMusicPlaylistItem *> *musicPlaylist;
    @property (nonatomic, assign) int allTime; //!< 音乐总时间
    @property (nonatomic, assign) int currentTime; //!< 当前时间
    @property (nonatomic, assign) float currentProgress;
    
    @property (nonatomic, copy) NSString *totalTimeStr; //!< mp3等专门特别针对获取
    
    @end
    @implementation VLCMusicViewController
    
    - (NSMutableArray<VLCMusicPlaylistItem *> *)musicPlaylist
    {
        if (_musicPlaylist == nil) {
            _musicPlaylist = [NSMutableArray array];
        }
        return _musicPlaylist;
    }
    
    // 系统自带API解析MP3信息
    - (NSDictionary *)getMusicInfo:(NSURL *)url
    {
        NSMutableDictionary *dictInfo = [NSMutableDictionary dictionary];
        AVURLAsset *avURLAsset = [[AVURLAsset alloc] initWithURL:url options:nil];
        // 获取音频总时长
        int time = avURLAsset.duration.value / avURLAsset.duration.timescale;
        if (time > 0) {
            int min = time/60;
            int sec = time%60;
            NSString *s1 ,*s2;
            if (min < 10) {
                s1 = [NSString stringWithFormat:@"0%d",min];
            } else {
                s1 = [NSString stringWithFormat:@"%d",min];
            }
            if (sec < 10) {
                s2 = [NSString stringWithFormat:@"0%d",sec];
            } else {
                s2 = [NSString stringWithFormat:@"%d",sec];
            }
            self.totalTimeStr = [NSString stringWithFormat:@"%@:%@",s1,s2];
        } else {
            self.totalTimeStr = @"--:--";
        }
        for (NSString *format in [avURLAsset availableMetadataFormats]){
            for (AVMetadataItem *metadata in [avURLAsset metadataForFormat:format]){
                NSLog(@"metadata.commonKey:%@",metadata.commonKey);
                if([metadata.commonKey isEqualToString:@"title"]){
                    dictInfo[@"title"] = (NSString *)metadata.value;
                }else if ([metadata.commonKey isEqualToString:@"artist"]) { // 歌手
                    dictInfo[@"artist"] = (NSString *)metadata.value;
                }else  if ([metadata.commonKey isEqualToString:@"albumName"]) { // 专辑名
                    dictInfo[@"albumName"] = (NSString *)metadata.value;
                }else  if([metadata.commonKey isEqualToString:@"artwork"]){ // 专辑图片
                    NSData *imageData = (NSData *)metadata.value;
                    dictInfo[@"artwork"] = imageData;
                }
            }
        }
    
        return dictInfo;
    }
    #pragma mark - -- VLC获取专辑图片
    - (void)updataMeta
    {
        if (self.onMetaDataAvailable) {
            NSDictionary *dict = [self getMusicInfo:self.currentPlaylistItem.url];
            // 获取歌曲信息
            NSDictionary *meta = [_activeStream.media metaDictionary];
            VLCMusicInfo *info = [VLCMusicInfo new];
            if (meta && meta.count > 1) {
                info.title = meta[VLCMetaInformationTitle];
                info.artist = meta[VLCMetaInformationArtist];
                info.albumName = meta[VLCMetaInformationAlbum];
                // 获取专辑图片路径(VLC把专辑图片写入了沙盒。。。)
                NSString *artworkPath = [self artworkPathForMediaItemWithTitle:info.title Artist:info.artist andAlbumName:info.albumName];
                info.imageData = [NSData dataWithContentsOfFile:artworkPath];
            } else {
                if (dict.allKeys.count > 0) {
                    info.title = dict[@"title"];
                    info.artist = dict[@"artist"];
                    info.imageData = dict[@"artwork"];
                } else {
                    info.title = [[FileManager shardInstance] deletePathExtension:self.currentPlaylistItem.title];
                    NSArray *array = [info.title componentsSeparatedByString:@"-"];
                    if (array.count > 1) {
                        info.title = array[1];
                        info.artist = array[0];
                    }
                }
    
            }
            self.onMetaDataAvailable(info);
        }
    
    }
    - (NSString *)artworkPathForMediaItemWithTitle:(NSString *)title Artist:(NSString*)artist andAlbumName:(NSString*)albumname
    {
        NSString *artworkURL;
        NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
        NSString *cacheDir = searchPaths[0];
        cacheDir = [cacheDir stringByAppendingFormat:@"/%@", [[NSBundle mainBundle] bundleIdentifier]];
    
        if ((artist.length == 0 || albumname.length == 0) && title != nil && title.length > 0) {
    
            artworkURL = [cacheDir stringByAppendingFormat:@"/art/arturl/%@/art.jpg", [self _md5FromString:title]];
        } else {
    
            artworkURL = [cacheDir stringByAppendingFormat:@"/art/artistalbum/%@/%@/art.jpg", artist, albumname];
        }
    
        return artworkURL;
    }
    - (NSString *)_md5FromString:(NSString *)string
    {
        const char *ptr = [string UTF8String];
        unsigned char md5Buffer[CC_MD5_DIGEST_LENGTH];
        CC_MD5(ptr, (unsigned int)strlen(ptr), md5Buffer);
        NSMutableString *output = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2];
        for (int i = 0; i < CC_MD5_DIGEST_LENGTH; i++)
            [output appendFormat:@"%02x",md5Buffer[i]];
    
        return [NSString stringWithString:output];
    }
    - (void)dealloc
    {
        [_activeStream stop];
        [self removeObserver:_activeStream];
        _activeStream = nil;
        _activeStream = nil;
    }
    
    #pragma mark - -- 歌曲切换
    - (void)playPreviousItem
    {
        [self setCurrentPlayIndex:self.index - 1];
    }
    - (void)playNextItem
    {
        [self setCurrentPlayIndex:self.index + 1];
    }
    - (BOOL)hasNextItem
    {
        if ((self.index +1) < self.musicPlaylist.count && self.musicPlaylist.count > 0) {
            return YES;
        }
        return NO;
    }
    - (BOOL)hasPreviousItem
    {
        if (self.musicPlaylist.count > 0 && (self.index - 1) >= 0 && ((self.index - 1) < self.musicPlaylist.count)) {
            return YES;
        }
    
        return NO;
    }
    - (void)seekToPosition:(float)progress
    {
        if (!self.activeStream.isPlaying) {
            [self.activeStream play];
        }
        if (progress >= 1.0) {
            switch (self.type) {
                case VLCMusicModelTypeListLoop:
                {
                    if ([self hasNextItem]) {
                        [self playNextItem];
                    } else {
                        [self playItemAtIndex:0];
                    }
                }
                    break;
                case VLCMusicModelTypeSingleLoop:
                {
                    self.currentTime = 0;
                    self.currentProgress = 0.0;
                    [self playItemAtIndex:self.index];
                }
                    break;
    
                case VLCMusicModelTypeRandomPlay:
                {
                    // 获取随机数
                    int index = arc4random() % self.musicPlaylist.count;
                    while (index == self.index) {
                        index = arc4random() % self.musicPlaylist.count;
                    }
                    [self playItemAtIndex:index];
                }
                    break;
    
                default:
                {
                    if ([self hasNextItem]) {
                        [self playNextItem];
                    } else {
                        [self playItemAtIndex:0];
                    }
                }
                    break;
            }
    
        } else {
            [self.activeStream setPosition:progress];
            if (self.onStateChange) {
                self.onStateChange(VLCMusicStateSeeking);
            }
        }
    
    }
    
    #pragma mark - -- 歌曲控制
    - (void)pause
    {
        if ([self.activeStream isPlaying]) {
            [self.activeStream pause];
        } else {
            [self.activeStream play];
        }
    }
    
    - (BOOL)isPlaying
    {
        if ([self.activeStream isPlaying]) {
            return YES;
        }
    
        return NO;
    }
    #pragma mark - -- 歌曲添加
    - (void)addPlaylistItem:(VLCMusicPlaylistItem *)item
    {
        [self.musicPlaylist addObject:item];
    }
    
    - (void)insertItem:(VLCMusicPlaylistItem *)item atIndex:(NSInteger)index
    {
        [self.musicPlaylist insertObject:item atIndex:index];
    }
    
    - (void)playFormPlaylist:(NSArray <VLCMusicPlaylistItem *> *)playlist
    {
        [self playFormPlaylist:playlist itemIndex:0];
    }
    
    - (void)playFormPlaylist:(NSArray <VLCMusicPlaylistItem *> *)playlist itemIndex:(NSUInteger)index
    {
        [self.musicPlaylist removeAllObjects];
        [self.musicPlaylist addObjectsFromArray:playlist];
        [self setCurrentPlayIndex:index];
    }
    
    - (void)playItemAtIndex:(NSUInteger)index
    {
        if (index == self.index && self.type != VLCMusicModelTypeSingleLoop) return;
        [self setCurrentPlayIndex:index];
    }
    
    #pragma mark - -- 播放顺序
    - (void)setType:(VLCMusicModelType)type
    {
        [[NSUserDefaults standardUserDefaults] setInteger:type forKey:@"VLCMusicPlayType"];
        [[NSUserDefaults standardUserDefaults] synchronize];
        if (self.onPlayType) {
            self.onPlayType(type);
        }
    }
    
    - (VLCMusicModelType)type
    {
        if ([[NSUserDefaults standardUserDefaults] objectForKey:@"VLCMusicPlayType"]) {
            return [[NSUserDefaults standardUserDefaults] integerForKey:@"VLCMusicPlayType"];
        } else {
            return VLCMusicModelTypeListLoop;
        }
    }
    
    #pragma mark -- 切歌
    - (void)setCurrentPlayIndex:(NSInteger)index
    {
        VLCMusicPlaylistItem *item = [self.musicPlaylist objectAtIndex:index];
        self.currentPlaylistItem = item;
        self.index = index;
    }
    
    #warning vlc切换有音频bug(直接导致列表式VLCMediaPlayer对象控制播放胎死腹中,只能每次重新创建,并且上一个销毁前还不能调用stop)
    - (void)setCurrentPlaylistItem:(VLCMusicPlaylistItem *)currentPlaylistItem
    {
        _currentPlaylistItem = currentPlaylistItem;
        // 根据item创建VLCMediaPlayer(因为vlc bug,只能每次播放前创建)
        VLCMediaPlayer *player = [[VLCMediaPlayer alloc] init];
        VLCMedia *media = [VLCMedia mediaWithURL:_currentPlaylistItem.url];
        [media synchronousParse]; // 同步分析媒体元数据(异步的话有一个代理专门回调解析完成)
        media.delegate = self;
        [player setMedia:media];
        if (self.activeStream) {
            [self.activeStream pause]; // 在这里不要调用stop,不然会导致音频切换过程偶发性的没声音
            [self removeObserver:self.activeStream];
            self.activeStream = nil;
        }
        self.activeStream = player;
        [self addObserver:self.activeStream];
        [self.activeStream play];
    }
    
    #pragma mark - -- kvo监听
    - (void)addObserver:(VLCMediaPlayer *)player
    {
        [player addObserver:self forKeyPath:@"remainingTime" options:0 context:nil];
        [player addObserver:self forKeyPath:@"state" options:0 context:nil];
    
    }
    
    - (void)removeObserver:(VLCMediaPlayer *)player
    {
        [player removeObserver:self forKeyPath:@"remainingTime"];
        [player removeObserver:self forKeyPath:@"state"];
    
    }
    
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
    

{
if ([keyPath isEqualToString:@"remainingTime"]) {
self.currentTime = [[[self.activeStream time] minuteStringValue] intValue] * 60;
self.allTime = [[[self.activeStream remainingTime] minuteStringValue] intValue] * 60 + self.currentTime;
self.currentProgress = [self.activeStream position];
[self update];
} else if ([keyPath isEqualToString:@"state"]) {
switch (self.activeStream.state) {
case VLCMediaPlayerStatePlaying:
{
if (self.onStateChange) {
self.onStateChange(VLCMusicStatePlaying);
}
}
break;

          case VLCMediaPlayerStateOpening:
          {
              if (self.onStateChange) {
                  self.onStateChange(VLCMusicStatePlaying);
              }
              [self updataMeta];
          }

              break;

          case VLCMediaPlayerStatePaused:
              if (self.onStateChange) {
                  self.onStateChange(VLCMusicStatePaused);
              }
              break;
          case VLCMediaPlayerStateStopped:
          {
            if (self.currentTime == self.allTime) { // 判断播放是否完结
                
                if (self.onStateChange) {
                    self.onStateChange(VLCMusicStateCompleted);
                }
                switch (self.type) {
                    case VLCMusicModelTypeListLoop:
                    {
                        if ([self hasNextItem]) {
                            [self playNextItem];
                        } else {
                            [self playItemAtIndex:0];
                        }
                    }
                        break;
                    case VLCMusicModelTypeSingleLoop:
                    {
                        self.currentTime = 0;
                        self.currentProgress = 0.0;
                        [self playItemAtIndex:self.index];

                    }
                        break;
                        
                    case VLCMusicModelTypeRandomPlay:
                    {
                        // 获取随机数
                        int index = arc4random() % self.musicPlaylist.count;
                        [self playItemAtIndex:index];
                    }
                        break;

                    default:
                    {
                        if ([self hasNextItem]) {
                            [self playNextItem];
                        } else {
                            [self playItemAtIndex:0];
                        }
                    }
                        break;
                }
            } else {
                if (self.onStateChange) {
                    self.onStateChange(VLCMusicStateStopped);
                }
            }
            
        }
              break;
          case VLCMediaPlayerStateBuffering:
              if (self.onStateChange) {
                  self.onStateChange(VLCMusicStateBuffering);
              }
              break;

          case VLCMediaPlayerStateError:
          {
              if (self.onStateChange) {
                  self.onStateChange(VLCMusicStateFailed);
              }

              if (self.onFailure) {
                  self.onFailure();
              }
          }

              break;
          default:
              break;
      }
          }

}

- (void)update
{
    if (self.onTimeChange) {
        VLCMusicTime *musicTime = [VLCMusicTime new];
        if (![self.totalTimeStr isEqualToString:@"--:--"]) {
            musicTime.allTime = self.totalTimeStr;
        } else {
            int time = (-[[self.activeStream remainingTime] intValue]) + [[self.activeStream time] intValue];
            VLCTime *v = [VLCTime timeWithInt:time];
            musicTime.allTime = [v stringValue];
        }
        musicTime.currentTime = [[self.activeStream time] stringValue];
        musicTime.progress = self.currentProgress;
        self.onTimeChange(musicTime);
    }
}

#pragma mark - -- VLCMediaDelegate
// vlc bug �本地的可以解析出,网络的不行了。。。。
- (void)mediaDidFinishParsing:(VLCMedia *)aMedia
{
    NSLog(@"mediaDidFinishParsing:%@",aMedia);
}

@end
```

接口用法

下面就简单说一下上面所封装的API的用法。设计UI的代码我就不贴出来了,相信你们只要把VLCMusicViewController看懂了或者不需要看懂(会用就行),就能设计出一个属于自己的漂亮的音乐播放控制器。如果很多人实在不会用我在考虑把接口用法demo上传到github。

ps:当初给类取名字的时候没取好,怪我!!!,你们可以自己改改(建议:VLCMusicManager)。
当初设计接口需求时可能多写了几个需求性属性控制参数,暂时没来得及实现(音量控制,自动播放下一首的关闭开关),勿怪啊。
相信看懂的你们可以自己在我这套不算很好的代码里整理出属于自己的精华。
1.创建VLCMusicViewController管理类
2.为VLCMusicViewController对象赋值几个block属性回调,
  onStateChange(状态回调,在这里可以改变播放按钮UI的改变),
  onMetaDataAvailable(歌曲信息改变回调,在这里可以改变歌曲信息UI),
  onTimeChange(时间和进度状态回调,在这里可以改变时间信息UI和进度条UI)
  onPlayType(播放顺序改变回调,在这里可以改变播放顺序UI)
  ...

放几张效果图

51C4A1DAEF2F4B92687E2EF08BA80370.gif
IMG_2568.jpg
IMG_2569.jpg
IMG_2567.jpg

好了就简单介绍到这里了,没几个接口方法,多试几遍就会了。觉得我写的不错的兄弟可以点赞或者关注Pocket的一些不定期的日常更新。共勉,加油!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容