@@ -20,32 +20,43 @@ class FFmpegCommandBuilder {
2020 final ua = headers? ['user-agent' ] ?? defaultUserAgent;
2121 final headerStr = _buildHeader (headers);
2222 final normalizedOutputPath = '$outputDir ${Platform .pathSeparator }%Y%m%d_%H%M%S.ts' ;
23- final rwTimeoutMicro = (rwTimeout * 1000000 ).clamp (0 , 2147483647 );
24-
2523 final args = < String > [
2624 '-y' ,
2725 '-hide_banner' ,
2826 '-loglevel' , 'info' ,
27+ // 允许的协议白名单,确保直播流能正常加载
2928 '-protocol_whitelist' , 'httpproxy,udp,rtp,tcp,tls,data,file,http,https,crypto' ,
29+
30+ // --- 重连与网络优化 ---
3031 '-reconnect' , '1' ,
3132 '-reconnect_streamed' , '1' ,
3233 '-reconnect_delay_max' , '2' ,
3334 '-reconnect_at_eof' , '1' ,
34- '-rw_timeout' , rwTimeoutMicro. toString (),
35+ '-rw_timeout' , '${ rwTimeout * 1000000 }' , // 转化为微秒
3536 '-max_delay' , '5000000' ,
3637 '-thread_queue_size' , threadQueueSize.toString (),
37- '-user_agent' , ua,
38- if (headerStr.isNotEmpty) ...['-headers' , _quote (headerStr)], //关键:headers
39- '-i' , url,
38+
39+ // --- 身份伪装 ---
40+ '-user_agent' , _quote (ua),
41+ if (headerStr.isNotEmpty) ...['-headers' , _quote (headerStr)], // 关键:headers必须包裹在引号内
42+ // --- 输入 ---
43+ '-i' , _quote (url),
44+
45+ // --- 轨道处理 ---
46+ // 使用 copy 模式避免 CPU 占用过高
4047 '-map' , preferBestStream ? '0:v:0' : '0:v' ,
4148 '-map' , preferBestStream ? '0:a:0' : '0:a' ,
4249 '-c' , 'copy' ,
50+
51+ // --- 分段逻辑 ---
4352 '-f' , 'segment' ,
4453 '-segment_format' , 'mpegts' ,
4554 '-segment_time' , segmentTime.toString (),
4655 '-reset_timestamps' , '1' ,
4756 '-strftime' , '1' ,
48- normalizedOutputPath,
57+
58+ // 输出路径 (使用 .ts 格式以防断流导致文件损坏)
59+ _quote (normalizedOutputPath),
4960 ];
5061
5162 return args.join (' ' );
@@ -57,6 +68,8 @@ class FFmpegCommandBuilder {
5768 .where ((e) => e.key.toLowerCase () != 'user-agent' )
5869 .map ((e) => '${e .key }: ${e .value }' )
5970 .join ('\r\n ' );
71+
72+ // FFmpeg 要求 headers 末尾也必须有换行符
6073 return lines.isEmpty ? '' : '$lines \r\n ' ;
6174 }
6275}
0 commit comments