课程视频播放

This commit is contained in:
禺狨
2023-03-24 18:42:18 +08:00
parent bafdbc3347
commit 8eb517fa86
74 changed files with 237 additions and 8 deletions

View File

@@ -7,7 +7,7 @@ export function detail(id: number) {
// 获取播放地址
export function playUrl(courseId: number, hourId: number) {
return client.get(`/api/v1/course/${courseId}/hour/${hourId}`, {});
return client.get(`/api/v1/course/${courseId}/hour/${hourId}/play`, {});
}
// 记录学员观看时长

View File

@@ -93,12 +93,14 @@ export const Header: React.FC = () => {
<Dropdown menu={{ items, onClick }} placement="bottomRight">
<div className="d-flex">
{user && user.name && (
<img
style={{ width: 36, height: 36, borderRadius: "50%" }}
src={user.avatar}
/>
<>
<img
style={{ width: 36, height: 36, borderRadius: "50%" }}
src={user.avatar}
/>
<span className="ml-8 c-admin">{user.name}</span>
</>
)}
<span className="ml-8 c-admin">{user.name}</span>
</div>
</Dropdown>
</Button.Group>

7
src/global.d.ts vendored Normal file
View File

@@ -0,0 +1,7 @@
export {};
declare global {
interface Window {
$microWidgetProps: any; //全局变量名
}
}

View File

@@ -5,9 +5,11 @@ import styles from "./hour.module.scss";
import mediaIcon from "../../../assets/images/commen/icon-medal.png";
import { Navigate } from "react-router-dom";
import { durationFormat } from "../../../utils/index";
import { VideoModel } from "./video";
interface PropInterface {
id: number;
cid: number;
title: string;
duration: number;
record: any;
@@ -16,14 +18,23 @@ interface PropInterface {
export const HourCompenent: React.FC<PropInterface> = ({
id,
cid,
title,
duration,
record,
progress,
}) => {
const navigate = useNavigate();
const [visible, setVisible] = useState<boolean>(false);
return (
<div className={styles["item"]}>
<VideoModel
cid={cid}
id={id}
title={title}
open={visible}
onCancel={() => setVisible(false)}
></VideoModel>
<div className={styles["left-item"]}>
<i className="iconfont icon-icon-video"></i>
<div className={styles["title"]}>
@@ -37,7 +48,7 @@ export const HourCompenent: React.FC<PropInterface> = ({
<div
className={styles["link"]}
onClick={() => {
navigate(`/course/play/${id}`);
setVisible(true);
}}
>
@@ -51,7 +62,7 @@ export const HourCompenent: React.FC<PropInterface> = ({
<div
className={styles["link"]}
onClick={() => {
navigate(`/course/play/${id}`);
setVisible(true);
}}
>

View File

@@ -0,0 +1,73 @@
.video-mask {
width: 100%;
height: 100%;
top: 0;
bottom: 0;
left: 0;
right: 0;
position: fixed;
background-color: #0e0e1e;
display: flex;
justify-content: center;
z-index: 100;
.top-cont {
position: fixed;
top: 0;
left: 0;
right: 0;
width: 100%;
height: 60px;
background: rgba(255, 255, 255, 0.1);
.box {
width: 1200px;
height: 60px;
display: flex;
align-items: center;
margin: 0 auto;
.close-btn {
width: 110px;
height: 40px;
display: flex;
align-items: center;
padding-left: 30px;
font-size: 14px;
font-weight: 400;
color: #ffffff;
line-height: 24px;
cursor: pointer;
}
}
}
.video-body {
width: 1200px;
height: auto;
display: flex;
flex-direction: column;
box-sizing: border-box;
padding-top: 30px;
margin-top: 60px;
animation: scaleBig 0.3s;
.video-title {
width: 1200px;
height: 36px;
font-size: 20px;
font-weight: 600;
color: rgba(255, 255, 255, 0.88);
line-height: 36px;
margin-bottom: 30px;
text-align: left;
}
.video-box {
width: 1200px;
height: 600px;
margin: 0 auto;
border-radius: 8px;
overflow: hidden;
background: linear-gradient(90deg, #f2f2f2 25%, #e6e6e6 37%, #f2f2f2 63%);
background-size: 400% 100%;
animation: el-skeleton-loading 1.4s ease infinite;
}
}
}

View File

@@ -0,0 +1,102 @@
import React, { useState, useEffect } from "react";
import styles from "./video.module.scss";
import { course } from "../../../api/index";
declare const window: any;
interface PropInterface {
id: number;
cid: number;
title: string;
open: boolean;
onCancel: () => void;
}
export const VideoModel: React.FC<PropInterface> = ({
id,
cid,
title,
open,
onCancel,
}) => {
const [playUrl, setPlayUrl] = useState<string>("");
const [playDuration, setPlayDuration] = useState(0);
useEffect(() => {
if (open) {
getVideoUrl();
}
}, [open, id, cid]);
const getVideoUrl = () => {
course.playUrl(cid, id).then((res: any) => {
setPlayUrl(res.data.url);
initDPlayer(res.data.url, 1);
});
};
const initDPlayer = (playUrl: string, isTrySee: number) => {
window.player = new window.DPlayer({
container: document.getElementById("meedu-player-container"),
autoplay: false,
video: {
quality: playUrl,
defaultQuality: 0,
},
try: isTrySee === 1,
bulletSecret: {
enabled: true,
text: "18119604035",
size: "15px",
color: "red",
opacity: 0.8,
},
ban_drag: false,
last_see_pos: 0,
});
// 监听播放进度更新evt
window.player.on("timeupdate", () => {
playTimeUpdate(parseInt(window.player.video.currentTime), false);
});
window.player.on("ended", () => {
playTimeUpdate(parseInt(window.player.video.currentTime), true);
window.player.destroy();
});
};
const playTimeUpdate = (duration: number, isEnd: boolean) => {
if (duration >= 10 || isEnd === true) {
setPlayDuration(duration);
course.record(cid, id, duration).then((res: any) => {});
}
};
return (
<>
{open && (
<div className={styles["video-mask"]}>
<div className={styles["top-cont"]}>
<div className={styles["box"]}>
<div
className={styles["close-btn"]}
onClick={() => {
window.player && window.player.destroy();
onCancel();
}}
>
</div>
</div>
</div>
<div className={styles["video-body"]}>
<div className={styles["video-title"]}>{title}</div>
<div className={styles["video-box"]}>
<div className="play-box" id="meedu-player-container"></div>
</div>
</div>
</div>
)}
</>
);
};

View File

@@ -92,6 +92,7 @@ const CoursePage = () => {
<div key={item.id} className={styles["hours-it"]}>
<HourCompenent
id={item.id}
cid={item.course_id}
title={item.title}
record={item.rid}
duration={item.duration}
@@ -110,6 +111,7 @@ const CoursePage = () => {
<div key={it.id} className={styles["hours-it"]}>
<HourCompenent
id={it.id}
cid={item.course_id}
title={it.title}
record={it.rid}
duration={it.duration}