ios - m3u8 解析组装

前言: 之前有项目要实现m3u8本地播放,后面自己写了一个多线程下载的demo,里面坑很多。因为时间问题,也没时间去填坑,后面还是采用单线程下载。但想了下m3u8本地缓存播放这个需求,每个公司的需求可能有所差异。但m3u8是协议好的东西,解析跟组装的部分应该都差不多。所以下面就记录下这部分的内容。

  1. 定义模型存储信息

    @class ZBLM3u8TsInfo;
    
    @interface ZBLM3u8Info : NSObject
    @property (nonatomic, copy) NSString *targetduration;
    @property (nonatomic, copy) NSString *version;
    @property (nonatomic, copy) NSString *mediaSequence;
    
    ///key信息
    @property (nonatomic, copy) NSString *keyMethod;
    @property (nonatomic, copy) NSString *keyUri;
    @property (nonatomic, copy) NSString *keyLocalUri;
    @property (nonatomic, copy) NSString *keyIv;
    @property (nonatomic, strong) NSMutableArray <ZBLM3u8TsInfo*> *tsInfos;
    @end
    
    @interface ZBLM3u8TsInfo : NSObject
    @property (nonatomic, copy) NSString *duration;
    @property (nonatomic, copy) NSString *oriUrlString;
    @property (nonatomic, copy) NSString *localUrlString;
    @property (nonatomic, assign) NSInteger index;
    @property (nonatomic, assign,getter = isHasDiscontiunity) BOOL hasDiscontiunity;
    @end
    复制代码
  2. 定义处理类,根据m3u8文本信息把信息保存到模型中并做相关的容错处理

    .h定义

    @class ZBLM3u8Info;
    
    typedef void (^ZBLM3u8AnalysiseCompletaionHandler)(ZBLM3u8Info *m3u8Info,NSError *error);
    
    @interface ZBLM3u8Analysiser : NSObject
    
    /**
     m3u8解析
    
     @param urlStr m3u8网络url
     @param completaionHandler 回调
     */
    + (void)analysisWithUrlString:(NSString*)urlStr completaionHandler:(ZBLM3u8AnalysiseCompletaionHandler)completaionHandler;
    
    /**
     m3u8文本组装
    
     @param m3u8Info m3u8信息模型
     @return m3u8文本
     */
    + (NSString*)synthesisLocalM3u8Withm3u8Info:(ZBLM3u8Info *)m3u8Info;
    
    FOUNDATION_EXPORT NSString * const ZBLM3u8AnalysiserResponeErrorDomain;
    FOUNDATION_EXPORT NSString * const ZBLM3u8AnalysiserAnalysisErrorDomain;
    
    @end
    
    复制代码

    .m实现

    /*解析m3u8 和组装m3u8*/
    
    NSString * const ZBLM3u8AnalysiserResponeErrorDomain = @"error.m3u8.analysiser.respone";
    NSString * const ZBLM3u8AnalysiserAnalysisErrorDomain = @"error.m3u8.analysiser.analysis";
    
    @implementation ZBLM3u8Analysiser
    
    + (void)analysisWithUrlString:(NSString*)urlStr completaionHandler:(ZBLM3u8AnalysiseCompletaionHandler)completaionHandler
    {
        NSLog(@"analysis start");
        //判断m3u8文本是否已经保存到本地,因为下载m3u8文本是耗时操作
        NSString *oriM3u8String = [NSString stringWithContentsOfFile:[[ZBLM3u8Setting fullCommonDirPrefixWithUrl:urlStr] stringByAppendingPathComponent:[ZBLM3u8Setting oriM3u8InfoFileName]] encoding:0 error:nil];
        
        __block BOOL happenException = NO;
        if (oriM3u8String.length) {
            NSLog(@"use local oriM3u8Info");
            //处理有可能失败,所有需要加try-catch-finally,保护程序
            @try {
                [self analysisWithM3u8String:oriM3u8String completaionHandler:completaionHandler];
            } @catch (NSException *exception) {
                happenException = YES;
                [[ZBLM3u8FileManager shareInstance]removeFileWithPath:[[ZBLM3u8Setting fullCommonDirPrefixWithUrl:urlStr] stringByAppendingPathComponent:[ZBLM3u8Setting oriM3u8InfoFileName]]];
                completaionHandler(nil,[[NSError alloc]initWithDomain:ZBLM3u8AnalysiserAnalysisErrorDomain code:NSURLErrorUnknown userInfo:@{@"info":exception.reason}]);
            } @finally {
                
            }
            if (!happenException) {
                return;
            }
        }
        
        happenException = NO;
        //异步获取
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSString *m3u8Str = nil;
            //处理有可能失败,所有需要加try-catch-finally,保护程序
            @try {
                NSError *error = nil;
                //根据url请求获取m3u8文本,耗时操作
                m3u8Str = [[NSString alloc] initWithContentsOfURL:[NSURL URLWithString:urlStr] usedEncoding:0 error:&error];
                
                if (error)
                {
                    completaionHandler(nil,error);
                    return ;
                }
                
                
                if (m3u8Str.length == 0)
                {
                    completaionHandler(nil,[[NSError alloc]initWithDomain:ZBLM3u8AnalysiserResponeErrorDomain code:NSURLErrorBadServerResponse userInfo:nil]);
                    return;
                }
                
                //解析
                [self analysisWithM3u8String:m3u8Str completaionHandler:completaionHandler];
                
            } @catch (NSException *exception) {
                happenException = YES;
                completaionHandler(nil,[[NSError alloc]initWithDomain:ZBLM3u8AnalysiserAnalysisErrorDomain code:NSURLErrorUnknown userInfo:@{@"info":exception.reason}]);
            } @finally {
                
            }
            
            if (!happenException) {
                //保存原m3u8文本,方便二次利用
                [[ZBLM3u8FileManager shareInstance]saveDate:[m3u8Str dataUsingEncoding:NSUTF8StringEncoding] ToFile:[[ZBLM3u8Setting fullCommonDirPrefixWithUrl:urlStr] stringByAppendingPathComponent:[ZBLM3u8Setting oriM3u8InfoFileName]] completaionHandler:nil];
            }
        });
    }
    
    
    /**
     private method
     m3u8文本解析
    
     @param m3u8String m3u8文本
     @param completaionHandler 回调
     */
    + (void)analysisWithM3u8String:(NSString*)m3u8String completaionHandler:(ZBLM3u8AnalysiseCompletaionHandler)completaionHandler
    {
        
        ZBLM3u8Info *info = [ZBLM3u8Info new];
        info.version = [[m3u8String subStringFrom:@"#EXT-X-VERSION:" to:@"#"] removeSpaceAndNewline];
        info.targetduration = [[m3u8String subStringFrom:@"#EXT-X-TARGETDURATION:" to:@"#"] removeSpaceAndNewline];
        info.mediaSequence = [[m3u8String subStringFrom:@"#EXT-X-MEDIA-SEQUENCE:" to:@"#"] removeSpaceAndNewline];
        
        info.keyMethod = [m3u8String subStringFrom:@"#EXT-X-KEY:METHOD=" to:@","];
        info.keyUri = [m3u8String subStringFrom:@"URI=\"" to:@"\""];
        info.keyIv = [[m3u8String subStringFrom:@"IV=" to:@"#"] removeSpaceAndNewline];
        
        NSRange tsRange = [m3u8String rangeOfString:@"#EXTINF:"];
        if (tsRange.location == NSNotFound) {
            completaionHandler(nil,[[NSError alloc]initWithDomain:ZBLM3u8AnalysiserAnalysisErrorDomain code:NSURLErrorUnknown userInfo:@{@"info":@"none downloadUrl for .ts file"}]);
            return;
        }
        NSInteger index = 0;
        NSMutableArray *tsInfos = @[].mutableCopy;
        m3u8String = [m3u8String substringFromIndex:tsRange.location];
        while (tsRange.location != NSNotFound) {
            ZBLM3u8TsInfo *tsInfo = [ZBLM3u8TsInfo new];
            tsInfo.duration = [m3u8String subStringFrom:@"#EXTINF:" to:@","];
            m3u8String = [m3u8String subStringForm:@"," offset:1];
            tsInfo.oriUrlString = [[m3u8String subStringTo:@"#"] removeSpaceAndNewline];
            
            NSRange exRange = [m3u8String rangeOfString:@"#EX"];
            NSRange discontinuityRange = [m3u8String rangeOfString:@"#EXT-X-DISCONTINUITY"];
            if (exRange.location == discontinuityRange.location) {
                tsInfo.hasDiscontiunity = YES;
            }
            tsInfo.index = index ++;
            [tsInfos addObject:tsInfo];
            tsRange = [m3u8String rangeOfString:@"#EXTINF:"];
            if (tsRange.location != NSNotFound) {
                m3u8String = [m3u8String subStringForm:@"#EXTINF:" offset:0];
            }
        }
        NSLog(@"analysis compelte");
        info.tsInfos = tsInfos;
        NSParameterAssert(completaionHandler);
        completaionHandler(info,nil);
    }
    
    复制代码
  3. 根据本地保存的ts信息更新模型,并重新组装m3u8文本信息

     + (NSString*)synthesisLocalM3u8Withm3u8Info:(ZBLM3u8Info *)m3u8Info
    {
        NSString *newM3u8String = @"";
        
        NSString *header = @"#EXTM3U\n";
        if (m3u8Info.version.length) {
            header = [header stringByAppendingString:[NSString stringWithFormat:@"#EXT-X-VERSION:%ld\n",(long)m3u8Info.version.integerValue]];
        }
        if (m3u8Info.targetduration.length) {
            header = [header stringByAppendingString:[NSString stringWithFormat:@"#EXT-X-TARGETDURATION:%ld\n",(long)m3u8Info.targetduration.integerValue]];
        }
        if (m3u8Info.mediaSequence.length) {
            header = [header stringByAppendingString:[NSString stringWithFormat:@"#EXT-X-MEDIA-SEQUENCE:%ld\n",(long)m3u8Info.mediaSequence.integerValue]];
        }
        
        NSString *keyStr = @"";
        if(m3u8Info.keyUri.length)
        {
            keyStr = [NSString stringWithFormat:@"#EXT-X-KEY:METHOD=%@,URI=\"%@\",IV=%@\n",m3u8Info.keyMethod,m3u8Info.keyLocalUri,m3u8Info.keyIv];
        }
        
        __block NSString *body = @"";
        [m3u8Info.tsInfos enumerateObjectsUsingBlock:^(ZBLM3u8TsInfo * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            NSString *tsInfo = [NSString stringWithFormat:@"#EXTINF:%.6lf,\n%@\n",obj.duration.floatValue,obj.localUrlString];
            body =  [body stringByAppendingString:tsInfo];
            if (obj.isHasDiscontiunity) body = [body stringByAppendingString:@"#EXT-X-DISCONTINUITY\n"];
        }];
        
        newM3u8String = [[[[newM3u8String stringByAppendingString:header] stringByAppendingString:keyStr] stringByAppendingString:body] stringByAppendingString:@"#EXT-X-ENDLIST"];
        
        return newM3u8String;
        }
    复制代码

demo: github.com/zmubai/ZBLM…

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值