mxivideo/demo.md

6.2 KiB

import React, { useEffect, useImperativeHandle, useRef, useState } from 'react'; import Hls from 'hls.js'; import axios from 'axios'; import { container, CoreEnv } from '@roasmax/core'; import { snowflake } from '@roasmax/utils'; import { useQuery } from '@tanstack/react-query'; import { LocAttachmentController } from '@roasmax/sdk';

export interface OnSourceLoaded { (player: HTMLVideoElement | null): void; } export interface OnPlayerError { (err: Error, player: HTMLVideoElement | null): void; } export interface OnPlayerTimeChange { (player: HTMLVideoElement | null): void; } export interface LiveStreamPlayerRef { seek(time: number): void; pause(): void; play(): void; getCurrentTime(): number; isPaused(): boolean; getDuration(): number; captureFrame(): string | undefined; fullScreen(): void; }

export async function uploadCaptureFrame(content: string, projectId: string): Promise<{ urn: string }> { const coreEnv = container.resolve(CoreEnv); return axios .create({ baseURL: coreEnv.MODAL_BASE_URL, headers: { Authorization: Bearer ${coreEnv.MODAL_SERVER_KEY || 'bowong7777'}, }, }) .request({ url: /cache/upload-s3-b64, method: post, data: { file: { raw_content: content, filename: images/${projectId}/${snowflake.nextId()}.png, content_type: application/json, }, }, }) .then((res) => res.data); }

export const LiveStreamPlayer: React.FC<{ id?: string; ref?: React.RefObject<LiveStreamPlayerRef | null>; onSourceLoaded: OnSourceLoaded; onError: OnPlayerError; onTimeChange: OnPlayerTimeChange; }> = ({ id, onSourceLoaded, onError, onTimeChange, ref }) => { const videoRef = useRef(null); const hlsRef = useRef<Hls | null>(null); const [isLoading, setIsLoading] = useState(true);

const { data: attachment } = useQuery({ queryKey: [generateUrlById, id], queryFn: async () => { if (!id) return ``; const c = container.resolve(LocAttachmentController); const attachment = await c.get(id); return attachment; }, });

useImperativeHandle(ref, () => ({ seek: (time: number) => { videoRef.current && (videoRef.current.currentTime = time); }, pause: () => { videoRef.current?.pause(); }, play: () => { videoRef.current?.play(); }, isPaused: () => { return !!videoRef.current?.paused; }, getCurrentTime: () => { return videoRef.current?.currentTime || 0; }, getDuration: () => { return videoRef.current?.duration || 0; }, captureFrame: () => { const video = videoRef.current; if (!video) return; // 创建canvas元素 const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); if (!ctx) return; // 设置canvas尺寸 canvas.width = video.videoWidth; canvas.height = video.videoHeight;

  // 上传到modal
  // 绘制视频帧到canvas
  ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
  const dataURL = canvas.toDataURL('image/jpeg', 0.05);
  return dataURL;
},
fullScreen: () => {
  const video = videoRef.current;
  if (video) {
    video.requestFullscreen();
  }
},

}));

useEffect(() => { const video = videoRef.current; if (!video) return;

const showLoading = () => setIsLoading(true);
const hideLoading = () => setIsLoading(false);

video.addEventListener('waiting', showLoading);
video.addEventListener('seeking', showLoading);

video.addEventListener('playing', hideLoading);
video.addEventListener('seeked', hideLoading);
video.addEventListener('canplay', hideLoading);

return () => {
  if (!video) return;
  video.removeEventListener('waiting', showLoading);
  video.removeEventListener('seeking', showLoading);
  video.removeEventListener('playing', hideLoading);
  video.removeEventListener('seeked', hideLoading);
  video.removeEventListener('canplay', hideLoading);
};

}, []);

useEffect(() => { const video = videoRef.current; if (!video || !attachment) return;

if (hlsRef.current) {
  hlsRef.current.destroy();
  hlsRef.current = null;
}

video.addEventListener('loadedmetadata', (a) => {
  return onSourceLoaded(video);
});
video.addEventListener('timeupdate', () => onTimeChange(video));
video.addEventListener('error', () => onError(new Error('Video error'), video));
if (attachment.type === 'live-stream') {
  if (video.canPlayType('application/vnd.apple.mpegurl')) {
    // Safari等原生支持HLS
    video.src = attachment.url!;
    video.poster = attachment.coverUrl!;
  } else if (Hls.isSupported()) {
    // 其他浏览器用hls.js
    video.poster = attachment.coverUrl!;
    const hls = new Hls();
    hlsRef.current = hls;
    // attachment.url!
    console.log(attachment.url);
    hls.loadSource(attachment.url!);
    hls.attachMedia(video);
    hls.on(Hls.Events.ERROR, (event, data) => {
      if (data.details === Hls.ErrorDetails.MANIFEST_LOAD_ERROR) {
        const corsError = new Error('CORS error: Check server Access-Control-Allow-Origin headers');
        onError(corsError, video);
      } else {
        onError(new Error(data?.details || 'HLS.js error'), video);
      }
    });
  } else {
    onError(new Error('HLS is not supported in this browser'), video);
  }
} else {
  video.src = attachment.url!;
  video.poster = attachment.coverUrl!;
}
return () => {
  if (hlsRef.current) {
    hlsRef.current.destroy();
    hlsRef.current = null;
  }
  if (video) {
    video.src = '';
  }
};

}, [attachment]);

return (