mirror of
https://github.com/PlayEdu/frontend.git
synced 2025-12-23 03:25:27 +08:00
课程视频播放
This commit is contained in:
@@ -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`, {});
|
||||
}
|
||||
|
||||
// 记录学员观看时长
|
||||
|
||||
@@ -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
7
src/global.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
export {};
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
$microWidgetProps: any; //全局变量名
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}}
|
||||
>
|
||||
继续学习
|
||||
|
||||
73
src/pages/course/compenents/video.module.scss
Normal file
73
src/pages/course/compenents/video.module.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
102
src/pages/course/compenents/video.tsx
Normal file
102
src/pages/course/compenents/video.tsx
Normal 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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user