feat: 播放期
This commit is contained in:
parent
f8be92ddde
commit
57ff9c0356
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
}
|
||||
|
||||
.video-js {
|
||||
box-shadow: 0 0 100px #c8c8c8 !important;
|
||||
// box-shadow: 0 0 100px #c8c8c8 !important;
|
||||
}
|
||||
|
||||
.vjs-control-bar {
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
useScript(
|
||||
["/player/libs/hls.min.0.13.2m.js", "/player/tcplayer.v4.7.2.min.js"],
|
||||
() => {
|
||||
console.log("[tcplayer] libs ready...");
|
||||
setLibReady(true);
|
||||
}
|
||||
);
|
||||
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(() => {
|
||||
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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,14 +152,30 @@ function CourseDetail() {
|
|||
|
||||
return (
|
||||
<div className="course-detail container">
|
||||
<ResizeBox.Split
|
||||
direction="horizontal"
|
||||
style={{ height: "100vh" }}
|
||||
// max={0.8}
|
||||
// min={0.2}
|
||||
size={0.6}
|
||||
panes={[
|
||||
<article>
|
||||
<h4 className="title">01 | 基础架构:一条SQL查询语句是如何执行的?</h4>
|
||||
<div className="player-container">
|
||||
<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>2023年3月2日</span>
|
||||
</Space>
|
||||
</div>
|
||||
</article>
|
||||
<aside>
|
||||
<p>目录</p>
|
||||
<div className="toc">
|
||||
{toc.map((i) => {
|
||||
if (i.level === 1) {
|
||||
|
@ -79,13 +193,14 @@ function CourseDetail() {
|
|||
>
|
||||
<span className="bs-ellipsis">{i.title}</span>
|
||||
<span className="time">{i.time}</span>
|
||||
<span className="get">get</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
</aside>
|
||||
</aside>,
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user