[toc]
前言
iOS m3u8本地缓存播放(控制下载并发、暂停恢复)
传统上传 VS 分片上传
传统上传方法的问题 |
分片上传的优点 |
大文件上传耗时长,容易导致超时。 |
将大文件拆分成较小的分片,更快更可靠地上传。 |
占用服务器和网络带宽资源,可能影响其他用户的访问速度。 |
监控并显示上传进度,提高用户体验。 |
如果上传中断,需要重新上传整个文件,效率低下。 |
充分利用浏览器的并发上传能力,减轻服务器负载。 |
难以显示和控制上传进度。 |
实现断点续传功能,避免重新上传已上传的分片。 |
传统文件下载 VS 文件分片下载
文件分片下载是一种通过将大文件拆分成较小的片段(分片)并同时下载它们来提高文件下载效率的技术。
问题/技术 |
传统文件下载 |
文件分片下载 |
长时间等待 |
用户可能需要等待很长时间才能开始使用大文件 |
只需下载第一个分片,客户端就可以开始使用文件 |
网络拥堵 |
如果网络带宽被大文件下载占用,其他用户可能会遇到下载速度慢的问题 |
可以使用多个并行请求来下载分片,充分利用带宽并提高整体下载速度 |
难以恢复下载 |
如果网络故障或用户中断,整个文件必须重新下载 |
如果下载被中断,只需重新下载未完成的分片,而不是整个文件 |
下载效率 |
下载速度较慢,特别是在网络不稳定或速度较慢的情况下 |
通过将大文件拆分成较小的片段并同时下载,提高文件下载效率 |
并行下载 |
不支持 |
支持,可以使用多个并行请求来下载分片 |
下载管理 |
整个文件作为一个整体进行下载 |
每个分片可以单独管理和下载,提供更好的灵活性 |
一、分片上传的实现方法
1、获取文件所有切片/分片:按指定的每个切片大小切(中断后的重新上传时,要过滤掉已上传完成的切片)。切片方法 [fileHandle readDataOfLength:chunkSize];
2、上传分片:提交时除了文件data,同时传入:总片数、当前是第几片。
3、上传所有分片后,有时候这里会额外调用服务器API告诉后台已完成全部上传。实际上后台自己也可以根据上传个数是否等于总数判断。服务端得到所有的数据片后合并数据。
其他注意点:
如果要上传的文件是录制的视频或者从系统相册中选择的视频,则该视频文件的路径必须是app的相对路径。缺点视频可被从相册中删除。
1. 定义分片上传的辅助类
首先,定义一个辅助类来管理分片上传的状态和数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
| @interface FileChunk : NSObject @property (nonatomic, strong) ChunkFileModel *fileModel; @property (nonatomic, assign) NSUInteger index; @property (nonatomic, assign) NSUInteger offset; @property (nonatomic, strong) NSData *data; @property (nonatomic, strong) NSURLResponse *response;
- (FileChunk *)initWithFileHandle(NSFileHandle *)fileHandle chunkSize:(NSUInteger)chunkSize chunkIndex:(NSUInteger)chunkIndex { self = [super init]; if (self) { chunk.data = [fileHandle readDataOfLength:chunkSize]; chunk.offset = [fileHandle offsetInFile]; chunk.index = chunkIndex; } return self; }
- (NSString *)uploadContentRangeString { return [NSString stringWithFormat:@"bytes %@-%@/total-size", @(chunk.offset), @(chunk.offset + chunk.data.length)]; } @end
+ (void)addFinishSaveChunk:(FileChunk *)chunk forFilePath:(NSString *)filePath { } + (void)getFinishSaveChunkForFilePath:(NSString *)filePath { }
+ (NSArray *)chunksFromFilePath:(NSString *)filePath chunkSize:(NSUInteger)chunkSize withoutChunkIndexs:(NSArray<NSUInteger>)chunkIndexs { NSMutableArray<FileChunk *> *chunks = [NSMutableArray array]; NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:filePath]; [fileHandle seekFileOffset:0]; int chunkIndex = 0; FileChunk *chunk = [[FileChunk alloc] initWithFileHandle:fileHandle chunkSize:chunkSize chunkIndex:chunkIndex]; while (chunk.data) { chunkIndex++; if ([chunkIndexs contains chunk.index]) { continue; } [chunks addObject:chunk]; } [fileHandle closeFile]; return chunks; }
+ (void)uploadChunk:(FileChunk *)chunk uploadId:(NSString *)uploadId completionHandler:(void (^)(BOOL success, NSError *error))completionHandler { NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"your-upload-chunk-api-endpoint"]]; request.HTTPMethod = @"PUT"; request.HTTPBody = chunk.data; [request setValue:chunk.uploadContentRangeString forHTTPHeaderField:@"Content-Range"];
NSURLResponse *response = nil; NSError *error = nil; [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error]; if (error) { completionHandler(NO, error); return; } chunk.response = response; [self addFinishSaveChunk:chunk forFilePath:chunk.filePath]; completionHandler(YES, nil); }
+ (void)uploadChunks:(NSArray<FileChunk *> *)chunks withUploadId:(NSString *)uploadId completionHandler:(void (^)())completionHandler { dispatch_group_t group = dispatch_group_create(); NSMutableArray *uploadTasks = [NSMutableArray array]; for (FileChunk *chunk in chunks) { dispatch_group_enter(group); [FileChunkHelper uploadChunk:chunk uploadId:uploadId completionHandler:^(BOOL success,NSError *error) { completionHandler(success, error); dispatch_group_leave(group); }];
[uploadTasks addObject:@{@"task": @"uploadChunk", @"chunk": chunk}]; }
dispatch_group_notify(group, dispatch_get_main_queue(), ^{ completionHandler(); }); }
|
2. 分片上传
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| - (void)requestUploadInfoForFilePath:(NSString *)filePath { NSDictionary *parameters = @{ @"file_name": @"filename.txt", @"file_size": @([FileChunkHelper getFileSize:filePath]), @"part_size": @(partSize) }; if (resultModel.uploadFinish) { NSString *url = resultModel.url; return; } NSString *uploadId = resultModel.uploadId; NSUInteger chunkSize = 1024 * 1024; NSArray<NSUInteger> chunkIndexs = [FileChunkHelper getFinishSaveChunkForFilePath:filePath]; NSArray<FileChunk *> *chunks = [FileChunkHelper chunksFromFilePath:filePath chunkSize:chunkSize withoutChunkIndexs:chunkIndexs]; [FileChunksUpload uploadChunks:chunks withUploadId:uploadId completionHandler:^(void){ }]; }
|
二、文件分段下载
iOS 文件分段下载
iOS文件下载,断点续传,后台下载.
NSURLSessionDataTask 方式断点续传之下载
原理:
每次向服务器下载数据的时候,告诉服务器从整个文件数据流的某个还未下载的位置开始下载,然后服务器就返回从那个位置开始的数据流.通过设置请求头Range可以指定每次从服务器下载数据包的大小.
Range示例
bytes = 0-499 从0到499的头500个字节
bytes = 500-999 从500到999第二个500个字节
bytes = 1000- 从1000字节以后所有的字节
bytes = -500 最后500个字节
bytes = 0-499,500-999 同时指定几个范围
1 2 3 4
| > > NSString *range = [NSString stringWithFormat:@"bytes=%lld-", self.currentLength]; > [request setValue:range forHTTPHeaderField:@"Range"]; >
|
数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| > > self.stream = [[NSOutputStream alloc] initToFileAtPath:filePath append:YES]; > > > [self.stream open]; > completionHandler(NSURLSessionResponseAllow); > > > [self.stream write:[data bytes] maxLength:data.length]; > > > [self.stream close]; > self.stream = nil; >
|
大文件视频上传:断点续传
End