ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

浏览器调用VLC插件全指南:兼容性避坑与替代方案

浏览器调用VLC插件全指南:兼容性避坑与替代方案 简介面向需要在网页中嵌入 VLC 播放能力的前端开发与系统运维人员这份资源聚焦 HTML5 环境下调用浏览器 VLC 插件的完整实现。压缩包仅含 1 个 docx 文档体积约 48KB内容精炼而聚焦直接给出 Windows 设备端注册 axvlc.dll 的命令步骤并附有 object 标签嵌入示例涵盖自动播放、循环、音量、视频源等关键参数。文档采用“环境准备—页面嵌入—兼容性验证”的叙述结构先讲如何注册插件再展示嵌入代码最后总结测试结论特别记录了 360 浏览器、Chrome、IE 的实测通过情况同时明确指出 Android 手机端不支持的现象与可能原因帮助读者在方案选型和排错阶段规避移动端适配风险。此外文档末尾提供三个相关参考链接便于按需深挖底层实现。目前已有 8371 人学习下载适合需要快速搭建基于 VLC 插件的本地或内网视频播放页面的技术人员。1. 浏览器调用VLC插件从播放器到网页的最后一公里“浏览器调用VLC插件”这句话在五六年前是内网监控平台、视频课件站和广电级流媒体调试工具里的高频需求。它的本质很直接把VLC这个桌面播放器以插件的形式塞进网页让浏览器页面有能力直接播放MKV、RMVB、RTSP流这类原生HTML5视频标签搞不定的格式。今天主流浏览器早就把插件机制清退干净了但大量存量业务系统还在依赖这个老方案新项目也常有业务方点名要求兼容。这篇笔记把这条路从原理到落地讲完整代码怎么写、参数怎么调、踩过哪些坑以及插件彻底失效后的替代路径。适合要接手老系统、或者在内网做视频播放方案的工程师。2. 先搞清楚VLC插件是什么NPAPI、ActiveX与三条现实存活路径2.1 VLC Web插件的底层机制为什么它能在网页里播本地视频VLC插件不是一个简单的“网页播放器壳子”它直接把libvlc核心库编译成了浏览器插件形态。在Windows平台上VLC插件同时提供两套接口NPAPI给Firefox和早期Chrome用ActiveX控件给IE用Linux和macOS上则只有NPAPI版本。插件被浏览器加载后页面里的object标签不再是一个代理对象它本质上就是VLC播放器实体播放、暂停、音量、进度这些操作全部通过libvlc的脚本桥接层暴露给JavaScript。这套结构决定了VLC插件的能力上限就是VLC播放器的能力上限。视频文件能播什么网页里就能播什么包括硬件解码、字幕轨切换、多音轨选择、网络流协议RTSP、MMS、HTTP-FLV都不在话下。对比之下HTML5 video标签只支持H.264/WebM等少数封装格式面对老监控设备输出的MJPEG、私有RTSP流或者教育行业存量TS流基本没有还手之力。这正是很多内网系统宁可带着老浏览器也要保留VLC插件的原因不是不想换是业务流格式换不掉。插件和浏览器网页的通信分成两条线。第一条是资源嵌入也就是object标签本身浏览器根据MIME类型application/x-vlc-plugin把插件实例化到页面上第二条是脚本桥接插件通过COM或XPCOM把方法暴露给window对象网页脚本拿到插件节点后直接调用.playlist.add()、.audio.volume这类接口。理解这两条线后面排错才能有的放矢——页面空白可能卡在第一条线能显示画面但控制不了则大概率是第二条线的问题。2.2 浏览器兼容性盘点Chrome、Edge、Firefox谁还认它要说清楚这个方案当前能跑在哪儿先看一张大致的兼容性表格。这张表只针对NPAPI插件ActiveX单独看IE那一列。浏览器NPAPI支持状态现实可用性谷歌浏览器ChromeChrome 422015年默认禁用45起彻底移除不可用EdgeChromium内核从未支持NPAPI不可用FirefoxFirefox 522017年起移除非Flash NPAPI不可用IE 8-11不支持NPAPI但支持ActiveX控件老系统可跑Firefox ESR 52及更早版本完整支持NPAPI单机部署可用这张表说明了一个残酷现实在企业内网里想继续用VLC插件要么守着一台装了旧版Firefox ESR或Chrome 41的专用终端要么退回IE阵营用ActiveX控件。实践中我见过不少项目走的是“专用播放终端”路线——一台锁死版本的小主机浏览器只管加载页面、加载插件不做任何日常升级。这个方案维护成本可控安全性上则要求这台机器不碰外网当独立设备管理。2.3 三条现实落地路径ActiveX、NPAPI残留与本地协议回调第一条路径是ActiveX只适用于IE和基于IE内核的国产浏览器壳。写入方式是把object的classid指向VLC的ActiveX控件然后依靠IE的信任策略放行。这条路的好处是Windows内网兼容性好坏处是IE本身已经被淘汰新电脑上跑IE的兼容性反而更麻烦。第二条路径是NPAPI残留。VLC在Debian/Ubuntu老版本仓库里带过browser-plugin-vlc这个包装VLC时会顺带把插件装上Windows的VLC安装包在早期版本里也带过Web Plugin组件。想用这条路径就得找老版本的Firefox ESR或者定制浏览器并且把站点域名加入允许列表。这里要留意安装VLC时插件不是默认组件需要在安装界面勾选很多人翻车就是VLC装了但Web Plugin没勾浏览器里永远看不到插件对象。第三条路径是本地协议回调严格说已经不算“插件调用”但它是从浏览器调用VLC最常见也最稳的方式网页不内嵌播放器而是通过自定义URL Scheme或者系统协议关联把链接甩给VLC客户端去播。代价是播放器独立于浏览器弹窗没法做页面内嵌的精细控制。这三条路径不是非此即彼很多项目最终是内网终端走插件、一般员工走协议唤起两边互补。3. 最小可用嵌入用object标签在网页里跑起VLC3.1 一个能跑的HTML页面从object标签写起在支持NPAPI或ActiveX的环境里最小可用页面其实只需要一个object标签加几个param参数。下面这段代码可以直接存成一个HTML文件在Firefox ESR 52或IE里打开前提是已经装了带Web Plugin组件的VLC。!DOCTYPE html html langzh-CN head meta charsetUTF-8 titleVLC插件调用演示/title /head body h3内嵌VLC播放器RTSP监控流示例/h3 !-- type用于让浏览器识别这是VLC插件缺了这个属性浏览器会当成普通对象 -- object typeapplication/x-vlc-plugin idvlcPlayer width720 height405 eventstrue !-- mrl: 媒体资源定位符支持rtsp、http、mms以及本地文件路径 -- param namemrl valuertsp://192.168.1.88:554/stream1 / !-- autoplay: true表示页面加载完成后立即播放监控场景一般开true -- param nameautoplay valuetrue / !-- mute: false表示保留声音输出 -- param namemute valuefalse / !-- volume: 0-100之间的整数设定初始音量 -- param namevolume value80 / /object /body /html这段代码里有几个点值得单独说明。eventstrue这个属性很容易被漏掉它决定插件对象是否向页面抛出播放状态事件或者说有没有把监听接口暴露给JavaScript。如果你后面要用事件回调来感知播放是否失败这里必须写true。mrl参数是插件加载时要打开的媒体地址这里用了RTSP监控流这是实际项目里最典型的用法如果要播本地文件路径要注意反斜杠和正斜杠的处理Windows下写成file:///D:/videos/test.mkv更稳。把这段页面打开后只要插件和播放源都是正常的画面就会直接出来不需要任何JavaScript参与。如果页面空白优先检查插件组件有没有装再看浏览器控制台有没有报MIME类型不支持——这两条占了运行时故障的一半以上。3.2 用JavaScript接管播放控制playlist、play、pause与音量静态页面只是第一步实际项目里要用按钮或页面逻辑控制播放进度。VLC插件的脚本接口设计比较老派几乎所有动作都挂在playlist对象下面直接调用vlc.Play()这类方法是不存在的很多第一次上手的人在这上面卡住。button onclickdoPlay()播放/button button onclickdoPause()暂停/继续/button button onclickdoStop()停止/button button onclicksetVolume(60)音量60%/button button onclickshowProgress()读进度/button script var vlc document.getElementById(vlcPlayer); function doPlay() { // 播放动作分两步先把媒体地址加入播放列表再触发播放 vlc.playlist.add(rtsp://192.168.1.88:554/stream1); vlc.playlist.play(); } function doPause() { // togglePause会在播放与暂停之间切换比单独调pause更实用 vlc.playlist.togglePause(); } function doStop() { // stop会停止当前媒体并释放解码资源 vlc.playlist.stop(); } function setVolume(val) { // audio是音频控制对象volume取0到100 vlc.audio.volume val; } function showProgress() { // input对象里position是0.0到1.0的进度time是当前秒数length是总时长 console.log(position:, vlc.input.position, time:, vlc.input.time, length:, vlc.input.length); } /script代码逻辑本身不难但有两处值得展开。一是playlist.add之后必须再调play()这个顺序在早期版本里可以合并成playlist.playItem(id)但不同VLC小版本对playItem的实现有差异用add后play是最通用的写法。二是vlc.input可能在媒体还没加载完成时返回空值所以点击“读进度”这个按钮最好在确认播放已经开始后再点否则读到的length是0。另外要注意插件里的audio.volume是VLC自己维护的音量值和操作系统音量无关和页面的CSS无关。把页面按钮的显示值同步到这个属性上需要自己写联动逻辑插件不会帮你同步UI。3.3 必调参数详解autoplay、mute、volume与缓冲选项VLC插件的param参数不算多但每个都直接影响运行表现。下表是实际项目里最常用的一组参数和它们的默认行为。参数名类型默认值说明mrlstring空初始媒体地址支持rtsp、http、mms、本地路径autoplaybooleanfalse页面加载后是否立即播放mutebooleanfalse静音启动部分网页希望用户主动点击后出声volumeinteger 0-10050初始音量loopbooleanfalse播放列表循环fullscreenbooleanfalse是否全屏显示textstring空叠加在画面上的提示文字这些参数中autoplay和mute是现场环境里最常用的两个。做监控墙时页面打开就要出画面两者都按true和false配做课件系统时通常会mute为true避免十几个课件页面同时出声吵翻机房。loop多见于信息发布屏的滚动视频广告机场景很实用。除了初始param插件运行时还能改几个关键属性vlc.audio.volume控制音量vlc.audio.mute控制静音vlc.input.position可以读取也可以写入写入0.5就会跳到媒体一半的位置。这些动态接口才是页面控制逻辑真正的基石做进度条、音量滑杆都靠它们。要提醒的是text参数在部分Linux版本的插件里不生效如果要叠加字幕或文字水印最好在VLC端做不要指望插件统一渲染。4. VLC插件调用避坑指南黑屏、崩溃与兼容性排查4.1 插件未安装或版本不匹配检测与提示的正确姿势现象页面打开后本应出现播放器的区域一片空白控制台显示application/x-vlc-plugin未被支持或者提示缺少插件。 原因最常见的是VLC虽然装了但安装时没勾选Web Plugin浏览器插件组件其次是64位浏览器配了32位插件或者反过来插件进程压根没被加载。 解决先做一次探测再给用户明确提示别让页面白屏。探测逻辑用下面这段代码能覆盖大部分情况。// 页面加载后检测当前浏览器有没有可用的VLC插件 function detectVLCPlugin() { // 标准浏览器用navigator.plugins查找插件名 if (navigator.plugins navigator.plugins.length 0) { for (var i 0; i navigator.plugins.length; i) { var pluginName navigator.plugins[i].name.toLowerCase(); if (pluginName.indexOf(vlc) ! -1) { return true; } } } // IE老内核走ActiveX探测 try { new ActiveXObject(VideoLAN.VLCPlugin.2); return true; } catch (e) { return false; } } if (!detectVLCPlugin()) { document.getElementById(playerArea).innerHTML 未检测到VLC插件请安装完整版VLC播放器安装时勾选Web Plugin组件; }这段代码在Firefox ESR里走navigator.plugins分支在IE里走ActiveX分支两种环境都覆盖到了。安装VLC时如果用的是官网下载的完整安装包默认界面下Web Plugin组件不一定会勾选需要在“选择组件”步骤里手动展开勾上。版本匹配上32位浏览器配32位VLC、64位浏览器配64位VLC混装是白屏重灾区尤其老项目里经常留着32位浏览器但新装了64位VLC。4.2 黑屏但有声音渲染模式与硬件加速的冲突现象拖入RTSP流或本地视频后能听到声音画面却是全黑的或者画面停在第一帧不动。 原因VLC插件默认启用硬件解码老显卡驱动对硬件加速的支持不完整加上浏览器自身的GPU合成和插件渲染区域冲突最终解码后的画面没有送达到object区域。 解决先把插件的硬解关掉再验证这是最快定位手段。常见做法是在object标签里补一个param强制走软件解码写法如下。object typeapplication/x-vlc-plugin idvlcPlayer width720 height405 eventstrue param namemrl valuertsp://192.168.1.88:554/stream1 / param nameautoplay valuetrue / !-- 强制关闭硬件解码解决黑屏但有声音的问题 -- param namehardware-decoding valuefalse / /object注意hardware-decoding这个参数在不同小版本里的名字并不统一有的版本叫enable-hardware-decoding有的版本在JS里通过vlc.video.hardwareDecoding控制。这类参数跨版本改名的玄学在这儿很常见最靠谱的办法是在目标机器上先手动用VLC播放同一路视频确认源没问题再依次试关闭硬解和关闭浏览器硬件加速两个手段。如果项目里的机器显卡配置差异大我一般直接默认禁止硬解换来的是CPU占用高一些但画面稳定。4.3 播放高码率视频就崩溃多实例与内存的坑现象播放720P以上的长时间视频浏览器标签页直接变白提示“页面无响应”或整个浏览器进程退出。 原因VLC插件运行在浏览器进程内部解码高码率视频时内存和CPU占用直接叠到浏览器头上。开多个播放器实例时内存叠加更容易触发浏览器单进程限制插件崩溃连带整个标签页一起牺牲。 解决控制并发实例数是第一原则一个页面最多同时挂两个VLC插件多了必然翻车。需要动态加载多个播放器时销毁逻辑要比创建逻辑更认真。function destroyPlayer(elementId) { // 先停止播放再移除DOM节点释放解码器资源 var vlc document.getElementById(elementId); if (vlc vlc.playlist) { vlc.playlist.stop(); } var parent vlc vlc.parentNode; if (parent) { parent.removeChild(vlc); } } // 需要重新创建播放器时从父容器重新插入object节点 function createPlayer(containerId, mrl) { var container document.getElementById(containerId); container.innerHTML object typeapplication/x-vlc-plugin idvlcTmp width640 height360 eventstrue param namemrl value mrl / param nameautoplay valuetrue / /object; }这里有个细节很多人忽略停止播放用playlist.stop()而不是直接把object节点删掉。stop会释放解码器占用的显存和音频设备直接删节点虽然视觉上也关了但底层资源不一定完全释放连续开关几个视频后照样崩。另外播放大文件时如果VLC日志里频繁出现“blocked waiting for buffer”说明磁盘或网络IO跟不上那不是插件的锅是源端的瓶颈。4.4 安全策略拦截本地文件能播、线上站点不行现象用file://协议直接双击打开HTML页面所有功能正常部署到内网服务器上、用HTTP访问之后插件区域空白控制台报“插件被策略禁用”。 原因浏览器对NPAPI/ActiveX插件的信任策略区分本地文件和远程站点。Firefox的老版本允许本地文件加载插件但对于HTTP站点需要把域名加入允许列表Chrome的NPAPI时代也有类似的站点白名单机制。 解决最省事的内网做法是站点也用HTTP协议并且确认方案部署在可信域名下必要的时候把站点加入插件的允许站点列表。这里要特别提醒HTTPS页面加载VLC插件几乎必死因为插件被现代浏览器视为非安全内容HTTPS的混合内容拦截会直接按掉。如果公司安全策略强制HTTPS那就别挣扎了直接看第5章的替代方案。5. 插件失效后的替代方案现代浏览器调用VLC的四种路径5.1 方案一VLC原生HTTP接口VLC自带一个HTTP控制接口启动时打开Web接口后浏览器里通过普通HTTP请求就能控制播放、暂停、切歌和调音量。这个方案不属于插件调用但它解决了“网页要控制VLC”的真实需求而且不需要任何插件的兼容性支持主流的Chrome、Edge都能直接用。先启动带Web接口的VLC命令在Windows和Linux通用只是可执行文件路径不同。# 启动VLC并开启HTTP接口端口8080密码yourpass C:\Program Files\VideoLAN\VLC\vlc.exe --extraintfhttp --http-port8080 --http-passwordyourpass # Linux下命令相同换成vlc可执行文件即可 vlc --extraintfhttp --http-port8080 --http-passwordyourpass接口起来后通过/requests/status.json这个端点控制播放器。验证接口是否存活直接用浏览器或curl访问这个地址能看到一长串JSON状态数据。# 查看当前状态Basic Auth的用户名为空密码填启动时的--http-password curl -u :yourpass http://127.0.0.1:8080/requests/status.json控制播放的命令通过command参数传常用的就这么几个pl_play播放、pl_pause暂停、pl_stop停止、pl_next下一个、volumeval60调音量。浏览器页面里用fetch就能发不需要任何插件。// 页面里控制VLC HTTP接口注意Basic Auth的写法 function vlcControl(action, value) { var url http://127.0.0.1:8080/requests/status.json?command action; if (value ! undefined) { url val value; } return fetch(url, { headers: { Authorization: Basic btoa(:yourpass) } }); } // 触发播放和暂停 vlcControl(pl_play); vlcControl(pl_pause); // 把音量调到60 vlcControl(volume, 60);这套方案的问题是没有画面内嵌VLC会以独立窗口弹出。对监控墙这类需要页面内嵌画面的场景不够用但对接后端自动化流程、做定时播放任务、或者做一个简单的“点一下让VLC开始播”的遥控页面够用了。5.2 方案二WebSocket桥接本地服务VLC HTTP接口是纯请求响应模式页面想实时感知播放进度得不停地轮询status接口体验很差。更顺手的做法是在本地跑一个小桥接服务用WebSocket把浏览器的命令转发给VLC同时把VLC的状态推送回页面。这个模式相当于自己给VLC做了一层实时控制协议封装。下面是一段简化版的Python桥接用websockets库提供服务同时用http.server在当前目录提供页面静态服务。浏览器访问本地8000端口页面WebSocket连接8765端口就能控制VLC。import asyncio import threading import requests import websockets from http.server import HTTPServer, SimpleHTTPRequestHandler VLC_API http://127.0.0.1:8080/requests/status.json VLC_AUTH (, yourpass) async def ws_handler(ws): async for message in ws: # 约定消息格式控制命令如 pl_play / pl_pause / volume:60 if : in message: action, value message.split(:, 1) params {command: action, val: value} else: params {command: message} # 转发给VLC同步等待结果把执行结果回给浏览器 requests.get(VLC_API, paramsparams, authVLC_AUTH, timeout3) await ws.send(ack: message) def start_ws_server(): # WebSocket服务跑在8765端口 loop asyncio.new_event_loop() asyncio.set_event_loop(loop) loop.run_until_complete( websockets.serve(ws_handler, 127.0.0.1, 8765) ) loop.run_forever() # 静态页面服务跑在8000端口控制页面和桥接同源避开跨域 threading.Thread(targetstart_ws_server, daemonTrue).start() HTTPServer((127.0.0.1, 8000), SimpleHTTPRequestHandler).serve_forever()这段桥接代码的逻辑很直白浏览器WebSocket发来pl_play桥接服务把它转成HTTP GET请求发给VLC再把确认消息推回页面。用这个桥接页面上可以做实时的播放状态显示也可以丢给多个页面共享控制权。注意一个前置条件浏览器页面和WebSocket服务必须同源或者服务端显式放开跨域否则浏览器会在握手阶段直接拦掉。我用这个方案时习惯把页面静态服务也跑在桥接进程里从根上避开CORS和Mixed Content问题。5.3 方案三自定义URL Scheme唤起VLC客户端如果需求只是“点击网页里的链接让本机VLC播放指定视频”不需要页面内嵌和控制的话第三个方案最轻量自定义URL Scheme。装好VLC之后系统本身就会注册rtsp://、mms://这类协议点击网页里的RTSP链接可以直接唤起VLC要做得更灵活就自己注册一个vlc://协议解析后拼完整地址。Windows下注册自定义协议需要写注册表下面是一份可用的注册表脚本。Windows Registry Editor Version 5.00 [HKEY_CLASSES_ROOT\vlc] URL:VLC Protocol URL Protocol [HKEY_CLASSES_ROOT\vlc\shell\open\command] \C:\\Program Files\\VideoLAN\\VLC\\vlc.exe\ \%1\执行这份脚本后网页里放一个vlc://rtsp://192.168.1.88/stream1这样的链接点击时VLC就会被唤起。注意VLC收到的参数是完整的vlc://rtsp://...字符串如果VLC不能识别前缀需要在协议处理端做一次剥壳或者用一段极小的批处理中转。实际项目中我不太推荐用自定义协议去拼复杂的参数因为协议字符串的转义规则在不同浏览器里并不一致中文路径和空格都可能出问题做做演示还行做正式产品不建议。5.4 方案对比什么时候该坚持插件什么时候该换四个方案放一起比较选型的逻辑就清楚了。方案页面内嵌画面需要客户端安装VLC需要本地服务控制延迟适用场景VLC插件支持是带插件组件否毫秒级老内网系统、专用播放终端HTTP接口不支持是否VLC自带毫秒级后台自动化控制、遥控页面WebSocket桥接不支持是是桥接脚本毫秒级需要状态回传的多页面控制系统URL Scheme不支持是否秒级进程启动点击链接让VLC单独播放怎么选我给一个判断原则要求画面嵌在页面里且浏览器是旧版内网环境继续用插件其余情况别恋战只要求能播、无所谓窗口形态优先用HTTP接口改动最小要做成有状态反馈的控制台就上WebSocket桥接权责清晰要是只为了“点个链接打开视频”那URL Scheme是最便宜的路径。6. 验证调用是否打通控制台、VLC日志与事件回调6.1 控制台三步验证插件对象、属性和运行状态接手别人的VLC插件页面时我习惯先把开发者工具打开做三步验证三十秒内分清问题出在前端还是后端。第一步在控制台输入document.getElementById(vlcPlayer)看返回对象是否存在且包含playlist属性这一步验证插件有没有被浏览器实例化。第二步直接读vlcPlayer.input.length如果能读到大于0的数字说明媒体已经加载成功问题大概率在播放状态如果返回空说明媒体源没加载出来去查mrl地址和网络。第三步看控制台有没有MIME类型警告或者插件内抛出的异常这类报错会直接指明是哪条链断了。这三步下来基本能定位九成的运行时故障。我遇到过接手现场直接说“插件坏了”的情况结果一查是页面用了vlc.play()这种民间流传的写法实际API是vlc.playlist.play()脚本方法名不存在控制台报错白屏前端自己却没看控制台。先看控制台永远比乱调参数快。6.2 用VLC verbose日志与事件回调精确定位问题控制台查完还定位不了就把VLC的日志开起来。启动VLC时加--verbose2所有模块的解码、渲染、网络请求信息都会打到stderr。浏览器页面加载插件时这个日志输出会跟在浏览器日志里或者你单独手动用命令行启动VLC播同一路源对比日志差异。日志里如果出现buffer deadlock prevented说明网络或磁盘IO跟不上出现d3d11 error说明显卡渲染链路有问题这类关键词比看黑屏猜原因靠谱得多。日志之外利用插件自身的事件回调可以精确感知播放器走到哪一步了。监听MediaPlayerPlaying和MediaPlayerEncounteredError就能区分“正常开始播放”和“加载媒体失败”。var vlc document.getElementById(vlcPlayer); // 前提是object标签里eventstrue已经打开 vlc.addEventListener(MediaPlayerPlaying, function() { console.log(播放已开始总时长(秒):, vlc.input.length); }); vlc.addEventListener(MediaPlayerEncounteredError, function() { console.error(媒体加载失败检查mrl地址、网络连通性和格式支持); });做页面时养成挂事件回调的习惯比靠人眼盯画面靠谱。我踩过最大的一个坑就是监控墙页面看似在播实际一直卡在首帧人肉眼根本看不出来后来就是靠MediaPlayerTimeChanged事件发现时间戳长时间不动才定位到缓冲问题。这类事件回调分别在老VLC插件的不同小版本里名称略有出入但Playing、Paused、EncounteredError这几个是稳定存在的放心用。踩多了之后我现在的习惯是任何要上线给业务方用的页面至少把MediaPlayerEncounteredError处理掉让错误浮在界面上而不是留一个黑匣子。希望帮到你。本文还有配套的精品资源点击获取
返回列表