课程视频播放

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

@ -9,5 +9,11 @@
<body> <body>
<div id="root"></div> <div id="root"></div>
<script type="module" src="/src/main.tsx"></script> <script type="module" src="/src/main.tsx"></script>
<script
crossorigin="anonymous"
integrity="sha512-oHrfR/z2wkuRuaHrdZ9NhoT/o/1kteub+QvmQgVzOKK7NTvIKQMvnY9+/RR0+eW311o4lAE/YzzLXXmP2XUvig=="
src="https://lib.baomitu.com/hls.js/1.1.4/hls.min.js"
></script>
<script src="/public/js/DPlayer.min.js" defer></script>
</body> </body>
</html> </html>

1
public/js/DPlayer.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("katex")):"function"==typeof define&&define.amd?define(["katex"],t):"object"==typeof exports?exports.renderMathInElement=t(require("katex")):e.renderMathInElement=t(e.katex)}("undefined"!=typeof self?self:this,(function(e){return function(){"use strict";var t={771:function(t){t.exports=e}},r={};function n(e){var i=r[e];if(void 0!==i)return i.exports;var a=r[e]={exports:{}};return t[e](a,a.exports,n),a.exports}n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,{a:t}),t},n.d=function(e,t){for(var r in t)n.o(t,r)&&!n.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)};var i={};return function(){n.d(i,{default:function(){return s}});var e=n(771),t=n.n(e),r=function(e,t,r){for(var n=r,i=0,a=e.length;n<t.length;){var o=t[n];if(i<=0&&t.slice(n,n+a)===e)return n;"\\"===o?n++:"{"===o?i++:"}"===o&&i--,n++}return-1},a=/^\\begin{/,o=function(e,t){for(var n,i=[],o=new RegExp("("+t.map((function(e){return e.left.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&")})).join("|")+")");-1!==(n=e.search(o));){n>0&&(i.push({type:"text",data:e.slice(0,n)}),e=e.slice(n));var l=t.findIndex((function(t){return e.startsWith(t.left)}));if(-1===(n=r(t[l].right,e,t[l].left.length)))break;var d=e.slice(0,n+t[l].right.length),s=a.test(d)?d:e.slice(t[l].left.length,n);i.push({type:"math",data:s,rawData:d,display:t[l].display}),e=e.slice(n+t[l].right.length)}return""!==e&&i.push({type:"text",data:e}),i},l=function(e,r){var n=o(e,r.delimiters);if(1===n.length&&"text"===n[0].type)return null;for(var i=document.createDocumentFragment(),a=0;a<n.length;a++)if("text"===n[a].type)i.appendChild(document.createTextNode(n[a].data));else{var l=document.createElement("span"),d=n[a].data;r.displayMode=n[a].display;try{r.preProcess&&(d=r.preProcess(d)),t().render(d,l,r)}catch(e){if(!(e instanceof t().ParseError))throw e;r.errorCallback("KaTeX auto-render: Failed to parse `"+n[a].data+"` with ",e),i.appendChild(document.createTextNode(n[a].rawData));continue}i.appendChild(l)}return i},d=function e(t,r){for(var n=0;n<t.childNodes.length;n++){var i=t.childNodes[n];if(3===i.nodeType){for(var a=i.textContent,o=i.nextSibling,d=0;o&&o.nodeType===Node.TEXT_NODE;)a+=o.textContent,o=o.nextSibling,d++;var s=l(a,r);if(s){for(var f=0;f<d;f++)i.nextSibling.remove();n+=s.childNodes.length-1,t.replaceChild(s,i)}else n+=d}else 1===i.nodeType&&function(){var t=" "+i.className+" ";-1===r.ignoredTags.indexOf(i.nodeName.toLowerCase())&&r.ignoredClasses.every((function(e){return-1===t.indexOf(" "+e+" ")}))&&e(i,r)}()}},s=function(e,t){if(!e)throw new Error("No element provided to render");var r={};for(var n in t)t.hasOwnProperty(n)&&(r[n]=t[n]);r.delimiters=r.delimiters||[{left:"$$",right:"$$",display:!0},{left:"\\(",right:"\\)",display:!1},{left:"\\begin{equation}",right:"\\end{equation}",display:!0},{left:"\\begin{align}",right:"\\end{align}",display:!0},{left:"\\begin{alignat}",right:"\\end{alignat}",display:!0},{left:"\\begin{gather}",right:"\\end{gather}",display:!0},{left:"\\begin{CD}",right:"\\end{CD}",display:!0},{left:"\\[",right:"\\]",display:!0}],r.ignoredTags=r.ignoredTags||["script","noscript","style","textarea","pre","code","option"],r.ignoredClasses=r.ignoredClasses||[],r.errorCallback=r.errorCallback||console.error,r.macros=r.macros||{},d(e,r)}}(),i=i.default}()}));

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

1
public/js/katex/katex.min.css vendored Normal file

File diff suppressed because one or more lines are too long

1
public/js/katex/katex.min.js vendored Normal file

File diff suppressed because one or more lines are too long

1
public/js/xg/hls.min.js vendored Normal file

File diff suppressed because one or more lines are too long

21
public/js/xg/index.js Normal file

File diff suppressed because one or more lines are too long

View File

@ -7,7 +7,7 @@ export function detail(id: number) {
// 获取播放地址 // 获取播放地址
export function playUrl(courseId: number, hourId: 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"> <Dropdown menu={{ items, onClick }} placement="bottomRight">
<div className="d-flex"> <div className="d-flex">
{user && user.name && ( {user && user.name && (
<img <>
style={{ width: 36, height: 36, borderRadius: "50%" }} <img
src={user.avatar} 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> </div>
</Dropdown> </Dropdown>
</Button.Group> </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 mediaIcon from "../../../assets/images/commen/icon-medal.png";
import { Navigate } from "react-router-dom"; import { Navigate } from "react-router-dom";
import { durationFormat } from "../../../utils/index"; import { durationFormat } from "../../../utils/index";
import { VideoModel } from "./video";
interface PropInterface { interface PropInterface {
id: number; id: number;
cid: number;
title: string; title: string;
duration: number; duration: number;
record: any; record: any;
@ -16,14 +18,23 @@ interface PropInterface {
export const HourCompenent: React.FC<PropInterface> = ({ export const HourCompenent: React.FC<PropInterface> = ({
id, id,
cid,
title, title,
duration, duration,
record, record,
progress, progress,
}) => { }) => {
const navigate = useNavigate(); const navigate = useNavigate();
const [visible, setVisible] = useState<boolean>(false);
return ( return (
<div className={styles["item"]}> <div className={styles["item"]}>
<VideoModel
cid={cid}
id={id}
title={title}
open={visible}
onCancel={() => setVisible(false)}
></VideoModel>
<div className={styles["left-item"]}> <div className={styles["left-item"]}>
<i className="iconfont icon-icon-video"></i> <i className="iconfont icon-icon-video"></i>
<div className={styles["title"]}> <div className={styles["title"]}>
@ -37,7 +48,7 @@ export const HourCompenent: React.FC<PropInterface> = ({
<div <div
className={styles["link"]} className={styles["link"]}
onClick={() => { onClick={() => {
navigate(`/course/play/${id}`); setVisible(true);
}} }}
> >
@ -51,7 +62,7 @@ export const HourCompenent: React.FC<PropInterface> = ({
<div <div
className={styles["link"]} className={styles["link"]}
onClick={() => { 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"]}> <div key={item.id} className={styles["hours-it"]}>
<HourCompenent <HourCompenent
id={item.id} id={item.id}
cid={item.course_id}
title={item.title} title={item.title}
record={item.rid} record={item.rid}
duration={item.duration} duration={item.duration}
@ -110,6 +111,7 @@ const CoursePage = () => {
<div key={it.id} className={styles["hours-it"]}> <div key={it.id} className={styles["hours-it"]}>
<HourCompenent <HourCompenent
id={it.id} id={it.id}
cid={item.course_id}
title={it.title} title={it.title}
record={it.rid} record={it.rid}
duration={it.duration} duration={it.duration}