如何用NanoHttpd实现小米视频无需加载全文件播放MKV/MP4视频(Android)
核心原因
小米视频的播放器内置了文件大小判断逻辑,对低于预设阈值(通常为100~200MB)的视频,默认会优先尝试全量下载后再播放;对超过阈值的视频,才会主动发送Range范围请求分段加载。另外你的实现里也存在两处细节问题,放大了这个现象:
- 仅在206响应头中添加了
Accept-Ranges标识,200全量响应未添加该头,播放器无法第一时间识别你的服务支持分段加载,因此对小文件直接走了全量下载逻辑 - 发送响应数据时没有主动flush输出流,操作系统TCP层的缓冲区会积压数据,导致播放器迟迟收不到视频头部的元数据,进一步拉长了等待时间
修复方案
1. 全响应添加分段支持标识
修改createFullResponse方法,不管返回200还是206响应,都带上Accept-Ranges: bytes头,明确告诉播放器服务支持分段请求:
private Response createFullResponse(String mimeType, InputStream inputStream, long length) { Response response = newFixedLengthResponse(Response.Status.OK, mimeType, inputStream, length); response.addHeader("Accept-Ranges", "bytes"); response.addHeader("Content-Length", String.valueOf(length)); return response; }
2. 小体积视频强制触发分段加载
对小于阈值的视频,首次请求直接返回206部分内容,主动切断全量下载逻辑,逼迫播放器后续用Range请求拉取剩余内容:
private Response createFullResponse(String mimeType, InputStream inputStream, long length) { // 对小于500MB的视频,首次仅返回前2MB内容 if (length < 500 * 1024 * 1024) { long partialLen = Math.min(2 * 1024 * 1024, length); Response response = newFixedLengthResponse(Response.Status.PARTIAL_CONTENT, mimeType, inputStream, partialLen); response.addHeader("Accept-Ranges", "bytes"); response.addHeader("Content-Length", String.valueOf(partialLen)); response.addHeader("Content-Range", String.format("bytes 0-%d/%d", partialLen - 1, length)); return response; } Response response = newFixedLengthResponse(Response.Status.OK, mimeType, inputStream, length); response.addHeader("Accept-Ranges", "bytes"); response.addHeader("Content-Length", String.valueOf(length)); return response; }
3. 响应数据及时flush
修改sendBody方法,每次写完数据后主动flush输出流,避免数据积压在本地缓冲区:
private void sendBody(OutputStream outputStream, long pending) throws IOException { long BUFFER_SIZE = 1048576L; // Instead of 16384 byte[] buff = new byte[(int)BUFFER_SIZE]; boolean sendEverything = pending == -1L; while(pending > 0L || sendEverything) { long bytesToRead = sendEverything ? BUFFER_SIZE : Math.min(pending, BUFFER_SIZE); int read = this.data.read(buff, 0, (int)bytesToRead); if (read <= 0) { break; } outputStream.write(buff, 0, read); outputStream.flush(); // 新增行,及时发送数据 if (!sendEverything) { pending -= (long)read; } } }
4. 优化视频文件元数据位置
- 对于MP4格式,保证
moov原子位于文件头部,不要放在尾部,可通过ffmpeg命令处理:ffmpeg -i 输入.mp4 -movflags faststart 输出.mp4 - 对于MKV格式,保证segment索引信息位于文件头部,可通过mkvtoolnix工具重新混流处理
内容的提问来源于stack exchange,提问作者Shir Barzur
相关产品推荐
相关产品推荐

