diff --git a/example/src/app.tsx b/example/src/app.tsx index e3bbf5d..657e2b6 100644 --- a/example/src/app.tsx +++ b/example/src/app.tsx @@ -1,4 +1,4 @@ -import { callBackType, JoLPlayerRef } from '@/interface'; +import { callBackType, JoLPlayerRef, qualityKey } from '@/interface'; import React, { useRef, useEffect, useState } from 'react'; import ReactDOM from 'react-dom'; import JoLPlayer from '../../src/index'; @@ -22,7 +22,7 @@ const AppCompent = () => { console.log(`onPlay`, val); }; const onTimeChange: callBackType = (val) => { - console.log(`onTimeChange`, val); + // console.log(`onTimeChange`, val); }; const onvolumechange: callBackType = (val) => { console.log(`onvolumechange`, val); @@ -30,9 +30,10 @@ const AppCompent = () => { const onError = () => { console.log(`onError`); }; - useEffect(() => { - console.log(`videoRef.current`, videoRef.current); - }, [videoRef.current]); + const onQualityChange: callBackType = (val) => { + console.log(`onQualityChange`, val); + }; + useEffect(() => {}, [videoRef.current]); const videoMethod = (status: string) => { if (status === 'play') { @@ -64,14 +65,33 @@ const AppCompent = () => { onTimeChange={onTimeChange} onvolumechange={onvolumechange} onError={onError} + onQualityChange={onQualityChange} option={{ - videoSrc: 'https://qiniu.qyhever.com/162962488432086ba29652658echrome.mp4', + videoSrc: + 'https://gs-files.oss-cn-hongkong.aliyuncs.com/okr/prod/file/2021/08/31/540p.mp4', width: 750, height: 420, theme, - poster: 'https://cdn.gudsen.com/2021/06/28/f81356b08b4842d7a3719499f557c8e4.JPG', + poster: + 'https://gs-files.oss-cn-hongkong.aliyuncs.com/okr/prod/file/2021/08/31/1080pp.png', language: 'en', isShowMultiple, + pausePlacement: 'center', + isShowPauseButton: false, + quality: [ + { + name: 'FHD', + url: 'https://gs-files.oss-cn-hongkong.aliyuncs.com/okr/prod/file/2021/08/31/720p.mp4', + }, + { + name: 'HD', + url: 'https://gs-files.oss-cn-hongkong.aliyuncs.com/okr/prod/file/2021/08/31/540p.mp4', + }, + { + name: 'SD', + url: 'https://gs-files.oss-accelerate.aliyuncs.com/okr/prod/file/2021/08/31/1630377480138360p.mp4', + }, + ], }} /> { width: 750, height: 420, poster: 'https://cdn.gudsen.com/2021/06/28/f81356b08b4842d7a3719499f557c8e4.JPG', + quality: [], }} /> diff --git a/package.json b/package.json index 13f8ebc..22b6592 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jol-player", - "version": "2.1.2", + "version": "2.2.0", "description": "简洁,美观,功能强大的react播放器,simple and beautiful, powerful react player", "main": "index.js", "scripts": { diff --git a/src/core/config/index.ts b/src/core/config/index.ts index 0e1f1c8..60f15d1 100644 --- a/src/core/config/index.ts +++ b/src/core/config/index.ts @@ -1,3 +1,5 @@ +import { qualityName, qualityKey } from '@/interface'; + export const multipleList = [ { id: 0.5, name: '0.5x' }, { id: 0.75, name: '0.75x' }, @@ -12,3 +14,16 @@ export const defaultTheme: string = '#ffb821'; export const defaultVolume: number = 60; export const defaultLanguage = 'zh'; + +export interface qualityListType { + key: qualityKey; + enName: qualityName; + name: string; + id: number; +} +export const qualityList: qualityListType[] = [ + { name: '标清', key: '360P', enName: 'SD', id: 1 }, + { name: '高清', key: '540P', enName: 'HD', id: 2 }, + { name: '超清', key: '720P', enName: 'FHD', id: 3 }, + { name: '蓝光', key: '1080P', enName: 'BD', id: 4 }, +]; diff --git a/src/core/context/index.ts b/src/core/context/index.ts index f1d9806..050d61d 100644 --- a/src/core/context/index.ts +++ b/src/core/context/index.ts @@ -1,5 +1,5 @@ import React, { useReducer } from 'react'; -import { videoOption } from '@/interface'; +import { videoOption, qualityKey } from '@/interface'; export interface VideoStateType { /** * @description 是否显示控件 @@ -13,6 +13,10 @@ export interface VideoStateType { * @description onProgressMouseUp事件的变化数据,用来区分是onProgressMouseUp事件触发的 */ progressMouseUpChangeVal: T; + /** + * @description 视频质量清晰度 + */ + quality: qualityKey | undefined; } /** * @description 永不变的数据 @@ -32,6 +36,7 @@ export const initialState = { isControl: false, progressSliderChangeVal: 0, progressMouseUpChangeVal: 0, + quality: undefined, }; export const defaultValue = { @@ -54,10 +59,15 @@ export interface progressMouseUpChangeValValActionType { type: 'progressMouseUpChangeVal'; data: VideoStateType['progressMouseUpChangeVal']; } +export interface qualityActionType { + type: 'quality'; + data: VideoStateType['quality']; +} export type mergeAction = | isControlActionType | progressSliderChangeValActionType - | progressMouseUpChangeValValActionType; + | progressMouseUpChangeValValActionType + | qualityActionType; export const useVideoFlow = () => { const reducer = (state: VideoStateType, action: mergeAction) => { @@ -68,6 +78,8 @@ export const useVideoFlow = () => { return { ...state, progressSliderChangeVal: action.data }; case 'progressMouseUpChangeVal': return { ...state, progressMouseUpChangeVal: action.data }; + case 'quality': + return { ...state, quality: action.data }; default: return state; } diff --git a/src/core/controller/index.tsx b/src/core/controller/index.tsx index d4e539d..4a532a4 100644 --- a/src/core/controller/index.tsx +++ b/src/core/controller/index.tsx @@ -6,6 +6,7 @@ import { FlowContext } from '@/core/context'; import { useVideo } from '@/core/useVideo'; import EndComponent from '@/components/end'; import Screenshot from '@/components/screenshot'; +import { filterDefaults } from '@/utils'; import './index.scss'; const Index = memo(function Index() { @@ -126,15 +127,18 @@ const Index = memo(function Index() { onMouseMove={mouseMove} onClick={handlePlay} > - {!isPlay && !isEndEd && ( - - )} + {filterDefaults(propsAttributes!.isShowPauseButton) + ? !isPlay && + !isEndEd && ( + + ) + : null}
[controlsContainerMove('move')]} diff --git a/src/core/controls/index.scss b/src/core/controls/index.scss index 0fa023a..03d62a0 100644 --- a/src/core/controls/index.scss +++ b/src/core/controls/index.scss @@ -28,6 +28,9 @@ .play-pause-timeline { display: flex; align-items: center; + .icon { + cursor: pointer; + } .time-wrap { font-size: 13px; vertical-align: middle; @@ -62,8 +65,14 @@ border-radius: 3px; background-color: rgba(0, 0, 0, 0.7); li { - padding: 12px 15px; + padding: 10px 22px; cursor: pointer; + font-size: 14px; + white-space: nowrap; + display: flex; + p { + padding: 0 3px; + } &:hover { background: rgba(255, 255, 255, 0.2); } diff --git a/src/core/controls/index.tsx b/src/core/controls/index.tsx index 1cd87c7..41ea36f 100644 --- a/src/core/controls/index.tsx +++ b/src/core/controls/index.tsx @@ -7,11 +7,12 @@ import { secondsToMinutesAndSecondes, capture, filterDefaults } from '@/utils'; import { useControls } from './variable'; import useWindowClient from '@/utils/useWindowClient'; import screenfull, { Screenfull } from 'screenfull'; -import { multipleList, defaultVolume, defaultLanguage } from '@/core/config'; +import { multipleList, defaultLanguage } from '@/core/config'; import SetComponent from './set'; import MultipleComponent from './multiple'; import VolumeComponent from './volume'; import MonitorComponent from './monitor'; +import QualityComponent, { qualityToggleType } from './quality'; import { il8n } from '@/language'; import './index.scss'; @@ -28,17 +29,24 @@ const Index: FC<{ setIsscreenshot: Function; setScreenshotLoading: Function }> = const reviceProps = useContext(FlowContext); - const { propsAttributes } = reviceProps!; + const { propsAttributes, dispatch: contentDispatch } = reviceProps!; const revicePropsData = useRef(null!); - const { isPlay, handleChangePlayState, currentTime, duration, isPictureinpicture, volume } = - useVideo( - { - videoElement: reviceProps.videoRef, - }, - [reviceProps.videoRef], - ); + const { + isPlay, + handleChangePlayState, + currentTime, + duration, + isPictureinpicture, + volume, + videoMethod, + } = useVideo( + { + videoElement: reviceProps.videoRef, + }, + [reviceProps.videoRef], + ); const { controlsState, dispatch } = useControls(); @@ -229,6 +237,15 @@ const Index: FC<{ setIsscreenshot: Function; setScreenshotLoading: Function }> = return controlsState.isScreentFull || controlsState.isWebPageFullScreen ? '13px' : '8px'; }, [controlsState.isWebPageFullScreen, controlsState.isScreentFull]); + /** + * @description 视频清晰度切换 + */ + const qualityToggle: qualityToggleType = (url, key) => { + contentDispatch!({ type: 'quality', data: key }); + videoMethod.setVideoSrc(url); + videoMethod.seek(currentTime); + videoMethod.play(); + }; return (
= totalTime={secondsToMinutesAndSecondes(duration)} />
+ {propsAttributes!.quality && propsAttributes!.quality.length ? ( + + ) : null} {filterDefaults(propsAttributes!.isShowMultiple) && ( = slideCurrentVolume={slideCurrentVolume} clearVolumeInterval={clearVolumeInterval} isMuted={controlsState.isMuted} - // toggleVolume={toggleVolume} toggleVolume={() => dispatch({ type: 'isMuted', data: !controlsState.isMuted })} /> {filterDefaults(propsAttributes!.isShowSet) && ( diff --git a/src/core/controls/monitor.tsx b/src/core/controls/monitor.tsx index 7dcea36..ca58e08 100644 --- a/src/core/controls/monitor.tsx +++ b/src/core/controls/monitor.tsx @@ -22,6 +22,7 @@ const Monitor: FC = memo(function Monitor({ fill="#fff" fontSize="20px" onClick={handleChangePlayState} + className="icon" /> {currentTime} diff --git a/src/core/controls/multiple.tsx b/src/core/controls/multiple.tsx index 7d461b2..0421b60 100644 --- a/src/core/controls/multiple.tsx +++ b/src/core/controls/multiple.tsx @@ -1,6 +1,5 @@ import React, { memo, FC, useState, useContext } from 'react'; -import { multipleList } from '@/core/config'; -import { defaultTheme } from '@/core/config'; +import { multipleList, defaultTheme } from '@/core/config'; import { FlowContext } from '@/core/context'; import './index.scss'; export interface MultipleType { diff --git a/src/core/controls/quality.tsx b/src/core/controls/quality.tsx new file mode 100644 index 0000000..02abe09 --- /dev/null +++ b/src/core/controls/quality.tsx @@ -0,0 +1,102 @@ +import React, { memo, FC, useState, useContext, useMemo } from 'react'; +import { defaultTheme, qualityList, qualityListType } from '@/core/config'; +import { FlowContext } from '@/core/context'; +import { qualityKey } from '@/interface'; +import './index.scss'; + +export type qualityToggleType = (par: string, key: qualityKey) => void; + +export interface QualityType { + style?: React.CSSProperties; + videoSrc?: string; + qualityToggle: qualityToggleType; +} + +export type qualityMerge = qualityListType & { url: string }; + +const Quality: FC = memo(function Quality({ style, videoSrc, qualityToggle }) { + const reviceProps = useContext(FlowContext); + + const { theme, quality, language } = reviceProps.propsAttributes!; + + const [isShow, setIsShow] = useState(false); + + const qualitySelectList = useMemo(() => { + if (quality) { + return quality + .map( + (item) => + qualityList + .map((items) => { + return item.name === items.enName && Object.assign({}, items, { url: item.url }); + }) + .filter((item) => item)[0], + ) + .sort((a, b) => (b && b.id ? b.id : 1) - (a && a.id ? a.id : 1)); + } + return []; + }, [quality]); + + const qualityText = useMemo>(() => { + let text: qualityMerge[] = []; + if (videoSrc && qualitySelectList) { + text = (qualitySelectList as qualityMerge[]).filter((item) => item.url === videoSrc); + } + return text.length ? text[0] : { key: '360P', enName: 'SD', name: '标清', id: 1 }; + }, [videoSrc, qualitySelectList]); + + const toggle = (url: string, key: qualityKey) => { + qualityToggle(url, key); + }; + return ( +
setIsShow((pre) => !pre)} + onMouseLeave={(e) => [setIsShow(false)]} + style={style} + > +

+ {qualityText + ? language && language === 'en' + ? qualityText.enName + : qualityText.name + : null} +

+
+ {qualitySelectList && qualitySelectList.length ? ( +
    + {qualitySelectList.map((item, index) => ( +
  • + toggle(item && item.url ? item.url : '', item && item.key ? item.key : '360P') + } + style={{ + color: + qualityText.key === (item && item.key ? item.key : null) + ? theme + ? theme + : defaultTheme + : '#fff', + }} + > +

    + {item && item.name + ? language && language === 'en' + ? item.enName + : item.name + : null} +

    +

    {item && item.key ? item.key : null}

    +
  • + ))} +
+ ) : null} +
+
+ ); +}); +export default Quality; diff --git a/src/core/index.scss b/src/core/index.scss index 0422642..f5e76d7 100644 --- a/src/core/index.scss +++ b/src/core/index.scss @@ -8,9 +8,12 @@ box-sizing: border-box; position: relative; z-index: 100; + // background-color: #000; .JoL-player { @include wh(100%, 100%); background-color: #000; + // 去除视频黑边 + object-fit: cover; @include position(absolute, 0, auto, auto, 0); z-index: 1; } diff --git a/src/core/index.tsx b/src/core/index.tsx index 30404ee..d5251c8 100644 --- a/src/core/index.tsx +++ b/src/core/index.tsx @@ -30,6 +30,7 @@ const JoLPlayer = function JoLPlayer(props: videoparameter, ref: React.Ref { @@ -152,7 +154,13 @@ const JoLPlayer = function JoLPlayer(props: videoparameter, ref: React.Ref
-