master
lgf 3 years ago
parent 872af4b6d7
commit d421bb6559
  1. 1
      src/appComponent.tsx
  2. 36
      src/components/screenshot/index.module.scss
  3. 23
      src/components/screenshot/index.tsx
  4. 114
      src/core/controller/index.tsx
  5. 47
      src/core/controls/index.tsx
  6. 9
      src/core/index.tsx
  7. 2
      src/core/progress/index.tsx
  8. 1
      src/icons/svg/close.svg
  9. 24
      src/interface/index.ts
  10. 11
      src/utils/index.ts

@ -79,6 +79,7 @@ const AppComponent = memo(function AppComponent(props) {
width: 750, width: 750,
height: 420, height: 420,
theme: '#00D3FF', theme: '#00D3FF',
isShowMultiple: false,
poster: 'https://cdn.gudsen.com/2021/06/28/f81356b08b4842d7a3719499f557c8e4.JPG', poster: 'https://cdn.gudsen.com/2021/06/28/f81356b08b4842d7a3719499f557c8e4.JPG',
}} }}
onProgressMouseDown={onProgressMouseDown} onProgressMouseDown={onProgressMouseDown}

@ -0,0 +1,36 @@
@import 'src/assets/css/mixin.scss';
.screenshot {
width: 280px;
@include position(absolute, auto, 0, 55px, auto);
background: #fff;
padding: 3px;
overflow: hidden;
.img {
height: 155px;
overflow: hidden;
position: relative;
canvas {
@include position(absolute, 50%, auto, auto, 50%);
transform: translate(-50%, -50%);
}
}
.close {
@include position(absolute, -24px, -24px, auto, auto);
@include wh(0, 0);
z-index: 1;
border: 26px solid transparent;
border-left-color: #fff;
-webkit-transform: rotate(-45deg);
transform: rotate(-45deg);
border-radius: 100%;
.icon {
@include position(absolute, -8px, 5px, auto, auto);
transform: rotate(45deg);
cursor: pointer;
}
}
.save {
padding: 10px;
text-align: center;
}
}

@ -0,0 +1,23 @@
import React, { memo, FC } from 'react';
import style from './index.module.scss';
import Broadcast from '@/components/svgIcon';
const Index: FC<{
setIsscreenshot: Function;
screenshotLoading: boolean;
}> = memo(function Index({ setIsscreenshot, screenshotLoading }) {
return (
<div className={style.screenshot}>
<div className={style.close} onClick={() => setIsscreenshot(false)}>
<Broadcast iconClass="close" fill="red" className={style.icon} />
</div>
<div className={style.img} id="JoL-screenshotCanvas">
<Broadcast iconClass="loading" className="player-loading" fontSize="55px" />
</div>
<p className={style.save}>
{screenshotLoading ? '截图加载失败,在点击下' : '鼠标右键图片另存为'}
</p>
</div>
);
});
export default Index;

@ -1,17 +1,14 @@
import React, { memo, useContext, useRef, useState, useMemo } from 'react'; import React, { memo, useContext, useRef, useMemo, useEffect, useState } from 'react';
import Broadcast from '@/components/svgIcon'; import Broadcast from '@/components/svgIcon';
import Progress from '../progress'; import Progress from '../progress';
import Controls from '../controls'; import Controls from '../controls';
import { FlowContext } from '@/core/context'; import { FlowContext } from '@/core/context';
import { useVideo } from '@/core/useVideo'; import { useVideo } from '@/core/useVideo';
import useWindowClient from '@/utils/useWindowClient';
import usePrevious from '@/utils/usePrevious';
import EndComponent from '@/components/end'; import EndComponent from '@/components/end';
import Screenshot from '@/components/screenshot';
import './index.scss'; import './index.scss';
const Index = memo(function Index(props) { const Index = memo(function Index() {
const { clientX } = useWindowClient();
const reviceProps = useContext(FlowContext); const reviceProps = useContext(FlowContext);
const { dispatch, propsAttributes } = reviceProps; const { dispatch, propsAttributes } = reviceProps;
@ -25,57 +22,72 @@ const Index = memo(function Index(props) {
const timer = useRef<NodeJS.Timeout | null>(null!); const timer = useRef<NodeJS.Timeout | null>(null!);
const viewClientX = useRef<number>(null!);
const prevCalculation = useRef<number>(null!);
const controllerRef = useRef<HTMLDivElement>(null!); const controllerRef = useRef<HTMLDivElement>(null!);
const [pre, setPre] = useState<number>(0);
viewClientX.current = clientX;
/** /**
* @description * @description
*/ */
prevCalculation.current = usePrevious(pre) as number; const userActivity = useRef<boolean>(false);
const inactivityTimeout = useRef<NodeJS.Timeout | null>(null!);
/** /**
* @description * @description
*/
const showControl = (e: any, status: string) => {
dispatch!({ type: 'isControl', data: status === 'enter' && !isEndEd ? true : false });
};
/**
* @description
*/ */
const hiddleCursor = () => { const isControlsContainerMove = useRef<boolean>(false);
if (timer.current) {
clearInterval(timer.current); const [isScreenshot, setIsscreenshot] = useState<boolean>(false);
}
const [screenshotLoading, setScreenshotLoading] = useState<boolean>(false);
useEffect(() => {
timer.current = setInterval(() => { timer.current = setInterval(() => {
setPre(viewClientX.current); if (userActivity.current) {
/** /**
* @description 1200ms没有任何操作的话 * @description
*/ */
if (viewClientX.current! !== prevCalculation.current) { userActivity.current = false;
dispatch!({ type: 'isControl', data: true }); dispatch!({ type: 'isControl', data: true });
controllerRef.current.style.cursor = 'pointer'; controllerRef.current.style.cursor = 'pointer';
} else { inactivityTimeout.current && clearTimeout(inactivityTimeout.current);
inactivityTimeout.current = setTimeout(
() => {
/**
* @note Controls上时
*/
if (!userActivity.current && !isControlsContainerMove.current) {
dispatch!({ type: 'isControl', data: false }); dispatch!({ type: 'isControl', data: false });
controllerRef.current.style.cursor = 'none'; controllerRef.current.style.cursor = 'none';
} }
}, 1200); },
}; propsAttributes!.hideMouseTime ? propsAttributes!.hideMouseTime : 2000,
const clearTimer = () => { );
controllerRef.current.style.cursor = 'pointer';
if (timer.current) {
clearInterval(timer.current);
} }
}, 200);
return () => {
timer.current && clearInterval(timer.current);
};
}, []);
/**
* @description
*/
const showControl = (status: string) => {
dispatch!({ type: 'isControl', data: status === 'enter' && !isEndEd ? true : false });
}; };
const handlePlay = () => { const handlePlay = () => {
handleChangePlayState && handleChangePlayState(); handleChangePlayState && handleChangePlayState();
}; };
const mouseMove: React.MouseEventHandler<HTMLDivElement> = (e) => {
userActivity.current = true;
/**
* @note false,falsebug
*/
isControlsContainerMove.current = false;
};
const leaveMove: React.MouseEventHandler<HTMLDivElement> = (e) => {
userActivity.current = false;
controllerRef.current.style.cursor = 'pointer';
};
/** /**
* @description * @description
*/ */
@ -93,18 +105,25 @@ const Index = memo(function Index(props) {
}; };
} }
}, [propsAttributes!.pausePlacement]); }, [propsAttributes!.pausePlacement]);
/**
* @param status
* @description
*/
const controlsContainerMove = (status: string) => {
isControlsContainerMove.current = status === 'move' ? true : false;
};
return ( return (
<div <div
className="JoL-controller-container" className="JoL-controller-container"
onMouseEnter={(e) => showControl(e, 'enter')} onMouseEnter={(e) => [showControl('enter')]}
onMouseLeave={(e) => showControl(e, 'leave')} onMouseLeave={(e) => [showControl('leave'), e.stopPropagation()]}
ref={controllerRef} ref={controllerRef}
> >
<div <div
id="play-or-pause-mask" id="play-or-pause-mask"
className="JoL-click-to-play-or-pause" className="JoL-click-to-play-or-pause"
onMouseEnter={hiddleCursor} onMouseLeave={leaveMove}
onMouseLeave={clearTimer} onMouseMove={mouseMove}
onClick={handlePlay} onClick={handlePlay}
></div> ></div>
{!isPlay && !isEndEd && ( {!isPlay && !isEndEd && (
@ -116,15 +135,22 @@ const Index = memo(function Index(props) {
style={pausePosition} style={pausePosition}
/> />
)} )}
<div className="JoL-progress-and-controls-wrap"> <div
className="JoL-progress-and-controls-wrap"
onMouseMove={(e) => [controlsContainerMove('move')]}
onMouseLeave={(e) => [controlsContainerMove('leave')]}
>
{isScreenshot ? (
<Screenshot setIsscreenshot={setIsscreenshot} screenshotLoading={screenshotLoading} />
) : null}
<Progress /> <Progress />
<Controls /> <Controls setIsscreenshot={setIsscreenshot} setScreenshotLoading={setScreenshotLoading} />
</div> </div>
{isEndEd ? ( {isEndEd ? (
propsAttributes!.setEndPlayContent ? ( propsAttributes!.setEndPlayContent ? (
propsAttributes!.setEndPlayContent propsAttributes!.setEndPlayContent
) : ( ) : (
<EndComponent handle={() => [handleChangePlayState(), hiddleCursor()]} /> <EndComponent handle={() => [handleChangePlayState(), showControl('enter')]} />
) )
) : null} ) : null}
</div> </div>

@ -1,9 +1,9 @@
import React, { memo, useContext, useRef, useEffect, useMemo, useCallback } from 'react'; import React, { memo, useContext, useRef, useEffect, useMemo, FC } from 'react';
import Broadcast from '@/components/svgIcon'; import Broadcast from '@/components/svgIcon';
import Tooltip from '@/components/tooltip'; import Tooltip from '@/components/tooltip';
import { FlowContext } from '@/core/context'; import { contextType, FlowContext } from '@/core/context';
import { useVideo } from '@/core/useVideo'; import { useVideo } from '@/core/useVideo';
import { secondsToMinutesAndSecondes, createALabel } from '@/utils'; import { secondsToMinutesAndSecondes, capture } from '@/utils';
import { useControls } from './variable'; import { useControls } from './variable';
import useWindowClient from '@/utils/useWindowClient'; import useWindowClient from '@/utils/useWindowClient';
import screenfull, { Screenfull } from 'screenfull'; import screenfull, { Screenfull } from 'screenfull';
@ -14,7 +14,8 @@ import VolumeComponent from './volume';
import MonitorComponent from './monitor'; import MonitorComponent from './monitor';
import './index.scss'; import './index.scss';
const Index = memo(function Index(props) { const Index: FC<{ setIsscreenshot: Function; setScreenshotLoading: Function }> = memo(
function Index({ setIsscreenshot, setScreenshotLoading }) {
/** /**
* @description * @description
*/ */
@ -26,7 +27,9 @@ const Index = memo(function Index(props) {
const reviceProps = useContext(FlowContext); const reviceProps = useContext(FlowContext);
const revicePropsData = useRef<any>(); const { propsAttributes } = reviceProps!;
const revicePropsData = useRef<contextType>();
const { isPlay, handleChangePlayState, currentTime, duration, isPictureinpicture, volume } = const { isPlay, handleChangePlayState, currentTime, duration, isPictureinpicture, volume } =
useVideo( useVideo(
@ -167,28 +170,30 @@ const Index = memo(function Index(props) {
/** /**
* @description 线 * @description 线
*/ */
const screenshot = () => { const screenshot = async () => {
const canvas = document.createElement('canvas') as HTMLCanvasElement; const output = document.querySelector('#JoL-screenshotCanvas')!;
canvas.width = reviceProps.videoRef!.offsetWidth; const canvas = capture(reviceProps.videoRef!, 0.45);
canvas.height = reviceProps.videoRef!.offsetHeight; setIsscreenshot(true);
const context = canvas.getContext('2d')!; if (output) {
context.drawImage(reviceProps.videoRef!, 0, 0, canvas.width, canvas.height); setScreenshotLoading(false);
try { output.innerHTML = '';
createALabel(canvas.toDataURL('image/png')); output.appendChild(canvas);
} catch (error) {} } else {
setScreenshotLoading(true);
}
}; };
/** /**
* @description * @description
*/ */
const switchChange = (e: string, flag: string) => { const switchChange = (e: string, flag: string) => {
const { videoRef, lightOffMaskRef } = revicePropsData.current; const { videoRef, lightOffMaskRef } = revicePropsData.current!;
if (flag === 'lights') { if (flag === 'lights') {
if (lightOffMaskRef) { if (lightOffMaskRef) {
lightOffMaskRef.style.display = e === 'yes' ? 'block' : 'none'; lightOffMaskRef.style.display = e === 'yes' ? 'block' : 'none';
} }
} else { } else {
const loop = videoRef.loop; const loop = videoRef!.loop;
videoRef.loop = loop ? false : true; videoRef!.loop = loop ? false : true;
} }
}; };
/** /**
@ -216,7 +221,7 @@ const Index = memo(function Index(props) {
return ( return (
<div <div
className="JoL-controls-container" className="JoL-controls-container"
style={{ opacity: reviceProps.videoFlow!.isControl ? '1' : '0' }} // style={{ opacity: reviceProps.videoFlow!.isControl ? '1' : '0' }}
> >
<MonitorComponent <MonitorComponent
isPlay={isPlay} isPlay={isPlay}
@ -225,11 +230,14 @@ const Index = memo(function Index(props) {
totalTime={secondsToMinutesAndSecondes(duration)} totalTime={secondsToMinutesAndSecondes(duration)}
/> />
<div className="JoL-multifunction"> <div className="JoL-multifunction">
{propsAttributes!.isShowMultiple && (
<MultipleComponent <MultipleComponent
multipleText={multipleText} multipleText={multipleText}
multiple={controlsState.multiple} multiple={controlsState.multiple}
selectPlayRate={selectPlayRate} selectPlayRate={selectPlayRate}
/> />
)}
<VolumeComponent <VolumeComponent
ref={volumeSliderMirror} ref={volumeSliderMirror}
volume={controlsState.volume} volume={controlsState.volume}
@ -294,6 +302,7 @@ const Index = memo(function Index(props) {
</div> </div>
</div> </div>
); );
}); },
);
export default Index; export default Index;

@ -175,5 +175,12 @@ const JoLPlayer = function JoLPlayer(props: videoparameter, ref: React.Ref<unkno
}; };
const JoLPlayerComponent = forwardRef<JoLPlayerRef, videoparameter>(JoLPlayer); const JoLPlayerComponent = forwardRef<JoLPlayerRef, videoparameter>(JoLPlayer);
JoLPlayerComponent.defaultProps = {
option: {
width: 750,
height: 420,
videoSrc: 'https://cdn.gudsen.com/2021/06/28/f81356b08b4842d7a3719499f557c8e4.JPG',
isShowMultiple: true,
},
};
export default JoLPlayerComponent; export default JoLPlayerComponent;

@ -172,7 +172,7 @@ const Index = memo(function Index(props) {
return ( return (
<div <div
className="JoL-progress-container" className="JoL-progress-container"
style={{ opacity: reviceProps.videoFlow!.isControl ? '1' : '0' }} // style={{ opacity: reviceProps.videoFlow!.isControl ? '1' : '0' }}
> >
<div className="progress-bg" ref={progressBgRef}> <div className="progress-bg" ref={progressBgRef}>
<div className="progress-buffered" style={{ width: `${calculateBufferedPercent}%` }}></div> <div className="progress-buffered" style={{ width: `${calculateBufferedPercent}%` }}></div>

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1629344784407" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6951" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M783.36 195.2L512 466.56 240.64 195.2a32 32 0 0 0-45.44 45.44L466.56 512l-271.36 271.36a32 32 0 0 0 45.44 45.44L512 557.44l271.36 271.36a32 32 0 0 0 45.44-45.44L557.44 512l271.36-271.36a32 32 0 0 0-45.44-45.44z" p-id="6952"></path></svg>

After

Width:  |  Height:  |  Size: 614 B

@ -40,6 +40,30 @@ export interface videoOption<T = string, K = boolean, U = number> {
* @description * @description
*/ */
pausePlacement?: pausePlacement; pausePlacement?: pausePlacement;
/**
* @description /ms
*/
hideMouseTime?: U;
/**
* @description
*/
isShowMultiple?: K;
/**
* @description
*/
isShowSet?: K;
/**
* @description
*/
isShowScreenshot?: K;
/**
* @description
*/
isShowPicture?: K;
/**
* @description
*/
isShowWebFullScreen?: K;
} }
export interface videoAttributes<T = number, K = boolean> { export interface videoAttributes<T = number, K = boolean> {
/** /**

@ -37,3 +37,14 @@ export const createALabel = (path: string, fileName: string = 'JoL-player.png')
link.remove(); link.remove();
window.URL.revokeObjectURL(path); window.URL.revokeObjectURL(path);
}; };
export const capture = (video: HTMLVideoElement, scaleFactor: number = 0.25) => {
var w = video.videoWidth * scaleFactor;
var h = video.videoHeight * scaleFactor;
var canvas = document.createElement('canvas') as HTMLCanvasElement;
canvas.width = w;
canvas.height = h;
var ctx = canvas.getContext('2d');
ctx!.drawImage(video, 0, 0, w, h);
return canvas;
};

Loading…
Cancel
Save