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>
<div id="bs-app"></div>
<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>
</html>

View File

@ -60,3 +60,16 @@ input {
overflow: hidden;
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 {
box-shadow: 0 0 100px #c8c8c8 !important;
// box-shadow: 0 0 100px #c8c8c8 !important;
}
.vjs-control-bar {

View File

@ -1,5 +1,5 @@
import { useEffect, useRef, useState } from "react";
import { useLink, useMount, useScript } from "../../hook";
import { useScript } from "../../hook";
import "./index.less";
export interface IVideo {
@ -10,70 +10,54 @@ export interface IVideo {
}
interface IProps {
video?: IVideo | null;
video: IVideo | null;
}
/**
* demo页面https://tcplayer.vcube.tencent.com/
*
* Chrome Firefox H5 Webrtc , tcplayer.vx.x.x.min.js TXLivePlayer-x.x.x.min.js
* Webrtc Webrtc HLS hls.min.x.xx.xm.js
* <video objectFill /> fill填满变形cover等比例会裁剪, contain等比例有黑边
* 1. fill填满变形cover等比例会裁剪, contain等比例有黑边
* 2. TCPlayer('container', video)video为空
*/
function Player(props: IProps) {
const playerRef = useRef<any>();
const [videoInfo, setVideoInfo] = useState<IVideo>({
fileID: "",
appID: "",
});
const videoRef = useRef<HTMLVideoElement | null>(null);
const [libReady, setLibReady] = useState(false);
console.log("执行一次");
const initPlayer = (video: IVideo) => {
const TCPlayer = (window as any).TCPlayer;
// 重写播放按钮
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"],
// () => {}
// );
useScript(
["/player/libs/hls.min.0.13.2m.js", "/player/tcplayer.v4.7.2.min.js"],
() => {
console.log("[tcplayer] libs ready...");
setLibReady(true);
}
);
useEffect(() => {
if (props.video) setVideoInfo(props.video);
}, [props.video]);
if (libReady) {
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(() => {
// if (videoInfo.fileID !== "") {
// const TCPlayer = (window as any).TCPlayer;
// TCPlayer(`video-${videoInfo.fileID}`, videoInfo);
// }
// }, [videoInfo]);
useEffect(() => {
if (props.video) {
console.log("[tcplayer] video change", props.video);
playerRef.current.loadVideoByID(props.video);
}
}, [props.video]);
return (
<video
onContextMenu={(e) => e.preventDefault()}
id={`video-${videoInfo.fileID}`}
ref={videoRef}
id="player"
style={{ width: "100%", height: "100%", objectFit: "contain" }}
preload="auto"
playsInline

View File

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

View File

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

View File

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