• HLS直播协议详解



    前言

    本文对 HLS 协议进行了详细的讲解,由浅入深,一点儿点儿揭开其神秘面纱。

    首先我们先使用 ffmepg 对一段视频文件进行切片,视频所在路径:D:\Work\test
    在这里插入图片描述
    切片命令行如下:

    ffmpeg -i SampleVideo_1280x720_20mb.mp4 -fflags flush_packets -max_delay 2 -flags -global_header -hls_time 5 -hls_list_size 0 -vcodec libx264 -acodec aac -r 30 -g 60 -y index.m3u8
    
    • 1

    这个命令是使用FFmpeg工具进行视频转码和分段处理的操作。下面是对每个参数的详细解释:

    • ffmpeg: FFmpeg命令行工具的名称,用于处理音视频文件。
    • -i SampleVideo_1280x720_20mb.mp4: 指定输入文件的路径和文件名。这里的输入文件是名为 “SampleVideo_1280x720_20mb.mp4” 的视频文件。
    • -fflags flush_packets: 强制立即刷新输出文件的数据包。
    • -max_delay 2: 设置最大延迟时间为2秒,以确保尽可能快地输出数据。
    • -flags -global_header: 禁用全局文件头,不将文件头写入每个分段文件。
    • -hls_time 5: 设置HLS(HTTP Live Streaming)分段的时长为5秒。这将影响生成的.m3u8文件中每个.ts分段文件的时长。
    • -hls_list_size 0: 设置.m3u8文件中包含的分段列表大小为0,表示将所有分段都包含在.m3u8文件中,而不生成分段列表文件。
    • -vcodec libx264: 指定使用libx264编码器进行视频编码。
    • -acodec aac: 指定使用AAC编码器进行音频编码。
    • -r 30: 设置输出视频的帧率为30帧/秒。
    • -g 60: 设置关键帧(I帧)之间的间隔为60帧。关键帧是视频编码中的重要帧,可以独立解码,而其他帧则依赖于关键帧进行解码。
    • -y index.m3u8: 将输出保存为名为"index.m3u8"的文件。这是HLS流的主索引文件,包含了指向各个分段文件的链接。

    通过执行这个命令,FFmpeg将会对输入的视频文件进行转码和分段处理,并生成一个HLS流的主索引文件(index.m3u8)和一系列分段文件(.ts文件),用于实现视频的流式传输和播放。

    在切片过程中,CPU 利用率飙升,这属于正常现象
    在这里插入图片描述
    切片后,可以在目录下看到下面的文件,ffmpeg 将源视频文件切成了 23 个子文件和一个 index.m3u8 文件
    在这里插入图片描述
    上面先有个基本的概念,下面开始我们的主题:HLS


    一、HLS 协议简介

    HLS 全称为 HTTP Live Streaming,是苹果公司提出的基于 HTTP 的流媒体网络传输协议。它的工作原理是把整个媒体流分成一个个小的基于 HTTP 的媒体分片来下载,每次只下载一些分片。在开始一个流媒体会话时,客户端会下载一个包含媒体分片的索引文件,即 extended M3U playlist 文件(m3u8),用于寻找可用的媒体分片。

    HLS 中,索引文件可以嵌套,一般只有一级索引二级索引; 媒体流封分片装格式只支持 MPEG-2 传输流(ts)、WebVTT[WebVTT]文件或 Packed Audio 文件。

    下图为索引文件(m3u8)和媒体分片(ts)之间的关系图:一级 m3u8 套二级 m3u8,二级 m3u8 描述 ts 分片。
    在这里插入图片描述

    二、HLS 总体框架

    先看下图:
    在这里插入图片描述

    • 服务器将媒体文件转换为 m3u8 及 ts 分片; 对于直播源,服务器需要实时动态更新。
    • 客户端请求 m3u8 文件,根据索引获取 ts 分片;点播与直播服务器不同的地方是,直播的 m3u8 文件会不断更新, 而点播的 m3u8 文件是不会变的,只需要客户端在开始时请求一次即可。

    客户端与服务器通过 HTTP 协议进行交互,以两级 m3u8 嵌套为例,客户端先 GET 请求到一级 m3u8,一级 m3u8 里面包含了服务器端可以用于传播的一个或多个不同带宽的 URL,这 URL 可以获取到二级 m3u8;二级 m3u8 包含了多个 ts 分片的 duration 及其 URL, 最后通过这个 URL 下载 ts 分片。

    交互的方式如下:
    在这里插入图片描述

    三、HLS 优势及劣势

    优势:

    • 客户端支持简单,只需要支持 HTTP 请求即可,HTTP 协议无状态,只需要按顺序下载媒体片段即可。
    • 使用 HTTP 协议网络兼容性好,HTTP 数据包也可以方便地通过防火墙或者代理服务器。
    • 当媒体流正在播放时,客户端可以选择从许多不同的备用源中以不同的速率下载同样的资源(多码流自适应) ,允许流媒体会话适应不同的数据速率。

    劣势:

    • 因其自身的实现方式, HLS 存在延迟(最少有一个分片),对于直播等实时敏感的场景,体验不好。

    四、HLS 主要的应用场景

    • 跨平台PC 主要的直播方案是 RTMP,也有一些库能播放 HLS,譬如 jwplayer,基于 osmf 的 hls 插件也一大堆。所以实际上如果选一种协议能跨 PC/Android/IOS,那就是 HLS。
    • IOS 上苛刻的稳定性要求:IOS 上最稳定的当然是 HLS, 稳定性不差于 RTMP 在 PC-flash
      上的表现。
    • 友好的 CDN 分发方式:目前 CDN 对于 RTMP 也是基本协议,但是 HLS 分发的基础是 HTTP,所以 CDN 的接入和分发会比 RTMP 更加完善。能在各种 CDN 之间切换,RTMP 也能,只是可能需要对接测试。
    • 简单:HLS 作为流媒体协议非常简单,apple 支持得也很完善。Android 对 HLS 的支持也
      会越来越完善。至于 DASH/HDS,好像没有什么特别的理由,就像 linux 已经大行其道而且开放,其他的系统很难再广泛应用。

    总之,SRS 支持 HLS 主要是作为输出的分发协议,直播以 RTMP+HLS 分发,满总各种应用场景。点播以 HLS 为主。

    五、M3U8 详解

    HLS 协议很大一部分内容即是对 M3U8 文本协议的描述。

    1、简介

    M3U8 即播放索引文件,也称为 Playlist,是由多个独立行组成的文本文件,必须通过 URI(.m3u8 或 .m3u)或者 HTTP Content-Type 来识别(application/vnd.apple.mpegurl 或 audio/mpegurl)。

    每行由用 \n 或者 \r\n 来标识换行。每一行可以是一个 URI、空白行或是一个 以 # 号开头的字符串。

    以 # 开头的是 tag 或者注释,以 #EXT 开头的是 tag, 其余的为注释, 在解析时应该忽略。URI 表示一个 ts 分片地址或是 Playlist 地址。 URI 可以用绝对地址或者相对地址,如果使用相对地址,那么是相对于当前 Playlist 的地址。有些 tag 带有属性值,多个属性用逗号分隔。

    常见的 m3u8 文件如下所示:

    2、一级 m3u8

    #EXTM3U
    #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=700,000
    http://xxx.itv.cmvideo.cn/low.m3u8?channel-id=bstvod&Contentid=4007432528
    #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1300,000
    http://xxx.itv.cmvideo.cn/mid.m3u8?channel-id=bstvod&Contentid=4007432527
    #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2300,000
    http://xxx.itv.cmvideo.cn/high.m3u8?channel-id=bstvod&Contentid=4007432526
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    bandwidth 指 定 视 频 流 的 比 特 率 , PROGRAM-ID 无 用 无 需 关 注 , 每 一 个 #EXT-X-STREAM-INF 的下一行是二级 index 文件的路径, 可以用相对路径也可以用绝对路径。

    例子中用的是相对路径。这个文件中记录了不同比特率视频流的二级 index 文件路径,客户端可以自己判断自己的现行网络带宽,来决定播放哪一个视频流。

    也可以在网络带宽变化的时候平滑切换到和带宽匹配的视频流。

    3、二级 m3u8

    #EXTM3U
    #EXT-X-VERSION:1
    #EXT-X-TARGETDURATION:11
    #EXT-X-MEDIA-SEQUENCE:19674922
    #EXT-X-PROGRAM-DATE-TIME:2019-03-28T04:33:40Z
    #EXTINF:10,
    19674922.ts?
    #EXT-X-PROGRAM-DATE-TIME:2019-03-28T04:33:50Z
    #EXTINF:10,
    19674923.ts?
    #EXT-X-PROGRAM-DATE-TIME:2019-03-28T04:34:00Z
    #EXTINF:10,
    19674924.ts?
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    4、tag 说明

    ①、名词说明

    • Media Playlist:二级 m3u8,携带 ts 分片 url 的 m3u8;
    • Master Playlist:一级 m3u8;
    • Media Segment:ts 分片;
    • Attribute Lists:属性列表

    Attribute Lists :

    • 有的 tags 的值带有 Attribute Lists。
    • 一个 Attribute List 是一个用逗号分隔的 attribute/value 对列表
    • 格式为:AttributeName=AttributeValue。

    ②、tag 分类

    tag 以 #EXT 开头,主要分为以下几类:

    1)Basic Tags

    Basic Tags 可以用在 Media Playlist 和 Master Playlist 里面。

    • EXTM3U:必须在文件的第一行,标识是一个 Extended M3U Playlist 文件。
    • EXT-X-VERSION:表示 Playlist 兼容的版本。
    2)Media Segment Tags

    每一个 Media Segment 通过一系列的 Media Segment tags 跟一个 URI 来指定。有的 Media Segment tags 只应用于下一个 segment,有的则是应用所有下面的 segments。一个 Media Segment tag 只能出现在 Media Playlist 里面。

    • EXTINF:用于指定 Media Segment 的 duration。
    • EXT-X-BYTERANGE:用于指定 URI 的 sub-range。
    • EXT-X-DISCONTINUITY:表示后续分片属性发生变化,如文件格式/编码/序号。
    • EXT-X-KEY:表示 Media Segment 已加密,该值用于解密。
    • EXT-X-MAP:表示 Media Segment 的头部信息,比如 PAT/PMT 或者 WebVTT 头。
    • EXT-X-PROGRAM-DATE-TIME:和 Media Segment 的第一个 sample 一起来确定时间戳。
    3)Media Playlist Tags

    Media Playlist tags 描述 Media Playlist 的全局参数。同样地,Media Playlist tags 只能出现在 Media Playlist 里面。

    • EXT-X-TARGETDURATION:用于指定最大的 Media Segment duration。
    • EXT-X-MEDIA-SEQUENCE:用于指定第一个 Media Segment 的序号。
    • EXT-X-DISCONTINUITY-SEQUENCE:用于不同 Variant Stream 之间同步。
    • EXT-X-ENDLIST:表示 Media Playlist 结束。
    • EXT-X-PLAYLIST-TYPE:可选,指定整个 Playlist 的类型。
    • EXT-X-I-FRAMES-ONLY:表示每个 Media Segment 均为 I-frame。
    4)Master Playlist Tags

    Master Playlist tags 定义 Variant Streams,Renditions 和其他显示的全局参数。Master Playlist tags 只能出现在 Master Playlist 中。

    • EXT-X-MEDIA:用于关联同一个内容的多个 Media Playlist 的多种翻译。
    • EXT-X-STREAM-INF:用于指定下级 Media Playlist 相关属性。
    • EXT-X-I-FRAME-STREAM-INF:与 EXT-X-STREAM-INF 类似,但指向的下级 Media Playlist 包含 Media Segment 均为 I-frame。
    • EXT-X-SESSION-DATA:可以随意存放一些 session 数据。
    5)Media or Master Playlist Tags

    这里的 tags 可以出现在 Media Playlist 或者 Master Playlist 中。但是如果同时出现在同一个 Master Playlist 和 Media Playlist 中时,必须为相同值。

    • EXT-X-INDEPENDENT-SEGMENTS:表示每个 Media Segment 可以独立解码。
    • EXT-X-START:标识一个优选的点来播放这个 Playlist。

    六、HLS 协议详解

    HLS 是提供一个 m3u8 地址:
    Apple 的 Safari 浏览器直接就能打开 m3u8 地址, 譬如:http://demo.srs.com/live/livestream.m3u8

    Android 不能直接打开, 需要使用 html5 的 video 标签, 然后在浏览器中打开这个页面即可,
    譬如:

    
    <video width="640" height="360"
    autoplay controls autobuffer
    src="http://demo.srs.com/live/livestream.m3u8"
    type="application/vnd.apple.mpegurl">
    video>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    PC:video.js

    1、HLS 协议规定

    视频的封装格式TS

    视频的编码格式为 H264,音频编码格式为 MP3、 AAC 或者 AC-3。

    除了 TS 视频文件本身, 还定义了用来控制播放的 m3u8 文件(文本文件)

    2、HLS 协议说明

    HLS 的 m3u8,是一个 ts 的列表,也就是告诉客户端或浏览器可以播放这些 ts 文件, 譬如:

    #EXTM3U
    #EXT-X-VERSION:3
    #EXT-X-ALLOW-CACHE:YES
    #EXT-X-TARGETDURATION:13
    #EXT-X-MEDIA-SEQUENCE:430
    #EXT-X-PLAYLIST-TYPE:VOD
    #EXTINF:11.800
    news-430.ts
    #EXTINF:10.120
    news-431.ts
    #EXT-X-DISCONTINUITY
    #EXTINF:11.952
    news-430.ts
    #EXTINF:12.640
    news-431.ts
    #EXTINF:11.160
    news-432.ts
    #EXT-X-DISCONTINUITY
    #EXTINF:11.751
    news-430.ts
    #EXTINF:2.040
    news-431.ts
    #EXT-X-ENDLIST
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • EXTM3U
      • 每个 M3U 文件第一行必须是这个 tag,提供标示作用
    • EXT-X-VERSION
      • 用以标示协议版本。这里是 3,那么这里用的就是 HLS 协议第三个版本,此标签只能有 0 或 1 个, 不写代表使用版本 1
    • EXT-X-TARGETDURATION
      • 所有切片的最大时长,有些 Apple 设备这个参数不正确会无法播放。
    • EXT-X-MEDIA-SEQUENCE
      • 切片的开始序号。每一个切片都有唯一的序号,相邻之间序号+1。这个编号会继续增长,保证流的连续性。
    • EXTINF
      • ts 切片的实际时长。duration:媒体持续时间
      • #EXTINF ,</li></ul> </li><li><strong><font color="red">EXT-X-PLAYLIST-TYPE</font></strong> <ul><li>类型,vod 表示点播,live 表示直播。</li></ul> </li><li><strong><font color="red">EXT-X-ENDLIST</font></strong> <ul><li>文件结束符号,表示不再向播放列表文件添加媒体文件。</li></ul> </li></ul> <p>#EXT-X-PLAYLIST-TYPE:VOD 的意思是当前的视频流并不是一个直播流,而是点播流,换句话说就是该视频的全部的 ts 文件已经被生成好了,#EXT-X-ENDLIST 这个表示视频结束,有这个标志同时也说明当前的流是一个非直播流。</p> <h3><a name="t16"></a><a id="3_228"></a>3、播放模式</h3> <p><font color="red">点播 VOD</font> 的特点就是当前时间点可以获取到所有 index 文件和 ts 文件,二级 index 文件中记录了所有 ts 文件的地址。这种模式允许客户端访问全部内容。上面的例子中就是一个点播模式下的 m3u8 的结构。</p> <p><font color="red">Live 模式</font>就是实时生成 M3u8 和 ts 文件。它的索引文件一直处于动态变化的,播放的时候需要不断下载二级 index 文件,以获得最新生成的 ts 文件播放视频。如果一个二级 index文件的末尾没有#EXT-X-ENDLIST 标志, 说明它是一个 Live 视频流。</p> <blockquote> <ul><li>客户端在播放 VOD 模式的视频时其实只需要下载一次一级 index 文件和二级 index 文件就可以得到所有 ts 文件的下载地址,除非客户端进行比特率切换,否则无需再下载任何 index文件,只需顺序下载 ts 文件并播放就可以了。</li><li>但是 Live 模式下略有不同,因为播放的同时,新 ts 文件也在被生成中,所以客户端实际上是下载一次二级 index 文件,然后下载 ts 文件,再下载二级 index 文件(这个时候这个二级 index 文件已经被重写,记录了新生成的 ts 文件的下载地址),再下载新 ts 文件,如此反复进行播放。</li></ul> </blockquote> <h3><a name="t17"></a><a id="4TS__235"></a>4、TS 文件</h3> <p>ts 文件为<font color="red">传输流文件(MPEG2 - tranport stream),</font>视频编码主要格式 h264/mpeg4,音频为 acc/MP3。</p> <p><strong>ts 文件分为三层:</strong> ts 层(Transport Stream)、 pes 层(Packet Elemental Stream)、 es 层(Elementary Stream)</p> <p>es 层就是音视频压缩数据,pes 层是在音视频数据 es 上加了时间戳(pts,dts)等对数据帧的说明信息,ts 层就是在 pes 层加入数据流的识别和传输必须的信息。<br> <img src="https://1000bd.com/contentImg/2024/03/25/b5701d1242ec12fe.png" alt="在这里插入图片描述"></p> <ul><li><strong><font color="red">ts 层</font></strong> <ul><li>ts 包大小固定为 188 字节,ts 层分为三个部分:ts header、adaptation field、payload。ts header 固定 4 个字节;adaptation field 可能存在也可能不存在,主要作用是给不足 188 字节的数据做填充;payload 是 pes 数据。</li></ul> </li><li><strong><font color="red">pes 层</font></strong> <ul><li>pes 层是在每一个视频/音频帧上加入了时间戳等信息,pes 包内容很多,我们只留下最常用的。</li></ul> </li><li><strong><font color="red">es 层</font></strong> <ul><li>es 层指的就是音视频数据, 我们只介绍 h.264 视频。</li></ul> </li></ul> <p>h.264 视频:打包 h.264 数据我们必须给视频数据加上一个 nalu(Network Abstraction Layer unit),nalu 包括 nalu header 和 nalu type,nalu header 固定为 0x00000001(帧开始)或 0x000001(帧中)。</p> <p>h.264 的数据是由 slice 组成的, slice 的内容包括:视频、sps、pps 等。</p> <p>nalu type 决定了后面的 h.264 数据内容。<br> <img src="https://1000bd.com/contentImg/2024/03/25/717a62c841f2a264.png" alt="在这里插入图片描述"></p> <hr> <p><mark>我的qq:2442391036,欢迎交流!</mark></p> <hr> </div> </div> </li> <li class="list-group-item ul-li"> <b>相关阅读:</b><br> <nobr> <a href="/Article/Index/1004100">django梳理</a> <br /> <a href="/Article/Index/984112">make quick-example I: 变量语法 & 变量插值</a> <br /> <a href="/Article/Index/1298815">微信小程序实现删除功能</a> <br /> <a href="/Article/Index/1183261">【矩阵论】4. 矩阵运算——广义逆——减号逆</a> <br /> <a href="/Article/Index/759756">LayaBox---TypeScript---JavaScript文件类型检查</a> <br /> <a href="/Article/Index/1360117">Linux shell编程学习笔记10:expr命令 和 算术运算</a> <br /> <a href="/Article/Index/846910">《高性能网站建设进阶指南》阅读笔记</a> <br /> <a href="/Article/Index/1048724">swagger工具编写接口文档</a> <br /> <a href="/Article/Index/990964">dplyr高级教程 tidyverse ADVANCED DATA MANIPULATION 高级操作复杂操作数据筛选根据条件筛选列行 根据条件取行列 dataframe 矩阵 matrix数据框</a> <br /> <a href="/Article/Index/939256">在Linux上使用yum安装MySQL</a> <br /> </nobr> </li> <li class="list-group-item from-a mb-2"> 原文地址:https://blog.csdn.net/qq_41839588/article/details/134093390 </li> </ul> </div> <div class="col-lg-4 col-sm-12"> <ul class="list-group" style="word-break:break-all;"> <li class="list-group-item ul-li-bg" aria-current="true"> 最新文章 </li> <li class="list-group-item ul-li"> <nobr> <a href="/Article/Index/1484446">攻防演习之三天拿下官网站群</a> <br /> <a href="/Article/Index/1515268">数据安全治理学习——前期安全规划和安全管理体系建设</a> <br /> <a href="/Article/Index/1759065">企业安全 | 企业内一次钓鱼演练准备过程</a> <br /> <a href="/Article/Index/1485036">内网渗透测试 | Kerberos协议及其部分攻击手法</a> <br /> <a href="/Article/Index/1877332">0day的产生 | 不懂代码的"代码审计"</a> <br /> <a href="/Article/Index/1887576">安装scrcpy-client模块av模块异常,环境问题解决方案</a> <br /> <a href="/Article/Index/1887578">leetcode hot100【LeetCode 279. 完全平方数】java实现</a> <br /> <a href="/Article/Index/1887512">OpenWrt下安装Mosquitto</a> <br /> <a href="/Article/Index/1887520">AnatoMask论文汇总</a> <br /> <a href="/Article/Index/1887496">【AI日记】24.11.01 LangChain、openai api和github copilot</a> <br /> </nobr> </li> </ul> <ul class="list-group pt-2" style="word-break:break-all;"> <li class="list-group-item ul-li-bg" aria-current="true"> 热门文章 </li> <li class="list-group-item ul-li"> <nobr> <a href="/Article/Index/888177">十款代码表白小特效 一个比一个浪漫 赶紧收藏起来吧!!!</a> <br /> <a href="/Article/Index/797680">奉劝各位学弟学妹们,该打造你的技术影响力了!</a> <br /> <a href="/Article/Index/888183">五年了,我在 CSDN 的两个一百万。</a> <br /> <a href="/Article/Index/888179">Java俄罗斯方块,老程序员花了一个周末,连接中学年代!</a> <br /> <a href="/Article/Index/797730">面试官都震惊,你这网络基础可以啊!</a> <br /> <a href="/Article/Index/797725">你真的会用百度吗?我不信 — 那些不为人知的搜索引擎语法</a> <br /> <a href="/Article/Index/797702">心情不好的时候,用 Python 画棵樱花树送给自己吧</a> <br /> <a href="/Article/Index/797709">通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,连憨憨学妹都学会了!</a> <br /> <a href="/Article/Index/797716">13 万字 C 语言从入门到精通保姆级教程2021 年版</a> <br /> <a href="/Article/Index/888192">10行代码集2000张美女图,Python爬虫120例,再上征途</a> <br /> </nobr> </li> </ul> </div> </div> </div> <!-- 主体 --> <!--body结束--> <!--这里是footer模板--> <!--footer--> <nav class="navbar navbar-inverse navbar-fixed-bottom"> <div class="container"> <div class="row"> <div class="col-md-12"> <div class="text-muted center foot-height"> Copyright © 2022 侵权请联系<a href="mailto:2656653265@qq.com">2656653265@qq.com</a>    <a href="https://beian.miit.gov.cn/" target="_blank">京ICP备2022015340号-1</a> </div> <div style="width:300px;margin:0 auto; padding:0px 5px;"> <a href="/regex.html">正则表达式工具</a> <a href="/cron.html">cron表达式工具</a> <a href="/pwdcreator.html">密码生成工具</a> </div> <div style="width:300px;margin:0 auto; padding:5px 0;"> <a target="_blank" href="http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=11010502049817" style="display:inline-block;text-decoration:none;height:20px;line-height:20px;"> <img src="" style="float:left;" /><p style="float:left;height:20px;line-height:20px;margin: 0px 0px 0px 5px; color:#939393;">京公网安备 11010502049817号</p></a> </div> </div> </div> </div> </nav> <!--footer--> <!--footer模板结束--> <script src="/js/plugins/jquery/jquery.js"></script> <script src="/js/bootstrap.min.js"></script> <!--这里是scripts模板--> <!--scripts模板结束--> </body> </html>