@@ -135,6 +135,11 @@ const props = defineProps({
135135 type: Object ,
136136 default : () => ({})
137137 },
138+ // 弹幕链接,用于弹幕功能
139+ danmakuUrl: {
140+ type: String ,
141+ default: ' '
142+ },
138143 // 画质相关属性
139144 qualities: {
140145 type: Array ,
@@ -357,19 +362,39 @@ const loadDanmakuData = async (videoUrl) => {
357362 danmakuLoading .value = true
358363
359364 try {
360- const danmakuUrl = parseDanmakuUrl (videoUrl)
365+ // 优先使用 T4 解析返回的弹幕链接
366+ let danmakuUrl = props .danmakuUrl
367+
368+ // 如果没有 T4 弹幕链接,尝试从视频URL解析
369+ if (! danmakuUrl) {
370+ danmakuUrl = parseDanmakuUrl (videoUrl)
371+ }
372+
361373 if (! danmakuUrl) {
362374 console .log (' 无法解析弹幕URL,使用默认弹幕数据' )
363375 return getDefaultDanmakuData ()
364376 }
365377
366- // 这里可以实现实际的弹幕数据加载逻辑
367- // const response = await fetch(danmakuUrl)
368- // const data = await response.json()
369- // return parseDanmakuData(data)
370-
371- // 暂时返回默认弹幕数据
372- return getDefaultDanmakuData ()
378+ console .log (' 弹幕链接:' , danmakuUrl)
379+
380+ // 处理不同协议的弹幕链接
381+ if (danmakuUrl .startsWith (' http://' ) || danmakuUrl .startsWith (' https://' )) {
382+ // HTTP协议:直接请求弹幕数据
383+ console .log (' 处理HTTP协议弹幕链接:' , danmakuUrl)
384+ const response = await fetch (danmakuUrl)
385+ const data = await response .json ()
386+ return parseDanmakuData (data)
387+ } else if (danmakuUrl .startsWith (' web://' )) {
388+ // WEB协议:创建自定义HTML层显示iframe
389+ console .log (' 处理WEB协议弹幕链接:' , danmakuUrl)
390+ const iframeUrl = danmakuUrl .replace (' web://' , ' ' )
391+ await createDanmakuIframeLayer (iframeUrl)
392+ // WEB协议不返回弹幕数据,而是通过iframe显示
393+ return []
394+ } else {
395+ console .log (' 未知弹幕协议,使用默认弹幕数据' )
396+ return getDefaultDanmakuData ()
397+ }
373398 } catch (error) {
374399 console .error (' 加载弹幕数据失败:' , error)
375400 return getDefaultDanmakuData ()
@@ -401,6 +426,174 @@ const getDefaultDanmakuData = () => {
401426 ]
402427}
403428
429+ // 解析弹幕数据 - 将HTTP协议返回的数据转换为ArtPlayer弹幕格式
430+ const parseDanmakuData = (data ) => {
431+ try {
432+ // 处理不同格式的弹幕数据
433+ if (Array .isArray (data)) {
434+ // 如果已经是数组格式,直接处理
435+ return data .map (item => ({
436+ text: item .text || item .content || ' ' ,
437+ time: parseFloat (item .time || item .timestamp || 0 ),
438+ color: item .color || ' #ffffff' ,
439+ type: item .type || ' right'
440+ })).filter (item => item .text .trim ().length > 0 )
441+ } else if (data .data && Array .isArray (data .data )) {
442+ // 如果数据在data字段中
443+ return data .data .map (item => ({
444+ text: item .text || item .content || ' ' ,
445+ time: parseFloat (item .time || item .timestamp || 0 ),
446+ color: item .color || ' #ffffff' ,
447+ type: item .type || ' right'
448+ })).filter (item => item .text .trim ().length > 0 )
449+ } else {
450+ console .warn (' 未知的弹幕数据格式:' , data)
451+ return getDefaultDanmakuData ()
452+ }
453+ } catch (error) {
454+ console .error (' 解析弹幕数据失败:' , error)
455+ return getDefaultDanmakuData ()
456+ }
457+ }
458+
459+ // 创建弹幕iframe层 - 处理web://协议的弹幕链接
460+ const createDanmakuIframeLayer = async (iframeUrl ) => {
461+ try {
462+ if (! artPlayerInstance .value ) {
463+ console .warn (' ArtPlayer实例不存在,无法创建弹幕iframe层' )
464+ return
465+ }
466+
467+ console .log (' 创建弹幕iframe层:' , iframeUrl)
468+
469+ // 移除已存在的弹幕iframe层
470+ removeDanmakuIframeLayer ()
471+
472+ // 更新iframe层内容
473+ artPlayerInstance .value .layers .update ({
474+ name: ' danmaku-iframe' ,
475+ html: `
476+ <div class="danmaku-iframe-container" style="
477+ position: absolute;
478+ top: 0;
479+ left: 0;
480+ width: 100%;
481+ height: 100%;
482+ pointer-events: none;
483+ z-index: 10;
484+ ">
485+ <iframe
486+ src="${ iframeUrl} "
487+ style="
488+ width: 100%;
489+ height: 100%;
490+ border: none;
491+ background: transparent;
492+ pointer-events: auto;
493+ "
494+ frameborder="0"
495+ allowtransparency="true"
496+ ></iframe>
497+ </div>
498+ ` ,
499+ style: {
500+ position: ' absolute' ,
501+ top: ' 0' ,
502+ left: ' 0' ,
503+ width: ' 100%' ,
504+ height: ' 100%' ,
505+ pointerEvents: ' none' ,
506+ display: ' block' ,
507+ zIndex: ' 10'
508+ }
509+ })
510+
511+ console .log (' 弹幕iframe层创建成功' )
512+ } catch (error) {
513+ console .error (' 创建弹幕iframe层失败:' , error)
514+ }
515+ }
516+
517+ // 移除弹幕iframe层
518+ const removeDanmakuIframeLayer = () => {
519+ try {
520+ if (artPlayerInstance .value && artPlayerInstance .value .layers ) {
521+ artPlayerInstance .value .layers .update ({
522+ name: ' danmaku-iframe' ,
523+ html: ' ' ,
524+ style: {
525+ position: ' absolute' ,
526+ top: ' 0' ,
527+ left: ' 0' ,
528+ width: ' 100%' ,
529+ height: ' 100%' ,
530+ pointerEvents: ' none' ,
531+ display: ' none' ,
532+ zIndex: ' 10'
533+ }
534+ })
535+ }
536+ } catch (error) {
537+ console .error (' 移除弹幕iframe层失败:' , error)
538+ }
539+ }
540+
541+ // 处理弹幕URL - 根据协议类型选择处理方式
542+ const handleDanmakuUrl = async () => {
543+ try {
544+ if (! props .danmakuUrl || ! props .danmakuUrl .trim ()) {
545+ console .log (' 没有弹幕URL,使用默认弹幕数据' )
546+ return
547+ }
548+
549+ const danmakuUrl = props .danmakuUrl .trim ()
550+ console .log (' 处理弹幕URL:' , danmakuUrl)
551+
552+ if (danmakuUrl .startsWith (' web://' )) {
553+ // 处理web://协议 - 创建iframe层
554+ const iframeUrl = danmakuUrl .substring (6 ) // 移除 'web://' 前缀
555+ console .log (' 检测到web://协议,创建iframe层:' , iframeUrl)
556+ await createDanmakuIframeLayer (iframeUrl)
557+ } else if (danmakuUrl .startsWith (' http://' ) || danmakuUrl .startsWith (' https://' )) {
558+ // 处理HTTP协议 - 加载弹幕数据
559+ console .log (' 检测到HTTP协议,加载弹幕数据:' , danmakuUrl)
560+ danmakuLoading .value = true
561+
562+ try {
563+ const response = await fetch (danmakuUrl)
564+ if (! response .ok ) {
565+ throw new Error (` HTTP ${ response .status } : ${ response .statusText } ` )
566+ }
567+
568+ const data = await response .json ()
569+ const parsedData = parseDanmakuData (data)
570+
571+ // 更新弹幕数据
572+ danmakuData .value = parsedData
573+ console .log (' 弹幕数据加载成功,条数:' , parsedData .length )
574+
575+ // 如果弹幕插件已初始化,更新弹幕数据
576+ if (artPlayerInstance .value && artPlayerInstance .value .plugins && artPlayerInstance .value .plugins .artplayerPluginDanmuku ) {
577+ artPlayerInstance .value .plugins .artplayerPluginDanmuku .config ({
578+ danmuku: parsedData
579+ })
580+ console .log (' 弹幕插件数据已更新' )
581+ }
582+ } catch (error) {
583+ console .error (' 加载弹幕数据失败:' , error)
584+ // 使用默认弹幕数据
585+ danmakuData .value = getDefaultDanmakuData ()
586+ } finally {
587+ danmakuLoading .value = false
588+ }
589+ } else {
590+ console .warn (' 不支持的弹幕URL协议:' , danmakuUrl)
591+ }
592+ } catch (error) {
593+ console .error (' 处理弹幕URL失败:' , error)
594+ }
595+ }
596+
404597// 初始化 ArtPlayer
405598const initArtPlayer = async (url ) => {
406599 if (! artPlayerContainer .value || ! url) return
@@ -681,6 +874,20 @@ const initArtPlayer = async (url) => {
681874 hideQualityLayer ()
682875 }
683876 }
877+ },
878+ {
879+ name: ' danmaku-iframe' ,
880+ html: ' ' ,
881+ style: {
882+ position: ' absolute' ,
883+ top: ' 0' ,
884+ left: ' 0' ,
885+ width: ' 100%' ,
886+ height: ' 100%' ,
887+ pointerEvents: ' none' ,
888+ display: ' none' ,
889+ zIndex: ' 10'
890+ }
684891 }
685892 ],
686893 // 插件配置
@@ -704,6 +911,8 @@ const initArtPlayer = async (url) => {
704911 console .log (' ArtPlayer 准备就绪' )
705912 // 应用片头片尾设置
706913 applySkipSettings ()
914+ // 处理弹幕URL
915+ handleDanmakuUrl ()
707916 })
708917
709918 art .on (' video:loadstart' , () => {
@@ -1689,6 +1898,19 @@ watch(danmakuEnabled, (newEnabled) => {
16891898 localStorage .setItem (' danmakuEnabled' , newEnabled .toString ())
16901899})
16911900
1901+ // 监听弹幕URL变化,重新处理弹幕
1902+ watch (() => props .danmakuUrl , async (newDanmakuUrl ) => {
1903+ console .log (' danmakuUrl 变化:' , newDanmakuUrl)
1904+
1905+ if (artPlayerInstance .value ) {
1906+ // 移除之前的iframe层
1907+ removeDanmakuIframeLayer ()
1908+
1909+ // 处理新的弹幕URL
1910+ await handleDanmakuUrl ()
1911+ }
1912+ })
1913+
16921914// 窗口大小变化处理
16931915const handleResize = () => {
16941916 if (artPlayerContainer .value && artPlayerInstance .value ) {
@@ -1748,6 +1970,9 @@ onUnmounted(() => {
17481970
17491971 // 销毁播放器实例
17501972 if (artPlayerInstance .value ) {
1973+ // 清理弹幕iframe层
1974+ removeDanmakuIframeLayer ()
1975+
17511976 // 清理自定义播放器
17521977 if (artPlayerInstance .value .customPlayer && artPlayerInstance .value .customPlayerFormat ) {
17531978 const format = artPlayerInstance .value .customPlayerFormat
0 commit comments