针对QQ浏览器video标签不可控实现的React组件

8 篇文章 0 订阅
2 篇文章 0 订阅

一个简单的视频播放控件,会在QQ浏览器上使用canvas进行视频播放

import {
  CSSProperties,
  ReactNode,
  Ref,
  forwardRef,
  useCallback,
  useEffect,
  useImperativeHandle,
  useRef,
  useState,
} from "react";
import videojs from "video.js";
import { fitSize } from "../theme";

interface SimpleVideoProps {
  uri: string;
  isPaused?: boolean;
  muted?: boolean;
  onVideoEnd?: () => void;
  loop?: boolean;
  poster?: string | undefined;
  style?: CSSProperties | undefined;
  w?: number | string | undefined;
  h?: number | string | undefined;
  children?: ReactNode;
  isReset?: boolean | undefined;
  resizeMode?: "cover" | "contain" | "stretch" | undefined;
}

interface SimpleVideoPropsRef {
  toggleVideo?: () => void;
  sonVideoPalyState?: boolean;
}

export const isQQ =
  navigator.userAgent.indexOf("MQQBrowser") > -1 ||
  navigator.userAgent.indexOf("QQTheme") > -1;

export const WebVideoCompatibility = forwardRef(
  (props: SimpleVideoProps, ref: Ref<SimpleVideoPropsRef>) => {
    return isQQ ? (
      <CanvasVideo {...props} ref={ref} />
    ) : (
      <WebVideo {...props} ref={ref} />
    );
  }
);
export const WebVideo = forwardRef(
  (props: SimpleVideoProps, ref: Ref<SimpleVideoPropsRef>) => {
    const {
      uri,
      isPaused = true,
      muted = true,
      loop = false,
      poster,
      children,
      isReset = true,
      w = "100%",
      h = "100%",
      style,
      onVideoEnd,
    } = props;
    const videoRef = useRef<HTMLVideoElement | null>(null);
    const playerRef = useRef(null);
    const [isPlaying, setIsPlaying] = useState(!isPaused);

    const loadVideo = useCallback(async () => {
      if (!uri) return;
      try {
        const videoElement = videoRef.current;
        const player = videojs(videoElement, {
          controls: false,
          poster: poster ?? "",
          autoplay: false,
          muted: muted,
          loop,
          playsinline: true,
          preload: "auto",
          controlsList: "nodownload nofullscreen noremoteplayback",
          html5: {
            hls: {
              enableLowInitialPlaylist: true,
              smoothQualityChange: true,
            },
          },
          plugins: {},
          errorDisplay: false,
        });
        player.src(encodeURI(uri));
        playerRef.current = player;
        player.on("ended", () => {
          onVideoEnd();
        });
      } catch (error) {
        console.error("视频加载失败:", error);
        return null;
      }
    }, [loop, muted, onVideoEnd, poster, uri]);

    useEffect(() => {
      if (!!uri) {
        loadVideo();
      }
      return () => {
        if (playerRef.current) {
          playerRef.current.dispose();
        }
      };
    }, [uri, loop, poster, loadVideo]);

    const videoState = useCallback(() => {
      const player = playerRef.current;
      if (player) {
        if (isPaused) {
          player.pause();
          if (isReset) player.currentTime(0);
        } else {
          player.play().catch((error) => {
            console.error("Error playing video:", error);
          });
        }
      }
    }, [isPaused, isReset]);

    useEffect(() => {
      if (!!uri) {
        videoState();
      }
    }, [isPaused, uri, videoState]);

    useImperativeHandle(ref, () => ({
      toggleVideo: () => {
        if (playerRef.current) {
          if (playerRef.current.paused()) {
            playerRef.current.play();
            setIsPlaying(true);
          } else {
            playerRef.current.pause();
            setIsPlaying(false);
          }
        }
      },
      sonVideoPalyState: isPlaying,
    }));

    useEffect(() => {
      const player = playerRef.current;
      if (player) {
        player.muted(muted);
      }
    }, [muted]);

    const container: CSSProperties = {
      width: w ?? "100%",
      height: h ?? "100%",
      backgroundColor: "transparent",
      display: "flex",
      alignItems: "center",
      justifyContent: "center",
      ...style,
    };

    const content: CSSProperties = {
      width: "100%",
      height: fitSize(442),
      display: "flex",
      alignItems: "center",
      justifyContent: "center",
    };

    const videoStyle: CSSProperties = {
      objectFit: "cover",
      width: "100%",
      height: "100%",
    };

    return (
      <div style={container}>
        <div style={content}>
          <video
            className="video-js"
            controls={false}
            x5-video-player-type="h5-page"
            muted={muted}
            style={videoStyle}
            ref={videoRef}
            onPause={() => {
              setIsPlaying(false);
            }}
            id="capsule"
          />
        </div>
        {children}
      </div>
    );
  }
);

export const CanvasVideo = forwardRef(
  (props: SimpleVideoProps, ref: Ref<SimpleVideoPropsRef>) => {
    const {
      uri,
      isPaused = true,
      loop = false,
      style,
      children,
      isReset,
      w = "100%",
      h = "100%",
      muted,
      onVideoEnd,
    } = props;
    const canvasRef = useRef(null);
    const videoRef = useRef(null);

    useEffect(() => {
      const canvas = canvasRef.current;
      const context = canvas.getContext("2d");
      const video = videoRef.current;
      video.addEventListener("play", () => {
        const drawFrame = () => {
          const videoWidth = video.videoWidth;
          const videoHeight = video.videoHeight;
          const canvasWidth = canvas.width;
          const canvasHeight = canvas.height;

          const scaleX = canvasWidth / videoWidth;
          const scaleY = canvasHeight / videoHeight;
          const scale = Math.max(scaleX, scaleY);

          const drawWidth = videoWidth * scale;
          const drawHeight = videoHeight * scale;

          const xOffset = (drawWidth - canvasWidth) / 2;
          const yOffset = (drawHeight - canvasHeight) / 2;

          context.drawImage(video, -xOffset, -yOffset, drawWidth, drawHeight);
          requestAnimationFrame(drawFrame);
        };

        drawFrame();
      });

      return () => {
        video.removeEventListener("play", () => {
          console.log("清理");
        });
      };
    }, []);

    useEffect(() => {
      const video = videoRef.current;
      if (video) {
        if (isPaused) {
          video.pause();
          if (isReset) {
            video.currentTime = 0;
          }
        } else {
          video.play();
        }
      }
    }, [isPaused, isReset]);

    useImperativeHandle(ref, () => ({
      toggleVideo: () => {
        const video = videoRef.current;
        if (video) {
          if (video.paused) {
            video.play();
          } else {
            video.pause();
          }
        }
      },
    }));

    const absoluteStyle: CSSProperties = children
      ? { position: "absolute", top: 0, left: 0 }
      : {};

    const container: CSSProperties = {
      width: "100%",
      height: "100%",
      backgroundColor: "transparent",
      display: "flex",
      alignItems: "center",
      justifyContent: "center",
      ...style,
    };

    const videoStyle = { display: "none" };

    const handleVideoEnded = useCallback(() => {
      onVideoEnd();
    }, [onVideoEnd]);

    useEffect(() => {
      const video = videoRef.current;
      if (video) {
        video.addEventListener("ended", handleVideoEnded);
        return () => {
          video.removeEventListener("ended", handleVideoEnded);
        };
      }
    }, [handleVideoEnded]);

    return (
      <div style={container}>
        <video
          src={uri}
          style={videoStyle}
          ref={videoRef}
          muted={muted}
          loop={loop}
          controls={false}
        />
        <canvas ref={canvasRef} width={w} height={h} style={absoluteStyle} />
        {children}
      </div>
    );
  }
);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现React制打印机打印指定内容,可以使用以下步骤: 1. 创建一个打印组件React 中创建一个组件,用于显示要打印的内容。这个组件需要包含所有需要打印的元素。 2. 添加打印按钮 在页面中添加一个按钮,当用户点击它时触发打印操作。可以在按钮的 onClick 事件中调用打印函数。 3. 编写打印函数 在打印函数中,首先获取要打印的元素,然后使用 window.print() 函数触发浏览器的打印功能。 以下是代码示例: ```jsx import React, { useRef } from 'react'; function PrintComponent() { const printRef = useRef(); function handlePrint() { const content = printRef.current.innerHTML; const printWindow = window.open('', '', 'width=800,height=600'); printWindow.document.write(content); printWindow.print(); printWindow.close(); } return ( <div> <div ref={printRef}>这里是要打印的内容</div> <button onClick={handlePrint}>打印</button> </div> ); } export default PrintComponent; ``` 在上面的示例代码中,我们创建了一个名为 PrintComponent 的组件,其中包含一个 div 元素用于显示要打印的内容。我们使用 useRef 钩子来获取这个 div 元素的引用。 在 handlePrint 函数中,我们首先使用 useRef 获取要打印的元素的引用,然后使用 window.open 函数打开一个新的窗口。接着,我们将要打印的内容写入新窗口的文档中,并使用 print 函数触发浏览器的打印功能。最后,我们关闭新窗口。 在组件中添加一个按钮,当用户点击它时触发 handlePrint 函数。这样,用户就可以通过这个按钮来制打印机打印指定内容了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值