Spring Cloud / Alibaba 微服务架构实战
超清原画 完整无密 包括所有视频课件以及源码
点击下崽:网盘链接
挪动端音视频需务实现计划探究
写作背景
最近的项目是一个健身类的Flutter App,其中中心功用是锻炼课程的播放。由于多种要素招致用户体验十分差,因而在接手项目的半年里我对这块功用做了2次改造,并且预研了一套长期的计划以支持课程播放这个中心功用的持续迭代。所以记载分享给大家,防止反复踩坑走弯路。
需求阐明的是:音视频(或者说任何技术难题)的难点都是在于处理计划,而不是任何详细的完成代码;而计划常常不是一蹴而就,由于触及到前端需求、开发资源、团队资源等一大堆问题,接下来我会着重聊聊我在音视频需务实现计划的整个探究过程。
落实计划的坎坷之路
一、亡羊补牢
项目初期,App的课程直接在线播放,也不做缓存机制,用户锻炼过程中停止呈现锻炼5秒等候10秒的状况,而且每次都在线加载,极端消耗流量。因而我们对视频停止剖析得到码率居然到达了16000kbps+,以致于短短30秒的视频就有60MB,这是播放过程中总是缓冲的缘由;同时视频的索引信息没有做优化,moov atom放在末尾,播放器解码速度慢,招致初次等候时间长。
鉴于运营才能,只能先运用紧缩工具减藐视频大小。我这边紧急上线一个下载功用,锻炼之前把一切课程章节的视频缓存到本地,锻炼过程运用本地资源播放。技术方面Flutter直接运用dio download按次第下载。
二、与时俱进
经过暂时上线的下载功用,用户能够顺畅锻炼;但很快又引出下一个问题:视频大小压不下,10个章节可能需求5 min+的下载时间,同时文件完好性没有做校验,出错率较高。如何处理?
-
从源头动手:经过跟剪辑人员的大量交流以及不时制造demo查看效果,我们发现一个尺寸为
1080 * 1080的mp4,码率在2000左右、帧数峰值25,在任何分辨率/dpi的手机屏幕上,流利度和明晰度都完整没问题。然后再经过专业的紧缩工具,根本每分钟的视频能够控制在8M以下。因而请求内容团队的同事依照这个尺寸出视频即可。 -
优化下载机制:下载的机制从最简单粗暴的按次第下载改为:下载第一章节,进入锻炼页面会启动后台下载,同时支持断点下载。
由此减少用户的等候下载的时间,同时防止用户切章节的时分,原先未完成下载的章节作废,节约了反复下载的流量。(主要也给效劳器节流😊)
这里聊下Flutter断点下载的完成:
-明白下载过程中的文件我们以.mp4.temp后缀名结尾,下载完成的文件以mp4结尾;
-读取本地缓存中此资源未完成下载的文件长度;
-把已下载的长度设置在dio get恳求headers的"range": "bytes=$downloadStart-"中;
-经过stream把下载进度通知给调用方。Future downloadFile({ required String url, required String savePath, // 本地缓存的途径 required CancelToken cancelToken, // 下载凭证由调用方传入,以操作下载节点(如:取消) ProgressCallback? onReceiveProgress, void Function()? done, void Function(Exception)? failed, }) async { int downloadStart = 0; File f = File(savePath); if (await f.exists()) { downloadStart = f.lengthSync(); } print("start: downloadStart"); try { var response = await downloadDio.get( url, options: Options( /// Receive response data as a stream responseType: ResponseType.stream, followRedirects: false, headers: { /// Downloading key locations in segments "range": "bytes=downloadStart-", }, ), ); File file = File(savePath); RandomAccessFile raf = file.openSync(mode: FileMode.append); int received = downloadStart; int total = await getContentLength(response); Stream stream = response.data!.stream; StreamSubscription? subscription; subscription = stream.listen( (data) { /// Write files must be synchronized raf.writeFromSync(data); received += data.length; onReceiveProgress?.call(received, total); }, onDone: () async { file.rename(savePath.replaceAll('.temp', '')); await raf.close(); done?.call(); }, onError: (e) async { await raf.close(); failed?.call(e); }, cancelOnError: true, ); cancelToken.whenCancel.then(() async { await subscription?.cancel(); await raf.close(); }); } on DioError catch (error) { if (CancelToken.isCancel(error)) { print("Download cancelled"); } else { failed?.call(error); } } } 复制代码
-
文件完好性校验:运用md5编码对文件完好性停止校验,运营平台上传视频时停止编码,App端下载胜利后进也行md5编码,二者相同则断定文件完好。
File(path).readAsBytes().then((Uint8List str) { if (md5.convert(str).toString() == md5Str) { // md5Str是效劳端返回的编码 // 二者相同,文件完好 } });