feat: 播放期
This commit is contained in:
parent
f8be92ddde
commit
57ff9c0356
|
@ -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>
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>2023年3月2日</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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user