使用 XGPlayer 和 React 实现视频封面预览及播放功能

使用 XGPlayer 和 React 实现视频封面预览及播放功能

在现代 web 开发中,如何高效地实现视频的封面展示和播放是一项常见的需求。今天,我们将介绍如何利用 XGPlayerReact 实现一个带有封面图预览和点击播放功能的视频组件。

项目背景

在这个项目中,我们的目标是:

  1. 从视频中提取第一帧作为封面图。
  2. 实现点击封面图时播放视频,并展示视频播放器。
  3. 使用字节跳动的开源视频播放器 XGPlayer,并结合 React 来封装组件。

项目结构

我们将创建一个 VideoMessage 组件,它接受 extradata 两个属性,其中 extra 包含了视频信息,比如视频的封面图和视频的 URL。

1. 初始化依赖

首先,我们需要安装以下依赖:

npm install xgplayer antd @ant-design/icons
  • xgplayer:字节跳动提供的高效视频播放器。
  • antd:UI 组件库,提供模态框和图标。
  • @ant-design/icons:Ant Design 的图标库,用于展示播放和关闭图标。

2. 组件结构和功能实现

接下来,我们开始实现组件。

2.1 组件定义
import React, { useState, useEffect, useRef } from 'react';
import 'xgplayer/dist/index.min.css';
import { Image, Modal } from 'antd';
import { PlayCircleOutlined, CloseOutlined } from '@ant-design/icons';
import { getImageInfo } from '@/utils/functions'; // 用于获取图片大小的工具
import Player from 'xgplayer'; // 引入 XGPlayer

const VideoMessage = ({ extra, data }) => {
    const [openVideo, setOpenVideo] = useState(false);
    const [cover, setCover] = useState(extra.cover || ''); // 默认封面为空
    const videoRef = useRef(null); // 用于隐藏的 video 元素
    const playerRef = useRef(null); // 用于存储 XGPlayer 实例

    // 获取图片尺寸信息
    const img = (src, width = 200) => {
        const info = getImageInfo(src);
        if (info.width === 0 || info.height === 0) return {};
        if (info.height > 300) return { height: '300px' };
        if (info.width < width) {
            return { width: `${info.width}px`, height: `${info.height}px` };
        }
        return { width: `${width}px`, height: `${info.height / (info.width / width)}px` };
    };

    // 播放视频
    const onPlay = () => {
        setOpenVideo(true); // 打开视频播放的 Modal
    };

    // 获取视频的第一帧作为封面图
    const captureFirstFrame = (videoElement) => {
        if (!videoElement) return;

        const canvas = document.createElement('canvas');
        const ctx = canvas.getContext('2d');
        
        // 设置 canvas 尺寸为视频的尺寸
        canvas.width = videoElement.videoWidth;
        canvas.height = videoElement.videoHeight;
        
        // 将视频帧绘制到 canvas 上
        ctx.drawImage(videoElement, 0, 0, canvas.width, canvas.height);
        
        // 将 canvas 转为 Blob 并设置为封面图
        canvas.toBlob(function (blob) {
            if (blob) {
                const url = URL.createObjectURL(blob);
                setCover(url); // 更新封面为第一帧图像
            } else {
                console.error("Failed to generate the blob from the canvas.");
            }
        }, 'image/jpeg', 0.8);
    };

    // 加载视频并提取第一帧
    useEffect(() => {
        const videoElement = videoRef.current;
        if (videoElement) {
            videoElement.src = "http://" + extra.url;
            videoElement.crossOrigin = 'anonymous'; // 跨域设置,保证视频的加载
            videoElement.currentTime = 1; // 直接跳到第一帧
            videoElement.addEventListener('loadeddata', () => {
                captureFirstFrame(videoElement); // 提取第一帧
            });
            return () => {
                videoElement.removeEventListener('loadeddata', captureFirstFrame); // 清理事件
            };
        }
    }, [extra.url]);

    // 在打开视频时初始化播放器
    useEffect(() => {
        if (openVideo && extra.url) {
            const player = new Player({
                id: 'im-xgplayer',
                url: "http://" + extra.url,
                fluid: true, // 播放器自适应尺寸
                autoplay: true, // 自动播放
                lang: 'zh-cn',
            });
            playerRef.current = player;
            // 关闭时销毁播放器实例
            return () => {
                if (playerRef.current) {
                    playerRef.current.destroy();
                }
            };
        }
    }, [openVideo, extra.url]);

    return (
        <>
            <div
                className="p-1 relative max-w-96"
                onClick={onPlay}>
                <Image
                    style={{ maxHeight: '100%', maxWidth: '100%' }}
                    src={cover} // 显示封面
                    preview={false}
                />
                <div className="absolute top-1/2 left-1/2 w-8 h-8 cursor-pointer transform translate-x-[-50%] translate-y-[-50%]">
                    <PlayCircleOutlined
                        className="text-gray-800 dark:text-gray-200 hover:text-sky-500 dark:hover:text-sky-500"
                        style={{ fontSize: '36px' }}
                    />
                </div>
            </div>
            <video ref={videoRef} style={{ display: 'none' }} /> {/* 隐藏的 video 元素 */}
            <Modal
                open={openVideo}
                onCancel={() => setOpenVideo(false)} // 关闭视频时关闭 Modal
                footer={null}
                width={800}
                closeIcon={<CloseOutlined />}
            >
                <div id="im-xgplayer"></div> {/* 放置 XGPlayer 的容器 */}
            </Modal>
        </>
    );
};

export default VideoMessage;

3. 功能详解

3.1 获取封面图

视频的封面图是通过 canvas 获取的。通过 canvas.toBlob 方法,我们能够将视频的第一帧转为图像 URL,从而设置为封面。

3.2 播放器初始化与销毁

当用户点击封面图时,打开一个模态框 (Modal),并在该模态框内初始化 XGPlayer 播放器。每当视频加载并准备播放时,我们会通过 Player 类初始化播放器实例,autoplay 设置为 true 来自动播放。

3.3 动态更新封面

每当视频加载完成并播放时,我们会更新封面图,并通过 setCover 将第一帧设为视频的封面图。

3.4 样式和布局

使用了 Ant DesignImage 组件来显示封面图,同时使用 PlayCircleOutlined 图标作为播放按钮,点击时打开模态框,展示视频播放器。

4. 关键点总结

  • Canvas API:利用 canvas 获取视频的第一帧并将其作为封面。
  • XGPlayer:通过字节跳动提供的高性能播放器组件来播放视频。
  • Ant Design Modal:封装视频播放器,点击封面时弹出播放窗口。
  • React Hooks:使用 useStateuseEffect 进行状态管理和副作用处理。

5. 总结

通过结合 XGPlayerReact,我们能够实现一个流畅且功能丰富的视频播放器组件,支持从视频提取封面、点击播放并展示播放器等功能。这样的视频播放体验提升了应用的交互性和用户体验,同时也能有效利用视频播放器的强大功能。

6.效果展示

在这里插入图片描述
在这里插入图片描述

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值