import Artplayer from 'https://dpangzi.com/library/artplayer/artplayer.mjs';
import artplayerPluginDanmuku from 'https://dpangzi.com/library/artplayer-plugin-danmuku/artplayer-plugin-danmuku.mjs'
import Hls from 'https://dpangzi.com/library/hls.js/hls.mjs';
/**
* Home Video Widget
* Handles the random video player on the homepage
*/
export class HomeVideo {
constructor() {
this.webApiBaseAddress = document.querySelector("meta[name=web-api-base-address]").content;
if (!this.webApiBaseAddress) {
return;
}
// New selector for the video container in the main content area
this.playerVideoContainerSelector = '.js-home-video-container';
this.container = document.querySelector(this.playerVideoContainerSelector);
if (!this.container) {
return;
}
this.hasPlayed = false;
this.init();
}
init() {
const that = this;
this.getVideos()
.then(videos => {
that.playRandom(videos);
})
.catch(err => console.warn('Failed to fetch videos:', err));
}
async getVideos() {
try {
const response = await fetch(`${this.webApiBaseAddress}/api/Video`, {
method: 'GET',
headers: {'Content-Type': 'application/json'},
mode: 'cors'
});
return await response.json();
} catch (err) {
return [];
}
}
playRandom(videos) {
if (!videos || videos.length === 0) return;
const index = Math.floor(Math.random() * videos.length);
const video = videos[index];
const videoId = video['id'];
const art = new Artplayer({
container: this.container,
url: video['m3u8'],
type: "m3u8",
poster: video['cover'],
fullscreenWeb: true,
fullscreen: true,
customType: {
m3u8: this.playM3u8
},
plugins: [
artplayerPluginDanmuku({
danmuku: async function () {
let danmakuResponse = await fetch(`/history/danmaku/v2/${videoId}`);
return await danmakuResponse.json();
},
async beforeEmit(danmaku){
if(danmaku.text === null || danmaku.text.trim() === ""){
return false;
}
danmaku["id"] = videoId;
await fetch(`/send/danmaku/v2`, {
method: 'post',
headers: {
'Content-Type': 'application/json;charset=utf-8'
},
body: JSON.stringify(danmaku)
});
return true;
}
})
],
controls: [],
});
art.on('fullscreenWeb', (state) => {
if (state) {
this.container.classList.add('is-web-fullscreen');
} else {
this.container.classList.remove('is-web-fullscreen');
}
});
art.on('fullscreen', (state) => {
if (state) {
this.container.classList.add('is-fullscreen');
} else {
this.container.classList.remove('is-fullscreen');
}
});
art.on('play', async () => {
if (!this.hasPlayed) {
this.hasPlayed = true;
await fetch(`/play/${videoId}`, { method: 'PATCH' });
}
});
}
playM3u8(video, url, art) {
if (Hls.isSupported()) {
if (art.hls) art.hls.destroy();
const hls = new Hls();
hls.loadSource(url);
hls.attachMedia(video);
art.hls = hls;
art.on('destroy', () => hls.destroy());
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
video.src = url;
} else {
art.notice.show = 'Unsupported playback format: m3u8';
}
}
}
⚠⚠ 以下内容为AI分析的结果,请根据实际情况进行判断。
下面把这段代码的功能、流程和关键点逐项讲清楚(中文说明):
概述
- 这是一个用于首页随机视频播放器的类 HomeVideo。
- 作用:在页面上找到指定容器,获取后端视频列表,随机选一个视频并用 Artplayer 播放,支持 m3u8(HLS)播放和弹幕(danmaku),并在第一次播放时向后端打点(PATCH /play/{id})。
主要流程和结构
构造函数 constructor()
- 读取的 content 作为 webApiBaseAddress(作为后端 API 基址)。
- 选择页面上的容器元素,选择器为 '.js-home-video-container'。如果 meta 或容器不存在则直接返回(不初始化)。
- 初始化状态 hasPlayed = false(用于确保只在首次播放时上报一次播放)。
- 调用 init() 开始流程。
init()
- 调用 getVideos() 获取视频列表(从
${webApiBaseAddress}/api/Video)。 - 成功返回后调用 playRandom(videos),失败则在控制台警告。
- 调用 getVideos() 获取视频列表(从
getVideos()
- 使用 fetch 向后端 GET /api/Video(带 Content-Type: application/json, mode: cors)请求视频数据并返回解析后的 JSON。
- 如果请求失败,catch 中返回空数组 []。
playRandom(videos)
- 如果 videos 空则直接返回。
- 随机选取一个 video(video.id, video.m3u8, video.cover 等字段被使用)。
- 用 Artplayer 创建播放器,主要配置:
- container: 容器元素
- url: video['m3u8']
- type: "m3u8"
- poster: video['cover']
- fullscreenWeb / fullscreen = true
- customType: 将 m3u8 类型的处理器绑定到类方法 playM3u8(用于 HLS 播放)
- plugins: 使用 artplayer 插件 artplayerPluginDanmuku 来加载和发送弹幕
- controls: [] (没有内置控制条)
- 绑定若干事件:
- fullscreenWeb:切换容器 class 'is-web-fullscreen'
- fullscreen:切换容器 class 'is-fullscreen'
- play:首次播放时(hasPlayed 为 false)将 hasPlayed 设为 true,并 PATCH 请求
/play/{videoId}(用于播放上报/计数)
弹幕插件配置(artplayerPluginDanmuku)
- danmuku: 异步函数,fetch(
/history/danmaku/v2/${videoId}) 并返回 JSON(用于加载历史弹幕) - beforeEmit(danmaku):发送前的回调
- 过滤掉 text 为 null 或仅空白的弹幕(返回 false 阻止发送)
- 为弹幕对象设置 id 字段为当前 videoId
- 异步 POST 到
/send/danmaku/v2(Content-Type: application/json;charset=utf-8),body 为 JSON.stringify(danmaku) - 返回 true 允许弹幕在播放器中显示(即在发送后仍显示)
- danmuku: 异步函数,fetch(
playM3u8(video, url, art)
- 若 Hls.isSupported()(hls.js 支持),就:
- 若 art.hls 已存在则 destroy 旧实例
- 新建 Hls,loadSource(url),attachMedia(video),并把实例保存到 art.hls,同时在 art 的 destroy 事件上销毁 hls
- 否则若浏览器原生能播放 'application/vnd.apple.mpegurl'(iOS Safari),设置 video.src = url
- 否则显示不支持的提示(art.notice.show = 'Unsupported playback format: m3u8')
- 若 Hls.isSupported()(hls.js 支持),就:
外部依赖
- 从外部 URL 导入 Artplayer(播放器库)、artplayer-plugin-danmuku(弹幕插件)、hls.js(HLS 支持)。
- 请求的后端接口(示例):
- GET ${webApiBaseAddress}/api/Video (获取视频列表)
- GET /history/danmaku/v2/{videoId} (加载弹幕历史)
- POST /send/danmaku/v2 (发送弹幕)
- PATCH /play/{videoId} (播放上报)
注意点 / 潜在问题
- 如果页面没有 meta[name="web-api-base-address"] 或没有 .js-home-video-container 容器,会直接不初始化(静默失败)。
- getVideos 在 fetch 出错时返回 [],不会暴露错误细节。
- art.notice.show 的用法:代码里写成赋值 art.notice.show = '...',但按某些 Artplayer API 可能是调用函数(需确认库的正确用法),若是错误可能不会展示提示。
- beforeEmit 中对发送弹幕的网络请求没有错误处理;如果发送失败,弹幕仍会被允许显示(返回 true)。
- play 的上报是 PATCH /play/{id} 且没有请求体,后端需按此实现。
- controls: [] 表示没有默认控制条,可能需要按需添加自定义控件或允许用户控制。
总结
- 该类实现了在页面指定容器内,随机选取后端视频并用 Artplayer 播放,提供 HLS 播放支持和弹幕功能,并在首次播放时向后端上报播放。整体逻辑清晰,依赖外部库与后端若干接口,错误处理和某些 edge case(比如通知显示、弹幕发送失败)可根据需要增强。
评论加载中...