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})。

主要流程和结构

  1. 构造函数 constructor()

    • 读取的 content 作为 webApiBaseAddress(作为后端 API 基址)。
    • 选择页面上的容器元素,选择器为 '.js-home-video-container'。如果 meta 或容器不存在则直接返回(不初始化)。
    • 初始化状态 hasPlayed = false(用于确保只在首次播放时上报一次播放)。
    • 调用 init() 开始流程。
  2. init()

    • 调用 getVideos() 获取视频列表(从 ${webApiBaseAddress}/api/Video)。
    • 成功返回后调用 playRandom(videos),失败则在控制台警告。
  3. getVideos()

    • 使用 fetch 向后端 GET /api/Video(带 Content-Type: application/json, mode: cors)请求视频数据并返回解析后的 JSON。
    • 如果请求失败,catch 中返回空数组 []。
  4. 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}(用于播放上报/计数)
  5. 弹幕插件配置(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 允许弹幕在播放器中显示(即在发送后仍显示)
  6. 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')

外部依赖

  • 从外部 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(比如通知显示、弹幕发送失败)可根据需要增强。
评论加载中...