如何在现代浏览器中实现无插件的FLV播放?flv.js完整实战指南

发布时间:2026/5/28 0:58:28

如何在现代浏览器中实现无插件的FLV播放?flv.js完整实战指南 如何在现代浏览器中实现无插件的FLV播放flv.js完整实战指南【免费下载链接】flv.jsHTML5 FLV Player项目地址: https://gitcode.com/gh_mirrors/fl/flv.js在视频直播和点播领域FLV格式曾经是Flash时代的王者但随着HTML5的兴起和Flash的淘汰如何在现代浏览器中播放FLV格式成为了开发者面临的重要挑战。flv.js作为一款纯JavaScript实现的HTML5 FLV播放器通过创新的技术方案让FLV格式在浏览器中重生为开发者提供了完美的解决方案。技术架构解析flv.js如何实现浏览器内FLV播放flv.js的核心工作原理是通过Media Source Extensions技术将FLV格式实时转换为浏览器支持的MP4分段。这一过程看似简单实则包含了复杂的转码流水线设计。整个系统采用分层架构将网络加载、格式解析、转码处理和媒体渲染等职责分离确保了系统的高效运行。flv.js技术架构展示了从数据加载到浏览器渲染的完整流程从上图可以看出flv.js的架构设计具有以下特点模块化设计每个模块职责单一便于维护和扩展异步处理核心转码逻辑运行在Web Worker中避免阻塞主线程浏览器兼容支持多种加载策略适配不同浏览器环境实时转码FLV到MP4的转换在内存中实时完成无需文件存储核心模块功能说明模块名称主要职责关键技术FlvPlayer用户接口层处理播放控制事件管理、状态控制MSEController媒体源控制器Media Source Extensions APITransmuxer格式转码器FLV到MP4实时转换IO Loaders网络加载器HTTP/WebSocket协议支持FlvDemuxerFLV解封装器FLV格式解析MP4RemuxerMP4封装器ISO BMFF格式封装快速上手5分钟构建你的第一个FLV播放器环境准备与安装首先你需要将flv.js集成到你的项目中。可以通过以下两种方式之一通过npm安装npm install flv.js通过CDN直接引入script srchttps://cdn.jsdelivr.net/npm/flv.js/dist/flv.min.js/script基础播放实现创建一个基础的FLV播放器只需要几行代码。以下是一个完整的示例!DOCTYPE html html head titleFLV播放器示例/title /head body video idvideoElement controls width800 height450/video script // 1. 检测浏览器兼容性 if (flvjs.isSupported()) { const videoElement document.getElementById(videoElement); // 2. 创建播放器配置 const playerConfig { type: flv, url: http://example.com/video.flv, isLive: false, // 点播视频 hasAudio: true, hasVideo: true, cors: true }; // 3. 创建播放器实例 const flvPlayer flvjs.createPlayer(playerConfig, { enableWorker: true, enableStashBuffer: true, stashInitialSize: 128 }); // 4. 关联视频元素并播放 flvPlayer.attachMediaElement(videoElement); flvPlayer.load(); flvPlayer.play(); // 5. 错误处理 flvPlayer.on(flvjs.Events.ERROR, (errorType, errorDetail) { console.error(播放错误:, errorType, errorDetail); }); } else { console.error(当前浏览器不支持flv.js); } /script /body /html高级配置优化播放性能与用户体验直播流配置优化对于直播场景延迟控制是关键。flv.js提供了丰富的配置选项来优化直播体验const livePlayerConfig { type: flv, url: ws://live.example.com/live.flv, // WebSocket协议延迟更低 isLive: true, cors: true, withCredentials: false }; const livePlayer flvjs.createPlayer(livePlayerConfig, { enableWorker: true, enableStashBuffer: false, // 直播流禁用缓冲池以降低延迟 stashInitialSize: 128, // 初始缓冲区大小(KB) lazyLoad: true, // 启用延迟加载 lazyLoadMaxDuration: 3, // 最大延迟加载时长(秒) accurateSeek: false, // 直播流不需要精确跳转 reuseRedirectedURL: true, // 重用重定向URL headers: { // 自定义请求头 User-Agent: Custom-Player/1.0 } });点播视频配置对于点播视频重点是缓冲管理和播放体验const vodPlayerConfig { type: flv, url: http://example.com/movie.flv, isLive: false, duration: 7200000, // 视频时长(毫秒) filesize: 104857600 // 文件大小(字节) }; const vodPlayer flvjs.createPlayer(vodPlayerConfig, { enableWorker: true, enableStashBuffer: true, // 点播流启用缓冲池 stashInitialSize: 512, // 更大的初始缓冲区 lazyLoad: true, lazyLoadMaxDuration: 30, // 更长的延迟加载时长 accurateSeek: true, // 启用精确跳转 seekType: range, // 使用Range请求跳转 autoCleanupSourceBuffer: true, // 自动清理源缓冲区 autoCleanupMaxBackwardDuration: 180, autoCleanupMinBackwardDuration: 120 });错误处理与监控构建稳定的播放系统错误分类与处理策略flv.js将播放错误分为三大类针对不同类型的错误需要采取不同的恢复策略const player flvjs.createPlayer(config); // 错误事件监听 player.on(flvjs.Events.ERROR, (errorType, errorDetail, errorInfo) { console.group(播放错误详情); console.log(错误类型:, errorType); console.log(错误详情:, errorDetail); console.log(错误信息:, errorInfo); console.groupEnd(); // 根据错误类型采取不同策略 switch(errorType) { case flvjs.ErrorTypes.NETWORK_ERROR: handleNetworkError(errorDetail); break; case flvjs.ErrorTypes.MEDIA_ERROR: handleMediaError(errorDetail); break; case flvjs.ErrorTypes.OTHER_ERROR: handleOtherError(errorDetail); break; } }); // 网络错误处理函数 function handleNetworkError(detail) { switch(detail) { case flvjs.ErrorDetails.NETWORK_TIMEOUT: console.warn(网络超时尝试重连...); scheduleRetry(); break; case flvjs.ErrorDetails.NETWORK_ERROR: console.error(网络连接错误检查网络状态); break; case flvjs.ErrorDetails.HTTP_STATUS_CODE_INVALID: console.error(HTTP状态码异常检查服务器响应); break; } }自动重试机制对于网络不稳定的场景实现智能重试机制至关重要let retryCount 0; const MAX_RETRIES 3; const RETRY_DELAYS [1000, 3000, 5000]; // 指数退避延迟 function setupAutoRetry(player) { player.on(flvjs.Events.ERROR, (errorType) { if (errorType flvjs.ErrorTypes.NETWORK_ERROR retryCount MAX_RETRIES) { const delay RETRY_DELAYS[retryCount] || 5000; console.log(第${retryCount 1}次重试${delay}ms后执行...); setTimeout(() { retryCount; player.unload(); player.load(); player.play(); }, delay); } else if (retryCount MAX_RETRIES) { console.error(已达到最大重试次数播放失败); showErrorToUser(网络连接失败请检查网络后重试); } }); // 播放成功时重置重试计数 player.on(flvjs.Events.LOADING_COMPLETE, () { retryCount 0; }); }性能监控与统计实时掌握播放质量播放统计信息收集flv.js提供了丰富的统计信息帮助开发者监控播放质量// 统计信息监听 player.on(flvjs.Events.STATISTICS_INFO, (stats) { const metrics { 播放速度: ${(stats.speed / 1024).toFixed(2)} KB/s, 缓冲时长: ${stats.bufferLength.toFixed(2)}s, 解码帧率: ${stats.decodedFrames} fps, 丢帧数: stats.droppedFrames, 网络延迟: ${stats.networkLatency}ms, 已加载字节: ${(stats.loadedBytes / 1048576).toFixed(2)} MB, 播放进度: ${((stats.currentTime / stats.duration) * 100).toFixed(1)}% }; // 实时更新监控面板 updateMetricsDashboard(metrics); // 根据网络状况动态调整配置 if (stats.networkLatency 1000) { console.warn(网络延迟较高建议优化网络连接); adjustBufferSize(increase); } if (stats.droppedFrames 10) { console.warn(丢帧严重可能硬件解码能力不足); adjustVideoQuality(lower); } });性能监控仪表板示例以下是一个简单的性能监控实现class PerformanceMonitor { constructor(player) { this.player player; this.metrics { networkSpeed: [], bufferLevel: [], droppedFrames: 0 }; this.setupMonitoring(); } setupMonitoring() { this.player.on(flvjs.Events.STATISTICS_INFO, (stats) { this.recordMetrics(stats); this.analyzeTrends(); this.alertIfNeeded(); }); } recordMetrics(stats) { // 记录最近10个数据点 this.metrics.networkSpeed.push(stats.speed); this.metrics.bufferLevel.push(stats.bufferLength); if (this.metrics.networkSpeed.length 10) { this.metrics.networkSpeed.shift(); this.metrics.bufferLevel.shift(); } this.metrics.droppedFrames stats.droppedFrames; } analyzeTrends() { const avgSpeed this.calculateAverage(this.metrics.networkSpeed); const avgBuffer this.calculateAverage(this.metrics.bufferLevel); console.log(平均网速: ${(avgSpeed / 1024).toFixed(2)} KB/s); console.log(平均缓冲: ${avgBuffer.toFixed(2)}s); return { avgSpeed, avgBuffer }; } calculateAverage(array) { return array.reduce((a, b) a b, 0) / array.length; } alertIfNeeded() { const { avgSpeed, avgBuffer } this.analyzeTrends(); if (avgSpeed 50 * 1024) { // 小于50KB/s console.warn(网络速度较慢可能影响播放体验); } if (avgBuffer 2) { // 缓冲小于2秒 console.warn(缓冲区不足可能造成卡顿); } if (this.metrics.droppedFrames 5) { console.warn(丢帧较多建议降低视频质量); } } }高级功能实战分片播放与自定义加载器分片播放实现对于大型视频文件flv.js支持分片播放模式这在大文件播放和CDN优化中特别有用const segmentedConfig { type: flv, isLive: false, segments: [ { duration: 60000, // 第一段60秒 filesize: 5242880, // 5MB url: https://cdn.example.com/video/part1.flv }, { duration: 60000, // 第二段60秒 filesize: 5242880, // 5MB url: https://cdn.example.com/video/part2.flv }, { duration: 60000, // 第三段60秒 filesize: 5242880, // 5MB url: https://cdn.example.com/video/part3.flv } // 可以继续添加更多分片 ] }; const segmentedPlayer flvjs.createPlayer(segmentedConfig, { enableWorker: true, enableStashBuffer: true, lazyLoad: true, lazyLoadMaxDuration: 10 }); // 分片加载事件监听 segmentedPlayer.on(flvjs.Events.SEGMENT_LOADED, (segmentInfo) { console.log(分片加载完成: ${segmentInfo.url}); console.log(分片时长: ${segmentInfo.duration}ms); console.log(分片大小: ${segmentInfo.filesize} bytes); }); segmentedPlayer.on(flvjs.Events.SEGMENT_LOAD_FAILED, (errorInfo) { console.error(分片加载失败:, errorInfo); // 可以实现分片重试或切换到备用分片 });自定义加载器开发flv.js支持自定义加载器这在需要特殊网络处理或协议支持的场景中非常有用// 自定义加载器示例 class CustomStreamLoader extends flvjs.BaseLoader { constructor() { super(custom-stream-loader); this._needStash true; } open(dataSource) { console.log(自定义加载器启动数据源:, dataSource); // 初始化加载状态 this._status flvjs.LoaderStatus.kConnecting; this._onOpen(); // 模拟数据加载 this._loadData(dataSource); return this; } abort() { console.log(自定义加载器中止); this._status flvjs.LoaderStatus.kComplete; this._onComplete(); } destroy() { console.log(自定义加载器销毁); super.destroy(); } _loadData(dataSource) { // 这里实现实际的数据加载逻辑 // 例如WebSocket连接、自定义协议处理等 setTimeout(() { if (this._status flvjs.LoaderStatus.kConnecting) { this._status flvjs.LoaderStatus.kBuffering; this._onDataArrival(new Uint8Array([0, 1, 2, 3]), 0, 4); } }, 100); } } // 注册自定义加载器 flvjs.LoggingControl.registerLoader(custom, CustomStreamLoader); // 使用自定义加载器 const customPlayer flvjs.createPlayer({ type: flv, url: custom://example.com/stream, loaderType: custom // 指定使用自定义加载器 });调试与优化提升开发效率与播放性能日志控制与调试flv.js提供了灵活的日志控制机制帮助开发者在不同环境下进行调试// 开发环境配置显示所有日志 flvjs.LoggingControl.enableAll true; flvjs.LoggingControl.enableDebug true; flvjs.LoggingControl.enableVerbose true; flvjs.LoggingControl.enableInfo true; flvjs.LoggingControl.enableWarn true; flvjs.LoggingControl.enableError true; // 生产环境配置只显示错误和警告 flvjs.LoggingControl.enableAll false; flvjs.LoggingControl.enableDebug false; flvjs.LoggingControl.enableVerbose false; flvjs.LoggingControl.enableInfo false; flvjs.LoggingControl.enableWarn true; flvjs.LoggingControl.enableError true; // 自定义日志输出 flvjs.LoggingControl.addLogListener((level, message) { const timestamp new Date().toISOString(); const levelMap { 0: DEBUG, 1: VERBOSE, 2: INFO, 3: WARN, 4: ERROR }; console.log([${timestamp}] [${levelMap[level]}] ${message}); // 可以发送到远程日志系统 if (level 3) { // WARN和ERROR级别 sendToRemoteLogging({ level: levelMap[level], message: message, timestamp: timestamp, userAgent: navigator.userAgent }); } });性能优化最佳实践根据实际使用经验以下优化策略可以显著提升flv.js的播放性能1. 缓冲区优化策略function optimizeBufferConfig(videoBitrate, networkSpeed) { // 根据视频码率和网络速度动态调整缓冲区 const bitrateKBps videoBitrate / 1024; const networkKBps networkSpeed / 1024; let stashInitialSize 384; // 默认384KB if (networkKBps bitrateKBps * 2) { // 网络较差增大缓冲区 stashInitialSize 768; } else if (networkKBps bitrateKBps * 5) { // 网络很好减小缓冲区以降低延迟 stashInitialSize 128; } return { enableStashBuffer: true, stashInitialSize: stashInitialSize, lazyLoad: true, lazyLoadMaxDuration: Math.max(3, Math.ceil(stashInitialSize / bitrateKBps)) }; }2. 内存管理优化class PlayerManager { constructor() { this.players new Map(); this.memoryMonitor null; } createPlayer(id, config) { // 检查是否已存在相同ID的播放器 if (this.players.has(id)) { this.destroyPlayer(id); } const player flvjs.createPlayer(config); this.players.set(id, player); // 设置内存监控 this.setupMemoryMonitoring(player, id); return player; } destroyPlayer(id) { const player this.players.get(id); if (player) { player.unload(); player.detachMediaElement(); player.destroy(); this.players.delete(id); console.log(播放器 ${id} 已销毁内存已释放); } } setupMemoryMonitoring(player, id) { // 监控播放器内存使用 player.on(flvjs.Events.STATISTICS_INFO, (stats) { const memoryUsage performance.memory ? (performance.memory.usedJSHeapSize / 1048576).toFixed(2) MB : N/A; if (performance.memory performance.memory.usedJSHeapSize 500 * 1024 * 1024) { console.warn(播放器 ${id} 内存使用过高: ${memoryUsage}); } }); } cleanupAll() { for (const [id, player] of this.players) { this.destroyPlayer(id); } this.players.clear(); } }故障排除与常见问题常见问题解决方案问题现象可能原因解决方案播放器无法加载浏览器不支持MSE使用flvjs.isSupported()检测兼容性视频有声音无画面视频编码不支持确保视频使用H.264编码直播延迟过高缓冲区设置不当设置enableStashBuffer: false跨域请求失败CORS配置问题服务器配置正确CORS头或设置cors: true内存占用过高播放器未正确销毁播放结束后调用player.unload()和player.destroy()跳转不准确关键帧间隔过大设置accurateSeek: true音频视频不同步时间戳问题设置fixAudioTimestampGap: true浏览器兼容性检查function checkBrowserCompatibility() { const compatibility { supported: flvjs.isSupported(), features: flvjs.getFeatureList(), browser: navigator.userAgent, mimeTypes: {} }; // 检查关键MIME类型支持 const mimeTypes [ video/mp4; codecsavc1.42E01E, video/mp4; codecsavc1.42E01E, mp4a.40.2, video/webm; codecsvp8, vorbis ]; mimeTypes.forEach(mimeType { compatibility.mimeTypes[mimeType] MediaSource.isTypeSupported(mimeType); }); console.table({ flv.js支持: compatibility.supported, H.264支持: compatibility.mimeTypes[video/mp4; codecsavc1.42E01E], H.264AAC支持: compatibility.mimeTypes[video/mp4; codecsavc1.42E01E, mp4a.40.2], 浏览器: compatibility.browser.match(/(Chrome|Firefox|Safari|Edge|IE)[\/\s][\d.]/)?.[0] || 未知 }); return compatibility; } // 页面加载时检查兼容性 window.addEventListener(load, () { const compat checkBrowserCompatibility(); if (!compat.supported) { showFallbackMessage(当前浏览器不支持flv.js请使用Chrome、Firefox、Safari 10、IE11或Edge浏览器); } });项目集成与实践建议与现代前端框架集成在React中使用flv.jsimport React, { useEffect, useRef } from react; import flvjs from flv.js; const FlvPlayerComponent ({ url, isLive false }) { const videoRef useRef(null); const playerRef useRef(null); useEffect(() { if (flvjs.isSupported() videoRef.current) { const player flvjs.createPlayer({ type: flv, url, isLive, cors: true }, { enableWorker: true, enableStashBuffer: !isLive }); player.attachMediaElement(videoRef.current); player.load(); playerRef.current player; return () { if (playerRef.current) { playerRef.current.unload(); playerRef.current.destroy(); playerRef.current null; } }; } }, [url, isLive]); const handlePlay () { if (playerRef.current) { playerRef.current.play(); } }; const handlePause () { if (playerRef.current) { playerRef.current.pause(); } }; return ( div classNameflv-player video ref{videoRef} controls width100% style{{ maxWidth: 800px }} / div classNamecontrols button onClick{handlePlay}播放/button button onClick{handlePause}暂停/button /div /div ); }; export default FlvPlayerComponent;在Vue中使用flv.jstemplate div classflv-player video refvideoElement controls :widthwidth :heightheight / div classplayer-status span v-ifloading加载中.../span span v-else-ifplaying播放中/span span v-else已暂停/span /div /div /template script import flvjs from flv.js; export default { name: FlvPlayer, props: { url: { type: String, required: true }, isLive: { type: Boolean, default: false }, width: { type: String, default: 800 }, height: { type: String, default: 450 } }, data() { return { player: null, loading: false, playing: false }; }, mounted() { this.initPlayer(); }, beforeUnmount() { this.destroyPlayer(); }, methods: { initPlayer() { if (!flvjs.isSupported()) { console.error(浏览器不支持flv.js); return; } this.player flvjs.createPlayer({ type: flv, url: this.url, isLive: this.isLive }, { enableWorker: true, enableStashBuffer: !this.isLive }); this.player.attachMediaElement(this.$refs.videoElement); this.setupEventListeners(); this.player.load(); }, setupEventListeners() { this.player.on(flvjs.Events.LOADING_COMPLETE, () { this.loading false; this.playing true; this.player.play(); }); this.player.on(flvjs.Events.ERROR, (errorType, errorDetail) { console.error(播放错误:, errorType, errorDetail); this.$emit(error, { errorType, errorDetail }); }); }, destroyPlayer() { if (this.player) { this.player.unload(); this.player.destroy(); this.player null; } } } }; /script性能监控与告警系统class PlayerMonitoringSystem { constructor(player, options {}) { this.player player; this.options { warningThresholds: { bufferLow: 2, // 缓冲低于2秒警告 highLatency: 1000, // 延迟高于1秒警告 lowSpeed: 100 * 1024, // 网速低于100KB/s警告 highDroppedFrames: 10 // 丢帧高于10警告 }, ...options }; this.metricsHistory []; this.setupMonitoring(); } setupMonitoring() { this.player.on(flvjs.Events.STATISTICS_INFO, (stats) { this.recordMetrics(stats); this.checkThresholds(stats); this.analyzePerformance(stats); }); } recordMetrics(stats) { const metric { timestamp: Date.now(), bufferLength: stats.bufferLength, networkLatency: stats.networkLatency, speed: stats.speed, droppedFrames: stats.droppedFrames, decodedFrames: stats.decodedFrames }; this.metricsHistory.push(metric); // 保留最近100个数据点 if (this.metricsHistory.length 100) { this.metricsHistory.shift(); } } checkThresholds(stats) { const warnings []; if (stats.bufferLength this.options.warningThresholds.bufferLow) { warnings.push(缓冲区不足: ${stats.bufferLength.toFixed(2)}s); } if (stats.networkLatency this.options.warningThresholds.highLatency) { warnings.push(网络延迟过高: ${stats.networkLatency}ms); } if (stats.speed this.options.warningThresholds.lowSpeed) { warnings.push(网络速度较慢: ${(stats.speed / 1024).toFixed(2)}KB/s); } if (stats.droppedFrames this.options.warningThresholds.highDroppedFrames) { warnings.push(丢帧严重: ${stats.droppedFrames}帧); } if (warnings.length 0) { this.emitWarning(warnings); } } analyzePerformance(stats) { // 计算平均性能指标 const recentMetrics this.metricsHistory.slice(-20); if (recentMetrics.length 5) return; const avgBuffer recentMetrics.reduce((sum, m) sum m.bufferLength, 0) / recentMetrics.length; const avgLatency recentMetrics.reduce((sum, m) sum m.networkLatency, 0) / recentMetrics.length; const avgSpeed recentMetrics.reduce((sum, m) sum m.speed, 0) / recentMetrics.length; // 性能评分0-100分 let score 100; if (avgBuffer 3) score - 20; if (avgBuffer 1) score - 30; if (avgLatency 500) score - 15; if (avgLatency 1000) score - 25; if (avgSpeed 200 * 1024) score - 10; if (avgSpeed 50 * 1024) score - 20; this.performanceScore Math.max(0, score); if (this.performanceScore 60) { console.warn(播放性能较差评分: ${this.performanceScore.toFixed(1)}); this.suggestImprovements(avgBuffer, avgLatency, avgSpeed); } } suggestImprovements(buffer, latency, speed) { const suggestions []; if (buffer 2) { suggestions.push(建议增大缓冲区大小 (stashInitialSize)); } if (latency 500) { suggestions.push(建议检查网络连接或使用CDN加速); } if (speed 100 * 1024) { suggestions.push(建议降低视频码率或使用更小的分辨率); } if (suggestions.length 0) { console.log(优化建议:, suggestions.join(; )); } } emitWarning(warnings) { console.warn(播放器警告:, warnings.join(, )); // 可以发送到监控系统 if (typeof this.options.onWarning function) { this.options.onWarning(warnings); } } getPerformanceReport() { if (this.metricsHistory.length 0) { return null; } const latest this.metricsHistory[this.metricsHistory.length - 1]; const avgBuffer this.metricsHistory.reduce((sum, m) sum m.bufferLength, 0) / this.metricsHistory.length; const avgLatency this.metricsHistory.reduce((sum, m) sum m.networkLatency, 0) / this.metricsHistory.length; return { timestamp: new Date().toISOString(), currentMetrics: latest, averageBuffer: avgBuffer, averageLatency: avgLatency, performanceScore: this.performanceScore || 0, totalMetrics: this.metricsHistory.length }; } }总结与展望flv.js作为一款优秀的HTML5 FLV播放器通过创新的技术方案解决了浏览器原生不支持FLV格式的难题。通过本文的完整指南你应该已经掌握了基础使用快速集成flv.js到你的项目中高级配置针对不同场景优化播放参数错误处理构建稳定的播放系统性能监控实时掌握播放质量并优化高级功能分片播放和自定义加载器的实现框架集成与现代前端框架的无缝集成最佳实践总结环境检测先行始终使用flvjs.isSupported()检测浏览器兼容性配置针对场景直播流禁用缓冲池降低延迟点播流启用缓冲池提升体验资源管理规范播放结束后及时调用unload()和destroy()释放资源监控全面覆盖实现完整的性能监控和错误告警系统渐进式优化根据实际网络和设备性能动态调整播放参数技术发展趋势虽然flv.js项目维护频率有所降低但其核心技术理念已被后续项目继承和发展。对于新项目建议关注mpegts.jsflv.js的继承者支持更多格式和特性WebCodecs API浏览器原生编解码接口性能更优WebRTC实时通信协议适用于超低延迟场景无论你是维护现有项目还是开发新的视频应用掌握flv.js都将为你提供坚实的技术基础。通过合理的配置和优化flv.js能够为你的用户提供流畅、稳定的FLV播放体验。【免费下载链接】flv.jsHTML5 FLV Player项目地址: https://gitcode.com/gh_mirrors/fl/flv.js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻