feat: 播放期

This commit is contained in:
mozzie 2023-03-02 11:30:38 +08:00
parent f8be92ddde
commit 57ff9c0356
7 changed files with 207 additions and 106 deletions

View File

@ -10,7 +10,5 @@
<body> <body>
<div id="bs-app"></div> <div id="bs-app"></div>
<script type="module" src="/src/main.tsx"></script> <script type="module" src="/src/main.tsx"></script>
<script src="/player/libs/hls.min.0.13.2m.js"></script>
<script src="/player/tcplayer.v4.7.2.min.js"></script>
</body> </body>
</html> </html>

View File

@ -60,3 +60,16 @@ input {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.bs-scrollbar {
&::-webkit-scrollbar {
width: 12px;
height: 4px;
}
&::-webkit-scrollbar-thumb {
border: 4px solid transparent;
background-clip: padding-box;
border-radius: 7px;
background-color: var(--color-text-4);
}
}

View File

@ -46,7 +46,7 @@
} }
.video-js { .video-js {
box-shadow: 0 0 100px #c8c8c8 !important; // box-shadow: 0 0 100px #c8c8c8 !important;
} }
.vjs-control-bar { .vjs-control-bar {

View File

@ -1,5 +1,5 @@
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { useLink, useMount, useScript } from "../../hook"; import { useScript } from "../../hook";
import "./index.less"; import "./index.less";
export interface IVideo { export interface IVideo {
@ -10,70 +10,54 @@ export interface IVideo {
} }
interface IProps { interface IProps {
video?: IVideo | null; video: IVideo | null;
} }
/** /**
* demo页面https://tcplayer.vcube.tencent.com/ * demo页面https://tcplayer.vcube.tencent.com/
* *
* Chrome Firefox H5 Webrtc , tcplayer.vx.x.x.min.js TXLivePlayer-x.x.x.min.js * 1. fill填满变形cover等比例会裁剪, contain等比例有黑边
* Webrtc Webrtc HLS hls.min.x.xx.xm.js * 2. TCPlayer('container', video)video为空
* <video objectFill /> fill填满变形cover等比例会裁剪, contain等比例有黑边
*/ */
function Player(props: IProps) { function Player(props: IProps) {
const playerRef = useRef<any>(); const playerRef = useRef<any>();
const [videoInfo, setVideoInfo] = useState<IVideo>({ const [libReady, setLibReady] = useState(false);
fileID: "",
appID: "",
});
const videoRef = useRef<HTMLVideoElement | null>(null);
console.log("执行一次"); useScript(
["/player/libs/hls.min.0.13.2m.js", "/player/tcplayer.v4.7.2.min.js"],
const initPlayer = (video: IVideo) => { () => {
const TCPlayer = (window as any).TCPlayer; console.log("[tcplayer] libs ready...");
setLibReady(true);
// 重写播放按钮 }
const Button = TCPlayer.getComponent("Button"); );
const BigPlayButton = TCPlayer.getComponent("BigPlayButton");
BigPlayButton.prototype.createEl = function () {
const el = Button.prototype.createEl.call(this);
const _html =
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1024 1024"><path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448s448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372s372 166.6 372 372s-166.6 372-372 372z" fill-opacity=".8" fill="currentColor"></path><path d="M512 140c-205.4 0-372 166.6-372 372s166.6 372 372 372s372-166.6 372-372s-166.6-372-372-372zm164.1 378.2L457.7 677.1a8.02 8.02 0 0 1-12.7-6.5V353a8 8 0 0 1 12.7-6.5l218.4 158.8a7.9 7.9 0 0 1 0 12.9z" fill-opacity=".1" fill="currentColor"></path><path d="M676.1 505.3L457.7 346.5A8 8 0 0 0 445 353v317.6a8.02 8.02 0 0 0 12.7 6.5l218.4-158.9a7.9 7.9 0 0 0 0-12.9z" fill-opacity=".8" fill="currentColor"></path></svg>';
el.appendChild(
TCPlayer.dom.createEl("div", {
className: "vjs-button-icon-custom",
innerHTML: _html,
})
);
return el;
};
playerRef.current = TCPlayer("player", video);
};
// useLink(["/player/tcplayer_pure.min.css"]);
// useScript(
// ["/player/libs/hls.min.0.13.2m.js", "/player/tcplayer.v4.7.2.min.js"],
// () => {}
// );
useEffect(() => { useEffect(() => {
if (props.video) setVideoInfo(props.video); if (libReady) {
}, [props.video]); const TCPlayer = (window as any).TCPlayer;
playerRef.current = TCPlayer("player", {
fileID: "243791579995468466",
appID: "1500018521",
plugins: {
ContinuePlay: {
auto: true,
},
},
});
console.log("[tcplayer] init success...");
}
}, [libReady]);
// useEffect(() => { useEffect(() => {
// if (videoInfo.fileID !== "") { if (props.video) {
// const TCPlayer = (window as any).TCPlayer; console.log("[tcplayer] video change", props.video);
// TCPlayer(`video-${videoInfo.fileID}`, videoInfo); playerRef.current.loadVideoByID(props.video);
// } }
// }, [videoInfo]); }, [props.video]);
return ( return (
<video <video
onContextMenu={(e) => e.preventDefault()} onContextMenu={(e) => e.preventDefault()}
id={`video-${videoInfo.fileID}`} id="player"
ref={videoRef}
style={{ width: "100%", height: "100%", objectFit: "contain" }} style={{ width: "100%", height: "100%", objectFit: "contain" }}
preload="auto" preload="auto"
playsInline playsInline

View File

@ -46,7 +46,7 @@ function Timeline(props: IProps) {
const [timelineData, setTimelineData] = useState(filterYearOnce(props.data)); const [timelineData, setTimelineData] = useState(filterYearOnce(props.data));
const cursorActiveRef = useRef<HTMLDivElement | null>(null); const cursorActiveRef = useRef<HTMLDivElement | null>(null);
const cursorStaticRef = useRef<HTMLDivElement | null>(null); const cursorStaticRef = useRef<HTMLDivElement | null>(null);
const orbitRef = useRef<HTMLDivElement | null>(null); const orbitRef = useRef<HTMLDivElement>();
/** /**
* cursorStatic * cursorStatic
@ -73,28 +73,28 @@ function Timeline(props: IProps) {
}, [props.model]); }, [props.model]);
useEffect(() => { useEffect(() => {
if (props.onScroll) if (props.onScroll && orbitRef.current)
props.onScroll({ props.onScroll({
top: cursorStatic.top, top: cursorStatic.top,
height: orbitRef.current!.clientHeight, height: orbitRef.current.clientHeight,
}); });
}, [cursorStatic.top]); }, [cursorStatic.top]);
useEffect(() => { useEffect(() => {
if (props.data) { if (props.data && orbitRef.current) {
setTimelineData(filterYearOnce(props.data)); setTimelineData(filterYearOnce(props.data));
// 全部的月份数数量平均划分 // 全部的月份数数量平均划分
const avg = orbitRef.current!.clientHeight / props.data.length; const avg = orbitRef.current.clientHeight / props.data.length;
setIntervalPixel(avg); setIntervalPixel(avg);
} }
}, [props.data]); }, [props.data]);
useEffect(() => { useEffect(() => {
window.addEventListener("resize", () => { window.addEventListener("resize", () => {
if (props.data) { if (props.data && orbitRef.current) {
setTimelineData(filterYearOnce(props.data)); setTimelineData(filterYearOnce(props.data));
// 全部的月份数数量平均划分 // 全部的月份数数量平均划分
const avg = orbitRef.current!.clientHeight / props.data.length; const avg = orbitRef.current.clientHeight / props.data.length;
setIntervalPixel(avg); setIntervalPixel(avg);
} }
}); });

View File

@ -1,14 +1,11 @@
.course-detail { .course-detail {
padding-top: 60px; padding-top: 60px;
display: grid;
grid-template-columns: 2fr 1fr;
article { article {
padding: 20px 40px 0 0; padding: 20px 40px 0 0;
.player-container { .player-container {
position: relative; position: relative;
height: 360px; height: 360px;
box-shadow: 0 4px 10px rgb(var(--gray-2));
&.float { &.float {
position: fixed !important; position: fixed !important;
left: 0; left: 0;
@ -19,19 +16,13 @@
z-index: 20; z-index: 20;
} }
} }
.title {
padding-bottom: 20px;
margin-top: 40px;
color: var(--color-text-2);
font-weight: 500;
line-height: 1.5;
font-size: 22px;
}
} }
aside { aside {
padding-left: 20px; padding-left: 20px;
border-left: 2px solid var(--color-border-1); > h2 {
margin-bottom: 10px;
}
.toc { .toc {
.level-1 { .level-1 {
@ -40,10 +31,10 @@
} }
.level-2 { .level-2 {
display: grid; display: grid;
grid-template-columns: 3fr 1fr 1fr; grid-template-columns: 9fr 1fr;
color: var(--color-text-2); color: var(--color-text-2);
.time, .time {
.get { color: var(--color-text-4);
text-align: right; text-align: right;
} }
} }

View File

@ -1,6 +1,8 @@
import { useState } from "react"; import { useState } from "react";
import Player, { IVideo } from "../../components/Player"; import Player, { IVideo } from "../../components/Player";
import "./index.less"; import "./index.less";
import { ResizeBox, Space, Result, Button } from "@arco-design/web-react";
import { Icon } from "@ricons/utils";
function CourseDetail() { function CourseDetail() {
const [toc, setToc] = useState([ const [toc, setToc] = useState([
@ -44,6 +46,102 @@ function CourseDetail() {
time: "6:55", time: "6:55",
get: false, get: false,
}, },
{
title: "使用CSS",
level: 1,
},
{
title: "使用 css行内样式",
level: 2,
time: "5:55",
get: false,
},
{
title: "使用 css行内样式2",
level: 2,
time: "6:55",
get: false,
},
{
title: "使用CSS",
level: 1,
},
{
title: "使用 css行内样式",
level: 2,
time: "5:55",
get: false,
},
{
title: "使用 css行内样式2",
level: 2,
time: "6:55",
get: false,
},
{
title: "使用CSS",
level: 1,
},
{
title: "使用 css行内样式",
level: 2,
time: "5:55",
get: false,
},
{
title: "使用 css行内样式2",
level: 2,
time: "6:55",
get: false,
},
{
title: "使用CSS",
level: 1,
},
{
title: "使用 css行内样式",
level: 2,
time: "5:55",
get: false,
},
{
title: "使用 css行内样式2",
level: 2,
time: "6:55",
get: false,
},
{
title: "使用CSS",
level: 1,
},
{
title: "使用 css行内样式",
level: 2,
time: "5:55",
get: false,
},
{
title: "使用 css行内样式2",
level: 2,
time: "6:55",
get: false,
},
{
title: "使用CSS",
level: 1,
},
{
title: "使用 css行内样式",
level: 2,
time: "5:55",
get: false,
},
{
title: "使用 css行内样式2",
level: 2,
time: "6:55",
get: false,
},
]); ]);
const [video, setVideo] = useState<IVideo | null>(null); const [video, setVideo] = useState<IVideo | null>(null);
@ -54,38 +152,55 @@ function CourseDetail() {
return ( return (
<div className="course-detail container"> <div className="course-detail container">
<article> <ResizeBox.Split
<h4 className="title">01 | SQL查询语句是如何执行的</h4> direction="horizontal"
<div className="player-container"> style={{ height: "100vh" }}
<Player video={video} /> // max={0.8}
</div> // min={0.2}
</article> size={0.6}
<aside> panes={[
<p></p> <article>
<div className="toc"> <Result
{toc.map((i) => { status="403"
if (i.level === 1) { subTitle="挖宝藏,请加入矿工"
return ( extra={<Button type="text"></Button>}
<div className="level-1" key={i.title}> ></Result>
{i.title} {/* <div className="player-container">
</div> <Player video={video} />
); </div> */}
} else if (i.level === 2) { </article>,
return ( <aside className="bs-scrollbar">
<div <h2>SQL查询语句是如何执行的</h2>
className="level-2" <div>
key={i.title} <Space style={{ color: "var(--color-text-3)" }}>
onClick={() => onclickItem(i)} <span>202332</span>
> </Space>
<span className="bs-ellipsis">{i.title}</span> </div>
<span className="time">{i.time}</span> <div className="toc">
<span className="get">get</span> {toc.map((i) => {
</div> if (i.level === 1) {
); return (
} <div className="level-1" key={i.title}>
})} {i.title}
</div> </div>
</aside> );
} else if (i.level === 2) {
return (
<div
className="level-2"
key={i.title}
onClick={() => onclickItem(i)}
>
<span className="bs-ellipsis">{i.title}</span>
<span className="time">{i.time}</span>
</div>
);
}
})}
</div>
</aside>,
]}
/>
</div> </div>
); );
} }