h5-video-embed
Version:
一个用于嵌入和播放各种视频平台内容的 React 组件库,支持 B站、YouTube、抖音等多个平台的纯前端解析
213 lines (205 loc) • 40.5 kB
JavaScript
(function(b,l){typeof exports=="object"&&typeof module<"u"?l(exports,require("react/jsx-runtime"),require("react")):typeof define=="function"&&define.amd?define(["exports","react/jsx-runtime","react"],l):(b=typeof globalThis<"u"?globalThis:b||self,l(b.H5VideoEmbed={},b.React,b.React))})(this,function(b,l,v){"use strict";const M=a=>{if(!a||typeof a!="string")return null;const e=/(?:https?:\/\/)?(?:www\.)?(?:youtube\.com\/(?:[^\/\n\s]+\/\S+\/|(?:v|e(?:mbed)?)\/|\S*?[?&]v=)|youtu\.be\/)([a-zA-Z0-9_-]{11})/,t=a.match(e);if(t)return t[1];const i=[/(?:https?:\/\/)?(?:www\.)?bilibili\.com\/video\/(BV[a-zA-Z0-9]+|av\d+)/,/(?:https?:\/\/)?(?:www\.)?bilibili\.com\/bangumi\/play\/(ep\d+|ss\d+)/,/(?:https?:\/\/)?live\.bilibili\.com\/(\d+)/,/(?:https?:\/\/)?(?:www\.)?bilibili\.com\/read\/(cv\d+)/,/(?:https?:\/\/)?t\.bilibili\.com\/(\d+)/];for(const s of i){const d=a.match(s);if(d)return d[1]}const o=/(?:https?:\/\/)?(?:www\.)?vimeo\.com\/(\d+)/,r=a.match(o);return r?r[1]:null},D=a=>{if(!a||typeof a!="string")return!1;try{return new URL(a),!0}catch{return[/^https?:\/\/.+/,/^\/\/.+/,/^\/[^\/].*/,/^[a-zA-Z0-9-]+\.[a-zA-Z]{2,}/].some(t=>t.test(a))}},Z=a=>{if(!a||typeof a!="number")return"00:00";const e=Math.floor(a/3600),t=Math.floor(a%3600/60),i=Math.floor(a%60);return e>0?`${e.toString().padStart(2,"0")}:${t.toString().padStart(2,"0")}:${i.toString().padStart(2,"0")}`:`${t.toString().padStart(2,"0")}:${i.toString().padStart(2,"0")}`};class A{constructor(e={}){this.corsProxy=e.corsProxy,this.strictFrontendOnly=e.strictFrontendOnly||!1}async parse(e){const t=this.extractBvid(e);if(!t)throw new Error("无效的B站链接");console.log(`📺 正在解析B站内容: ${JSON.stringify(t)}`);try{switch(t.type){case"video":return await this.parseVideo(t,e);case"bangumi":return await this.parseBangumi(t,e);case"live":return await this.parseLive(t,e);case"medialist":return await this.parseMedialist(t,e);default:throw new Error(`不支持的B站内容类型: ${t.type}`)}}catch(i){return console.warn("直接API调用失败:",i.message),this.strictFrontendOnly?(console.log("⚡ 严格前端模式:返回基础解析信息"),this.createFallbackData(t,e)):this.corsProxy?(console.log("🔄 尝试通过代理解析"),await this.parseViaProxy(t,e)):(console.log("📦 无代理服务器,返回基础解析信息"),this.createFallbackData(t,e))}}async parseVideo(e,t){const i=await this.getVideoInfo(e.id);return this.formatVideoData(i,t,e)}async parseBangumi(e,t){const i=await this.getBangumiInfo(e.id);return this.formatBangumiData(i,t,e)}async parseLive(e,t){const i=await this.getLiveInfo(e.id);return this.formatLiveData(i,t,e)}async parseMedialist(e,t){const i=await this.getMedialistInfo(e.id);return this.formatMedialistData(i,t,e)}async getVideoInfo(e){const i=e.startsWith("av")?`https://api.bilibili.com/x/web-interface/view?aid=${e.slice(2)}`:`https://api.bilibili.com/x/web-interface/view?bvid=${e}`,o=await fetch(i,{method:"GET",headers:{"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",Referer:"https://www.bilibili.com/",Origin:"https://www.bilibili.com"}});if(!o.ok)throw new Error(`B站API请求失败: ${o.status}`);const r=await o.json();if(r.code!==0)throw new Error(`B站API错误: ${r.message}`);return r.data}async getBangumiInfo(e){const t=e.startsWith("ep"),i=e.slice(2),o=t?`https://api.bilibili.com/pgc/view/web/season?ep_id=${i}`:`https://api.bilibili.com/pgc/view/web/season?season_id=${i}`,r=await fetch(o,{method:"GET",headers:{"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",Referer:"https://www.bilibili.com/",Origin:"https://www.bilibili.com"}});if(!r.ok)throw new Error(`B站番剧API请求失败: ${r.status}`);const s=await r.json();if(s.code!==0)throw new Error(`B站番剧API错误: ${s.message}`);return s.result}async getLiveInfo(e){const t=`https://api.live.bilibili.com/room/v1/Room/get_info?room_id=${e}`,i=await fetch(t,{method:"GET",headers:{"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",Referer:"https://live.bilibili.com/",Origin:"https://live.bilibili.com"}});if(!i.ok)throw new Error(`B站直播API请求失败: ${i.status}`);const o=await i.json();if(o.code!==0)throw new Error(`B站直播API错误: ${o.message}`);return o.data}async getMedialistInfo(e){const i=`https://api.bilibili.com/x/v2/medialist/resource/list?media_id=${e.slice(2)}&pn=1&ps=20`,o=await fetch(i,{method:"GET",headers:{"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",Referer:"https://www.bilibili.com/",Origin:"https://www.bilibili.com"}});if(!o.ok)throw new Error(`B站合集API请求失败: ${o.status}`);const r=await o.json();if(r.code!==0)throw new Error(`B站合集API错误: ${r.message}`);return r.data}async getVideoInfoViaProxy(e){const t=`${this.corsProxy}/api/proxy/bilibili/video/${e}`,i=await fetch(t);if(!i.ok)throw new Error(`代理请求失败: ${i.status}`);const o=await i.json();if(!o.success)throw new Error(o.message||"代理解析失败");return o.data}async parseViaProxy(e,t){const i=`${this.corsProxy}/api/proxy/bilibili/parse`,o=await fetch(i,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({url:t,extractResult:e})});if(!o.ok)throw new Error(`代理解析请求失败: ${o.status}`);const r=await o.json();if(!r.success)throw new Error(r.message||"代理解析失败");return{success:!0,data:r.data,platform:"bilibili",source:"backend_proxy"}}formatVideoData(e,t,i){var r,s,d,n,c,f,p,w,_,x,h;const o={id:e.bvid||`av${e.aid}`,aid:e.aid,bvid:e.bvid,title:e.title,description:e.desc||"",duration:e.duration,thumbnail:e.pic,uploader:((r=e.owner)==null?void 0:r.name)||"未知用户",uploader_id:(s=e.owner)==null?void 0:s.mid,uploader_avatar:(d=e.owner)==null?void 0:d.face,upload_date:new Date(e.pubdate*1e3).toISOString().slice(0,10).replace(/-/g,""),view_count:((n=e.stat)==null?void 0:n.view)||0,like_count:((c=e.stat)==null?void 0:c.like)||0,comment_count:((f=e.stat)==null?void 0:f.reply)||0,coin_count:((p=e.stat)==null?void 0:p.coin)||0,favorite_count:((w=e.stat)==null?void 0:w.favorite)||0,share_count:((_=e.stat)==null?void 0:_.share)||0,danmaku_count:((x=e.stat)==null?void 0:x.danmaku)||0,webpage_url:t,platform:"bilibili",platform_name:"B站",extractor:"bilibili_frontend",content_type:"video",tags:e.tag||[],tid:e.tid,tname:e.tname,copyright:e.copyright===1?"原创":"转载",pages:((h=e.pages)==null?void 0:h.map(y=>({cid:y.cid,page:y.page,part:y.part,duration:y.duration,dimension:y.dimension})))||[],formats:[{format_id:"bilibili_web",url:t,ext:"mp4",quality:720,width:1280,height:720,fps:30,vcodec:"h264",acodec:"aac",note:"需要B站播放器"}],embed:{type:"iframe",url:`https://player.bilibili.com/player.html?bvid=${e.bvid}&autoplay=0`,width:1280,height:720}};if(i.page&&e.pages&&e.pages[i.page-1]){const y=e.pages[i.page-1];o.current_page=i.page,o.page_title=y.part,o.page_duration=y.duration,o.embed.url+=`&p=${i.page}`}return o}formatBangumiData(e,t,i){var s,d,n;const o=e.episodes||[],r=o.find(c=>c.ep_id==i.id.slice(2))||o[0];return{id:`ss${e.season_id}`,season_id:e.season_id,title:e.title,description:e.evaluate||"",thumbnail:e.cover,type_name:e.type_name,total_count:e.total,webpage_url:t,platform:"bilibili",platform_name:"B站",extractor:"bilibili_frontend",content_type:"bangumi",rating:((s=e.rating)==null?void 0:s.score)||0,tags:e.styles||[],actors:((d=e.actor)==null?void 0:d.info)||"",staff:((n=e.staff)==null?void 0:n.info)||"",current_episode:r?{ep_id:r.ep_id,title:r.title,long_title:r.long_title,duration:r.duration,thumbnail:r.cover}:null,episodes:o.map(c=>({ep_id:c.ep_id,title:c.title,long_title:c.long_title,duration:c.duration,thumbnail:c.cover})),embed:{type:"iframe",url:r?`https://player.bilibili.com/player.html?bvid=${r.bvid}&autoplay=0`:t,width:1280,height:720}}}formatLiveData(e,t,i){return{id:e.room_id,title:e.title,description:e.description||"",thumbnail:e.user_cover||e.keyframe,uploader:e.uname,uploader_id:e.uid,uploader_avatar:e.face,live_status:e.live_status,online:e.online||0,webpage_url:t,platform:"bilibili",platform_name:"B站",extractor:"bilibili_frontend",content_type:"live",area_name:e.area_name,parent_area_name:e.parent_area_name,live_time:e.live_time,embed:{type:"iframe",url:`https://live.bilibili.com/blanc/${e.room_id}`,width:1280,height:720}}}formatMedialistData(e,t,i){var s,d,n;const o=e.info||{},r=e.medias||[];return{id:i.id,title:o.title,description:o.intro||"",thumbnail:o.cover,uploader:(s=o.upper)==null?void 0:s.name,uploader_id:(d=o.upper)==null?void 0:d.mid,uploader_avatar:(n=o.upper)==null?void 0:n.face,media_count:o.media_count||0,webpage_url:t,platform:"bilibili",platform_name:"B站",extractor:"bilibili_frontend",content_type:"medialist",type:o.type,attr:o.attr,medias:r.map(c=>{var f,p;return{id:c.bvid,title:c.title,thumbnail:c.cover,uploader:(f=c.upper)==null?void 0:f.name,duration:c.duration,view_count:((p=c.cnt_info)==null?void 0:p.play)||0}}),embed:{type:"iframe",url:r[0]?`https://player.bilibili.com/player.html?bvid=${r[0].bvid}&autoplay=0`:t,width:1280,height:720}}}extractBvid(e){const t=[/(?:bilibili\.com\/video\/|b23\.tv\/)(BV[a-zA-Z0-9]+)/i,/(?:bilibili\.com\/video\/|b23\.tv\/)(av\d+)/i,/m\.bilibili\.com\/video\/((?:BV|av)[a-zA-Z0-9]+)/i,/player\.bilibili\.com\/player\.html.*?(?:bvid=|aid=)((?:BV|av)[a-zA-Z0-9]+)/i,/(?:bilibili\.com\/bangumi\/play\/)(ep\d+)/i,/(?:bilibili\.com\/bangumi\/play\/)(ss\d+)/i,/(?:live\.bilibili\.com\/)(h5\/)?(\d+)/i,/b23\.tv\/([a-zA-Z0-9]+)/i,/(?:bilibili\.com\/video\/)((?:BV|av)[a-zA-Z0-9]+)(?:\?p=(\d+))?/i,/(?:bilibili\.com\/video\/)((?:BV|av)[a-zA-Z0-9]+)(?:\?.*?t=(\d+))?/i,/(?:bilibili\.com\/medialist\/play\/)(ml\d+)/i,/(?:bilibili\.com\/favlist\?fid=)(\d+)/i];for(const i of t){const o=e.match(i);if(o){let r=o[1],s=o[2];if(r.startsWith("ep")||r.startsWith("ss"))return{type:"bangumi",id:r};if(r.startsWith("ml"))return{type:"medialist",id:r};if(/^\d+$/.test(r))return{type:"live",id:r};const d={type:"video",id:r};return s&&/^\d+$/.test(s)&&(d.page=parseInt(s)),d}}return null}static canParse(e){return[/(?:www\.)?bilibili\.com\/video\//i,/(?:www\.)?bilibili\.com\/bangumi\/play\//i,/(?:m\.)?bilibili\.com\/video\//i,/live\.bilibili\.com\//i,/player\.bilibili\.com\/player\.html/i,/(?:www\.)?bilibili\.com\/medialist\/play\//i,/(?:www\.)?bilibili\.com\/favlist/i,/b23\.tv\//i].some(i=>i.test(e))}createFallbackData(e,t){const i=e.id,o={id:i,title:this.extractTitleFromUrl(t)||`B站${e.type}内容`,description:"基础解析模式:部分信息可能不完整",thumbnail:"https://i0.hdslb.com/bfs/archive/default.jpg",webpage_url:t,platform:"bilibili",platform_name:"B站",extractor:"bilibili_frontend_fallback",content_type:e.type,is_fallback:!0,fallback_reason:"无法访问B站API,仅提供基础播放功能",supports_embed:!0};switch(e.type){case"video":o.embed={type:"iframe",url:`https://player.bilibili.com/player.html?bvid=${i}&autoplay=0`,width:1280,height:720},e.page&&(o.embed.url+=`&p=${e.page}`);break;case"bangumi":o.embed={type:"iframe",url:t.includes("player.bilibili.com")?t:`https://www.bilibili.com/bangumi/play/${i}`,width:1280,height:720};break;case"live":o.embed={type:"iframe",url:`https://live.bilibili.com/blanc/${i}`,width:1280,height:720},o.live_status=1;break;default:o.embed={type:"link",url:t,width:1280,height:720}}return o}extractTitleFromUrl(e){const i=new URL(e).searchParams,o=["title","t","name"];for(const r of o){const s=i.get(r);if(s)return decodeURIComponent(s)}return null}static getSupportedUrlExamples(){return["https://www.bilibili.com/video/BV1xx411c7mD","https://www.bilibili.com/video/av12345678","https://www.bilibili.com/video/BV1xx411c7mD?p=2","https://m.bilibili.com/video/BV1xx411c7mD","https://www.bilibili.com/bangumi/play/ep123456","https://www.bilibili.com/bangumi/play/ss12345","https://live.bilibili.com/12345","https://www.bilibili.com/medialist/play/ml123456","https://b23.tv/abcdefg","https://player.bilibili.com/player.html?bvid=BV1xx411c7mD"]}}class ${constructor(e={}){this.corsProxy=e.corsProxy}async parse(e){console.log(`🎵 正在解析抖音视频: ${e}`);try{const t=await this.resolveRealUrl(e);if(!this.extractVideoId(t||e))throw new Error("无法提取抖音视频ID");const o=await this.extractFromPage(t||e);return this.formatVideoData(o,e)}catch(t){return console.warn("抖音解析失败:",t.message),this.createFallbackData(e)}}async resolveRealUrl(e){if(e.includes("v.douyin.com")||e.includes("dy.com"))try{if(this.corsProxy){const t=await fetch(`${this.corsProxy}/api/resolve-url`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({url:e})});if(t.ok)return(await t.json()).resolvedUrl}}catch(t){console.warn("短链接解析失败:",t.message)}return e}async extractFromPage(e){try{if(this.corsProxy){const t=await fetch(`${this.corsProxy}/api/proxy/douyin/page`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({url:e})});if(t.ok)return(await t.json()).data}throw new Error("需要代理服务器解析抖音页面")}catch{throw new Error("抖音页面解析失败,建议使用后端解析")}}formatVideoData(e,t){var i,o,r,s,d,n,c,f,p;return{id:e.aweme_id||"unknown",title:e.desc||"抖音视频",description:e.desc||"",duration:e.duration?e.duration/1e3:0,thumbnail:e.cover||e.dynamic_cover,uploader:((i=e.author)==null?void 0:i.nickname)||"抖音用户",uploader_id:(o=e.author)==null?void 0:o.unique_id,uploader_avatar:(r=e.author)==null?void 0:r.avatar_thumb,upload_date:e.create_time?new Date(e.create_time*1e3).toISOString().slice(0,10).replace(/-/g,""):new Date().toISOString().slice(0,10).replace(/-/g,""),view_count:((s=e.statistics)==null?void 0:s.play_count)||0,like_count:((d=e.statistics)==null?void 0:d.digg_count)||0,comment_count:((n=e.statistics)==null?void 0:n.comment_count)||0,share_count:((c=e.statistics)==null?void 0:c.share_count)||0,webpage_url:t,platform:"douyin",platform_name:"抖音",extractor:"douyin_frontend",music:e.music?{title:e.music.title,author:e.music.author,duration:e.music.duration}:null,hashtags:((p=(f=e.text_extra)==null?void 0:f.filter(w=>w.hashtag_name))==null?void 0:p.map(w=>w.hashtag_name))||[],formats:[{format_id:"douyin_web",url:t,ext:"mp4",quality:720,width:720,height:1280,fps:30,vcodec:"h264",acodec:"aac",note:"需要抖音播放器或后端解析"}],embed:null}}createFallbackData(e){return{id:this.extractVideoId(e)||"unknown",title:"抖音视频(需要后端解析获取完整信息)",description:"由于抖音的反爬虫机制,建议使用后端解析获取完整视频信息",duration:0,thumbnail:"https://via.placeholder.com/720x1280/000000/FFFFFF?text=Douyin+Video",uploader:"抖音用户",uploader_id:"unknown",upload_date:new Date().toISOString().slice(0,10).replace(/-/g,""),view_count:0,like_count:0,comment_count:0,share_count:0,webpage_url:e,platform:"douyin",platform_name:"抖音",extractor:"douyin_frontend_fallback",formats:[{format_id:"douyin_fallback",url:e,ext:"mp4",quality:720,width:720,height:1280,fps:30,vcodec:"h264",acodec:"aac",note:"需要后端解析获取真实播放地址"}],embed:null,needsBackendParsing:!0,frontendLimitation:"抖音平台限制,前端无法获取完整信息"}}extractVideoId(e){const t=[/video\/(\d+)/,/v\.douyin\.com\/([A-Za-z0-9]+)/,/aweme\/v1\/aweme\/detail\/\?aweme_id=(\d+)/];for(const i of t){const o=e.match(i);if(o)return o[1]}return null}static canParse(e){return/douyin\.com|dy\.com|iesdouyin\.com/.test(e)}}class I{constructor(e={}){this.corsProxy=e.corsProxy}async parse(e){const t=this.extractVideoId(e);if(!t)throw new Error("无效的腾讯视频链接");return console.log(`🎬 正在解析腾讯视频: ${t}`),this.createBasicData(t,e)}createBasicData(e,t){return{id:e,title:"腾讯视频(需要完整解析请使用后端)",description:"腾讯视频前端解析受限,建议使用后端获取完整信息",duration:0,thumbnail:`https://puui.qpic.cn/qqvideo_ori/0/${e}_496_280/0`,uploader:"腾讯视频",uploader_id:"tencent",upload_date:new Date().toISOString().slice(0,10).replace(/-/g,""),view_count:0,like_count:0,comment_count:0,webpage_url:t,platform:"tencent",platform_name:"腾讯视频",extractor:"tencent_frontend_basic",formats:[{format_id:"tencent_web",url:t,ext:"mp4",quality:720,width:1280,height:720,fps:30,vcodec:"h264",acodec:"aac",note:"需要腾讯视频播放器"}],embed:{type:"iframe",url:`https://v.qq.com/txp/iframe/player.html?vid=${e}&autoplay=0`,width:1280,height:720},needsBackendParsing:!0,frontendLimitation:"腾讯视频CORS限制,前端无法获取详细信息"}}extractVideoId(e){const t=[/\/([a-zA-Z0-9_]+)\.html/,/vid=([a-zA-Z0-9_]+)/,/v\.qq\.com\/x\/page\/([a-zA-Z0-9_]+)/,/v\.qq\.com\/x\/cover\/[^\/]+\/([a-zA-Z0-9_]+)/];for(const i of t){const o=e.match(i);if(o)return o[1]}return null}static canParse(e){return/v\.qq\.com|qq\.com\/x\//.test(e)}}class V{constructor(e={}){this.corsProxy=e.corsProxy}async parse(e){const t=this.extractVideoId(e);if(!t)throw new Error("无效的西瓜视频链接");return console.log(`🍉 正在解析西瓜视频: ${t}`),this.createBasicData(t,e)}createBasicData(e,t){return{id:e,title:"西瓜视频(需要完整解析请使用后端)",description:"西瓜视频前端解析受限,建议使用后端获取完整信息",duration:0,thumbnail:"https://via.placeholder.com/1280x720/FF6B35/FFFFFF?text=Xigua+Video",uploader:"西瓜视频用户",uploader_id:"unknown",upload_date:new Date().toISOString().slice(0,10).replace(/-/g,""),view_count:0,like_count:0,comment_count:0,webpage_url:t,platform:"xigua",platform_name:"西瓜视频",extractor:"xigua_frontend_basic",formats:[{format_id:"xigua_web",url:t,ext:"mp4",quality:720,width:1280,height:720,fps:30,vcodec:"h264",acodec:"aac",note:"需要西瓜视频播放器或后端解析"}],embed:null,needsBackendParsing:!0,frontendLimitation:"西瓜视频反爬虫限制,前端无法获取详细信息"}}extractVideoId(e){const t=[/\/(\d+)\//,/xigua\.com\/(\d+)/,/ixigua\.com\/(\d+)/];for(const i of t){const o=e.match(i);if(o)return o[1]}return null}static canParse(e){return/ixigua\.com|xigua\.com/.test(e)}}class S{constructor(e={}){this.corsProxy=e.corsProxy}async parse(e){const t=this.extractPhotoId(e);if(!t)throw new Error("无效的快手链接");return console.log(`⚡ 正在解析快手视频: ${t}`),this.createBasicData(t,e)}createBasicData(e,t){return{id:e,title:"快手视频(需要完整解析请使用后端)",description:"快手视频前端解析受限,建议使用后端获取完整信息",duration:0,thumbnail:"https://via.placeholder.com/720x1280/FFE066/000000?text=Kuaishou+Video",uploader:"快手用户",uploader_id:"unknown",upload_date:new Date().toISOString().slice(0,10).replace(/-/g,""),view_count:0,like_count:0,comment_count:0,webpage_url:t,platform:"kuaishou",platform_name:"快手",extractor:"kuaishou_frontend_basic",formats:[{format_id:"kuaishou_web",url:t,ext:"mp4",quality:720,width:720,height:1280,fps:30,vcodec:"h264",acodec:"aac",note:"需要快手播放器或后端解析"}],embed:null,needsBackendParsing:!0,frontendLimitation:"快手反爬虫限制,前端无法获取详细信息"}}extractPhotoId(e){const t=[/photo\/(\d+)/,/short-video\/([a-zA-Z0-9_-]+)/,/kuaishou\.com\/u\/[^\/]+\/([a-zA-Z0-9_-]+)/];for(const i of t){const o=e.match(i);if(o)return o[1]}return null}static canParse(e){return/kuaishou\.com|ks\.com/.test(e)}}class E{constructor(e={}){this.corsProxy=e.corsProxy,this.apiKey=e.apiKey}async parse(e){const t=this.extractVideoId(e);if(!t)throw new Error("无效的YouTube链接");console.log(`🎬 正在解析YouTube视频: ${t}`);try{if(this.apiKey){const o=await this.getVideoInfoFromApi(t);return this.formatVideoData(o,e,"api")}const i=await this.getVideoInfoFromOEmbed(t);return this.formatVideoData(i,e,"oembed")}catch(i){return console.warn("YouTube解析失败:",i.message),this.createBasicEmbedData(t,e)}}async getVideoInfoFromApi(e){const t=`https://www.googleapis.com/youtube/v3/videos?id=${e}&part=snippet,statistics,contentDetails&key=${this.apiKey}`,i=await fetch(t);if(!i.ok)throw new Error(`YouTube API请求失败: ${i.status}`);const o=await i.json();if(!o.items||o.items.length===0)throw new Error("视频不存在或不可访问");return{type:"api",data:o.items[0]}}async getVideoInfoFromOEmbed(e){const t=`https://www.youtube.com/oembed?url=https://www.youtube.com/watch?v=${e}&format=json`,i=await fetch(t);if(!i.ok)throw new Error(`YouTube oEmbed请求失败: ${i.status}`);return{type:"oembed",data:await i.json()}}formatVideoData(e,t,i){var o,r,s,d;if(e.type==="api"){const n=e.data;return{id:n.id,title:n.snippet.title,description:n.snippet.description,duration:this.parseISO8601Duration(n.contentDetails.duration),thumbnail:((o=n.snippet.thumbnails.maxres)==null?void 0:o.url)||((r=n.snippet.thumbnails.high)==null?void 0:r.url)||((s=n.snippet.thumbnails.medium)==null?void 0:s.url),uploader:n.snippet.channelTitle,uploader_id:n.snippet.channelId,upload_date:n.snippet.publishedAt.slice(0,10).replace(/-/g,""),view_count:parseInt(n.statistics.viewCount)||0,like_count:parseInt(n.statistics.likeCount)||0,comment_count:parseInt(n.statistics.commentCount)||0,webpage_url:t,platform:"youtube",platform_name:"YouTube",extractor:"youtube_frontend_api",category_id:n.snippet.categoryId,tags:n.snippet.tags||[],language:n.snippet.defaultLanguage,formats:[{format_id:"youtube_embed",url:`https://www.youtube.com/embed/${n.id}`,ext:"mp4",quality:720,width:1280,height:720,fps:30,vcodec:"h264",acodec:"aac"}],embed:{type:"iframe",url:`https://www.youtube.com/embed/${n.id}?autoplay=0&controls=1`,width:1280,height:720}}}else if(e.type==="oembed"){const n=e.data,c=this.extractVideoId(t);return{id:c,title:n.title,description:"通过YouTube oEmbed API获取",duration:0,thumbnail:n.thumbnail_url,uploader:n.author_name,uploader_id:(d=n.author_url)==null?void 0:d.split("/").pop(),upload_date:new Date().toISOString().slice(0,10).replace(/-/g,""),view_count:0,like_count:0,comment_count:0,webpage_url:t,platform:"youtube",platform_name:"YouTube",extractor:"youtube_frontend_oembed",formats:[{format_id:"youtube_embed_oembed",url:`https://www.youtube.com/embed/${c}`,ext:"mp4",quality:n.height||720,width:n.width||1280,height:n.height||720,fps:30,vcodec:"h264",acodec:"aac"}],embed:{type:"iframe",url:`https://www.youtube.com/embed/${c}?autoplay=0&controls=1`,width:n.width||1280,height:n.height||720}}}}createBasicEmbedData(e,t){return{id:e,title:"YouTube视频(基础嵌入)",description:"无法获取详细信息,仅提供嵌入播放",duration:0,thumbnail:`https://img.youtube.com/vi/${e}/maxresdefault.jpg`,uploader:"未知",uploader_id:"unknown",upload_date:new Date().toISOString().slice(0,10).replace(/-/g,""),view_count:0,like_count:0,comment_count:0,webpage_url:t,platform:"youtube",platform_name:"YouTube",extractor:"youtube_frontend_basic",formats:[{format_id:"youtube_embed_basic",url:`https://www.youtube.com/embed/${e}`,ext:"mp4",quality:720,width:1280,height:720,fps:30,vcodec:"h264",acodec:"aac"}],embed:{type:"iframe",url:`https://www.youtube.com/embed/${e}?autoplay=0&controls=1`,width:1280,height:720}}}extractVideoId(e){const t=[/(?:https?:\/\/)?(?:www\.)?youtube\.com\/watch\?v=([a-zA-Z0-9_-]{11})/,/(?:https?:\/\/)?(?:www\.)?youtu\.be\/([a-zA-Z0-9_-]{11})/,/(?:https?:\/\/)?(?:www\.)?youtube\.com\/embed\/([a-zA-Z0-9_-]{11})/,/(?:https?:\/\/)?(?:www\.)?youtube\.com\/v\/([a-zA-Z0-9_-]{11})/];for(const i of t){const o=e.match(i);if(o)return o[1]}return null}parseISO8601Duration(e){const t=e.match(/PT(\d+H)?(\d+M)?(\d+S)?/);if(!t)return 0;const i=parseInt(t[1])||0,o=parseInt(t[2])||0,r=parseInt(t[3])||0;return i*3600+o*60+r}static canParse(e){return/youtube\.com|youtu\.be/.test(e)}}class F{constructor(e={}){this.corsProxy=e.corsProxy}async parse(e){const t=this.extractVideoId(e);if(!t)throw new Error("无效的Vimeo链接");console.log(`🎬 正在解析Vimeo视频: ${t}`);try{const i=await this.getVideoInfoFromOEmbed(t);return this.formatVideoData(i,e)}catch(i){return console.warn("Vimeo解析失败:",i.message),this.createBasicData(t,e)}}async getVideoInfoFromOEmbed(e){const t=`https://vimeo.com/api/oembed.json?url=https://vimeo.com/${e}`,i=await fetch(t);if(!i.ok)throw new Error(`Vimeo oEmbed请求失败: ${i.status}`);return await i.json()}formatVideoData(e,t){var o;const i=this.extractVideoId(t);return{id:i,title:e.title,description:e.description||"通过Vimeo oEmbed API获取",duration:e.duration||0,thumbnail:e.thumbnail_url,uploader:e.author_name,uploader_id:(o=e.author_url)==null?void 0:o.split("/").pop(),upload_date:new Date().toISOString().slice(0,10).replace(/-/g,""),view_count:0,like_count:0,comment_count:0,webpage_url:t,platform:"vimeo",platform_name:"Vimeo",extractor:"vimeo_frontend_oembed",formats:[{format_id:"vimeo_embed",url:`https://player.vimeo.com/video/${i}`,ext:"mp4",quality:e.height||720,width:e.width||1280,height:e.height||720,fps:30,vcodec:"h264",acodec:"aac"}],embed:{type:"iframe",url:`https://player.vimeo.com/video/${i}?autoplay=0&controls=1`,width:e.width||1280,height:e.height||720}}}createBasicData(e,t){return{id:e,title:"Vimeo视频(基础嵌入)",description:"无法获取详细信息,仅提供嵌入播放",duration:0,thumbnail:"https://via.placeholder.com/1280x720/1AB7EA/FFFFFF?text=Vimeo+Video",uploader:"未知",uploader_id:"unknown",upload_date:new Date().toISOString().slice(0,10).replace(/-/g,""),view_count:0,like_count:0,comment_count:0,webpage_url:t,platform:"vimeo",platform_name:"Vimeo",extractor:"vimeo_frontend_basic",formats:[{format_id:"vimeo_embed_basic",url:`https://player.vimeo.com/video/${e}`,ext:"mp4",quality:720,width:1280,height:720,fps:30,vcodec:"h264",acodec:"aac"}],embed:{type:"iframe",url:`https://player.vimeo.com/video/${e}?autoplay=0&controls=1`,width:1280,height:720}}}extractVideoId(e){const t=[/(?:https?:\/\/)?(?:www\.)?vimeo\.com\/(\d+)/,/(?:https?:\/\/)?(?:www\.)?vimeo\.com\/channels\/[\w-]+\/(\d+)/,/(?:https?:\/\/)?(?:www\.)?vimeo\.com\/groups\/[\w-]+\/videos\/(\d+)/];for(const i of t){const o=e.match(i);if(o)return o[1]}return null}static canParse(e){return/vimeo\.com/.test(e)}}class C{constructor(e={}){this.corsProxy=e.corsProxy||null,this.youtubeApiKey=e.youtubeApiKey||null,this.strictFrontendOnly=e.strictFrontendOnly||!1,this.parsers={bilibili:new A({corsProxy:this.strictFrontendOnly?null:this.corsProxy,strictFrontendOnly:this.strictFrontendOnly}),douyin:new $({corsProxy:this.strictFrontendOnly?null:this.corsProxy,strictFrontendOnly:this.strictFrontendOnly}),tencent:new I({corsProxy:this.strictFrontendOnly?null:this.corsProxy,strictFrontendOnly:this.strictFrontendOnly}),xigua:new V({corsProxy:this.strictFrontendOnly?null:this.corsProxy,strictFrontendOnly:this.strictFrontendOnly}),kuaishou:new S({corsProxy:this.strictFrontendOnly?null:this.corsProxy,strictFrontendOnly:this.strictFrontendOnly}),youtube:new E({corsProxy:this.strictFrontendOnly?null:this.corsProxy,apiKey:this.youtubeApiKey,strictFrontendOnly:this.strictFrontendOnly}),vimeo:new F({corsProxy:this.strictFrontendOnly?null:this.corsProxy,strictFrontendOnly:this.strictFrontendOnly})}}async parseVideo(e){const t=this.detectPlatform(e);if(!t)throw new Error("不支持的视频平台");console.log(`🎯 检测到平台: ${t}`);try{return{success:!0,data:await this.parsers[t].parse(e),platform:t,source:"frontend"}}catch(i){if(console.error(`${t} 解析失败:`,i),this.strictFrontendOnly)throw console.log("⚡ 严格前端模式:不会降级到后端解析"),new Error(`前端解析失败: ${i.message}`);if(this.corsProxy)try{return console.log("🔄 前端解析失败,尝试后端解析..."),await this.fallbackToBackend(e)}catch(o){throw new Error(`前端和后端解析都失败: ${i.message}, ${o.message}`)}throw i}}detectPlatform(e){if(A.canParse(e))return"bilibili";if($.canParse&&$.canParse(e))return"douyin";if(I.canParse&&I.canParse(e))return"tencent";if(V.canParse&&V.canParse(e))return"xigua";if(S.canParse&&S.canParse(e))return"kuaishou";if(E.canParse&&E.canParse(e))return"youtube";if(F.canParse&&F.canParse(e))return"vimeo";const t={bilibili:/bilibili\.com|b23\.tv/,douyin:/douyin\.com|dy\.com|iesdouyin\.com/,tencent:/v\.qq\.com|qq\.com\/x\//,xigua:/ixigua\.com|xigua\.com/,kuaishou:/kuaishou\.com|ks\.com/,youtube:/youtube\.com|youtu\.be/,vimeo:/vimeo\.com/};for(const[i,o]of Object.entries(t))if(o.test(e))return i;return null}async fallbackToBackend(e){if(!this.corsProxy)throw new Error("未配置后端代理服务器");const t=await fetch(`${this.corsProxy}/api/video/parse`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({url:e})});if(!t.ok)throw new Error(`后端解析失败: ${t.status}`);const i=await t.json();if(!i.success)throw new Error(i.message||"后端解析失败");return{...i,source:"backend"}}getSupportedPlatforms(){return{chinese:[{name:"哔哩哔哩",key:"bilibili",domains:["bilibili.com","b23.tv"]},{name:"抖音",key:"douyin",domains:["douyin.com","dy.com"]},{name:"腾讯视频",key:"tencent",domains:["v.qq.com"]},{name:"西瓜视频",key:"xigua",domains:["ixigua.com"]},{name:"快手",key:"kuaishou",domains:["kuaishou.com"]}],international:[{name:"YouTube",key:"youtube",domains:["youtube.com","youtu.be"]},{name:"Vimeo",key:"vimeo",domains:["vimeo.com"]}]}}}const W=(a,e,t)=>{const i=Y(e),o=a.message||"未知错误";if(o.includes("CORS"))return`❌ CORS跨域错误
🎯 平台: ${i}
📋 问题: 浏览器阻止了跨域请求
💡 解决方案:
• 确保后端CORS代理服务器运行正常
• 或切换到"纯后端解析"模式
• 检查服务器地址: ${{}.VITE_SERVER_URL||"http://localhost:3001"}`;if(o.includes("Network Error")||o.includes("fetch"))return`❌ 网络连接错误
🎯 平台: ${i}
📋 问题: 无法连接到目标服务器
💡 解决方案:
• 检查网络连接是否正常
• 确认视频链接是否可访问
• 尝试刷新页面重新加载`;if(o.includes("后端解析功能已简化"))return`ℹ️ 服务器配置说明
🎯 平台: ${i}
📋 当前服务器: 轻量级CORS代理服务器
💡 建议方案:
• 优先使用"前端解析"模式(推荐)
• 如需完整后端解析,请启动 packages/server/server.js
• 当前服务器主要支持前端解析的CORS代理功能`;if(o.includes("无效")||o.includes("链接"))return`❌ 视频链接无效
🎯 平台: ${i}
📋 问题: 无法识别或解析此视频链接
💡 解决方案:
• 确认链接格式正确 (如: https://www.bilibili.com/video/BV...)
• 检查视频是否存在或已被删除
• 尝试使用完整的视频页面链接`;if(o.includes("API")||o.includes("请求失败"))return`❌ API调用失败
🎯 平台: ${i}
📋 问题: 平台API返回错误 (${o})
💡 解决方案:
• 视频可能已被删除或设为私密
• 某些平台可能需要登录才能访问
• 尝试切换解析模式`;const r=U(t,i);return`❌ ${i}解析失败
📋 错误详情: ${o}
⚙️ 当前模式: ${t==="frontend"?"前端解析":t==="backend"?"后端解析":"智能模式"}
💡 建议尝试:
${r}`},Y=a=>a.includes("bilibili.com")||a.includes("b23.tv")?"B站":a.includes("youtube.com")||a.includes("youtu.be")?"YouTube":a.includes("douyin.com")?"抖音":a.includes("v.qq.com")?"腾讯视频":a.includes("ixigua.com")?"西瓜视频":a.includes("kuaishou.com")?"快手":a.includes("vimeo.com")?"Vimeo":"未知平台",U=(a,e)=>{const t=[];return a==="frontend"?(t.push('• 尝试切换到"智能模式"或"后端解析"'),e==="B站"||e==="YouTube"?t.push("• 检查网络连接和CORS设置"):t.push("• 此平台可能需要后端代理支持")):a==="backend"?(t.push("• 确认后端服务器正在运行"),t.push("• 检查服务器地址配置"),t.push('• 尝试切换到"前端解析"模式')):(t.push('• 尝试手动切换到"前端解析"或"后端解析"'),t.push("• 检查网络连接和服务器状态")),t.push("• 确认视频链接格式正确且视频存在"),t.join(`
`)},T=({url:a,width:e="100%",height:t="315",autoplay:i=!1,controls:o=!0,muted:r=!1,serverUrl:s="http://localhost:3001",youtubeApiKey:d=null,preferFrontend:n=!0,strictFrontendOnly:c=!1,forceBackendOnly:f=!1,onError:p,onLoad:w,className:_="",style:x={}})=>{const[h,y]=v.useState(null),[K,j]=v.useState(!1),[q,P]=v.useState(null),[X,O]=v.useState(null),L=v.useRef(null),k=v.useRef(null);v.useEffect(()=>{k.current=new C({corsProxy:s,youtubeApiKey:d,strictFrontendOnly:c})},[s,d,c]),v.useEffect(()=>{if(!a||!D(a)){P("无效的视频链接"),p&&p("无效的视频链接");return}z()},[a,n,c,f]);const z=async()=>{j(!0),P(null),O(null);try{if(f){console.log("🔄 强制使用后端解析"),await B();return}if(c){console.log("⚡ 严格前端解析模式 - 不会调用后端接口"),await N(!0);return}if(n&&k.current){console.log("🎯 优先使用前端解析");try{await N(!1);return}catch(m){console.warn("前端解析失败:",m.message),console.log("🔄 降级到后端解析"),await B();return}}await B()}catch(m){let u="auto";c?u="frontend":f?u="backend":n?u="frontend":u="backend";const g=W(m,a,u);P(g),p&&p(g)}finally{j(!1)}},N=async(m=!1)=>{if(!k.current)throw new Error("前端解析器未初始化");const u=await k.current.parseVideo(a);y(u.data),O(u.source),w&&w(u.data,u.source),u.data.needsBackendParsing&&!m&&console.warn("💡 建议使用后端解析获取更完整的信息"),m&&u.data.needsBackendParsing&&console.info("ℹ️ 严格前端模式:已获取基础信息,如需完整信息可切换到后端模式")},B=async()=>{const m=await fetch(`${s}/api/video/parse`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({url:a})});if(!m.ok)throw new Error(`后端解析失败: ${m.status}`);const u=await m.json();if(u.success)y(u.data),O("backend"),w&&w(u.data,"backend");else throw new Error(u.message||"视频解析失败")},J=()=>{P("视频播放失败"),p&&p("视频播放失败")};if(K)return l.jsxs("div",{className:`video-embed-container loading ${_}`,style:{width:e,height:t,...x},children:[l.jsxs("div",{className:"video-embed-loading",children:[l.jsx("div",{className:"loading-spinner"}),l.jsx("p",{children:"正在加载视频..."})]}),l.jsx("style",{jsx:!0,children:`
.video-embed-container {
position: relative;
background: #000;
border-radius: 8px;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
.video-embed-loading {
text-align: center;
color: white;
}
.loading-spinner {
width: 40px;
height: 40px;
border: 4px solid #333;
border-top: 4px solid #fff;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 16px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
`})]});if(q)return l.jsxs("div",{className:`video-embed-container error ${_}`,style:{width:e,height:t,...x},children:[l.jsxs("div",{className:"video-embed-error",children:[l.jsx("div",{className:"error-icon",children:"⚠️"}),l.jsx("pre",{className:"error-message",children:q}),l.jsx("button",{onClick:z,className:"retry-button",children:"重试"})]}),l.jsx("style",{jsx:!0,children:`
.video-embed-container {
position: relative;
background: #f5f5f5;
border: 2px dashed #ddd;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
}
.video-embed-error {
text-align: center;
color: #666;
padding: 20px;
}
.error-icon {
font-size: 48px;
margin-bottom: 16px;
}
.error-message {
white-space: pre-wrap;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
font-size: 13px;
line-height: 1.5;
color: #555;
background: #f8f9fa;
padding: 12px;
border-radius: 6px;
border-left: 4px solid #dc3545;
margin: 16px 0;
text-align: left;
max-height: 200px;
overflow-y: auto;
}
.retry-button {
background: #007bff;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
margin-top: 12px;
}
.retry-button:hover {
background: #0056b3;
}
`})]});if(!h)return null;const G=()=>h.embed&&h.embed.type==="iframe"?l.jsx("iframe",{src:h.embed.url,width:"100%",height:"100%",frameBorder:"0",allow:"autoplay; fullscreen; picture-in-picture",allowFullScreen:!0,style:{borderRadius:"8px"},title:h.title}):null,R=()=>{var u;const m=(u=h.formats)==null?void 0:u.filter(g=>g.url&&!g.url.includes("embed")&&!g.note);return m&&m.length>0?l.jsxs("video",{ref:L,width:"100%",height:"100%",controls:o,autoPlay:i,muted:r,onError:J,style:{borderRadius:"8px"},poster:h.thumbnail,children:[m.map((g,H)=>l.jsx("source",{src:g.url,type:`video/${g.ext}`},H)),"您的浏览器不支持视频播放。"]}):null};return l.jsxs("div",{className:`video-embed-container ${_}`,style:{width:e,height:t,...x},children:[G()||R()||l.jsxs("div",{className:"video-placeholder",children:[l.jsx("img",{src:h.thumbnail,alt:h.title,style:{width:"100%",height:"100%",objectFit:"cover",borderRadius:"8px"}}),l.jsx("div",{className:"play-overlay",children:l.jsx("div",{className:"play-button",onClick:()=>window.open(h.webpage_url,"_blank"),children:"▶️ 播放视频"})})]}),l.jsx("style",{jsx:!0,children:`
.video-embed-container {
position: relative;
border-radius: 8px;
overflow: hidden;
background: #000;
}
.video-placeholder {
position: relative;
width: 100%;
height: 100%;
}
.play-overlay {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0, 0, 0, 0.8);
border-radius: 50px;
padding: 12px 24px;
cursor: pointer;
transition: all 0.3s ease;
}
.play-overlay:hover {
background: rgba(0, 0, 0, 0.9);
transform: translate(-50%, -50%) scale(1.05);
}
.play-button {
color: white;
font-size: 16px;
font-weight: 500;
}
.video-info {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: linear-gradient(transparent, rgba(0,0,0,0.8));
color: white;
padding: 20px 16px 16px;
transform: translateY(100%);
transition: transform 0.3s ease;
}
.video-embed-container:hover .video-info {
transform: translateY(0);
}
.video-title {
font-size: 14px;
font-weight: 500;
margin-bottom: 8px;
line-height: 1.3;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.video-meta {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 4px;
flex-wrap: wrap;
}
.platform-badge {
background: linear-gradient(45deg, #667eea, #764ba2);
color: white;
padding: 2px 8px;
border-radius: 12px;
font-size: 11px;
font-weight: 500;
}
.source-badge {
padding: 2px 6px;
border-radius: 10px;
font-size: 10px;
font-weight: 500;
}
.source-badge.frontend {
background: #10b981;
color: white;
}
.source-badge.backend {
background: #f59e0b;
color: white;
}
.uploader {
font-size: 11px;
opacity: 0.9;
}
.video-stats {
display: flex;
gap: 12px;
font-size: 11px;
opacity: 0.8;
}
`})]})};b.VideoEmbed=T,b.default=T,b.extractVideoId=M,b.formatDuration=Z,b.isValidUrl=D,Object.defineProperties(b,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
//# sourceMappingURL=index.umd.js.map