diff --git a/src/api/course.ts b/src/api/course.ts index 603604c..53d62bf 100644 --- a/src/api/course.ts +++ b/src/api/course.ts @@ -5,6 +5,11 @@ export function detail(id: number) { return client.get(`/api/v1/course/${id}`, {}); } +// 线上课课时详情 +export function play(courseId: number, id: number) { + return client.get(`/api/v1/course/${courseId}/hour/${id}`, {}); +} + // 获取播放地址 export function playUrl(courseId: number, hourId: number) { return client.get(`/api/v1/course/${courseId}/hour/${hourId}/play`, {}); diff --git a/src/pages/course/compenents/hour.tsx b/src/pages/course/compenents/hour.tsx index 23be64b..02a30bf 100644 --- a/src/pages/course/compenents/hour.tsx +++ b/src/pages/course/compenents/hour.tsx @@ -1,8 +1,7 @@ -import React, { useState, useEffect } from "react"; +import React from "react"; import { useNavigate } from "react-router-dom"; import styles from "./hour.module.scss"; import { durationFormat } from "../../../utils/index"; -import { VideoModel } from "./video"; interface PropInterface { id: number; @@ -11,9 +10,6 @@ interface PropInterface { duration: number; record: any; progress: number; - totalHours: any; - records: any; - onChange: () => void; } export const HourCompenent: React.FC = ({ @@ -23,73 +19,14 @@ export const HourCompenent: React.FC = ({ duration, record, progress, - totalHours, - records, - onChange, }) => { - // const navigate = useNavigate(); - const [visible, setVisible] = useState(false); - const [currentId, setCurrentId] = useState(id); - const [currentTitle, setCurrentTitle] = useState(title); - const [isLastpage, setIsLastpage] = useState(false); - const [lastSeeDuration, setLastSeeDuration] = useState(0); - - useEffect(() => { - getData(); - }, [totalHours]); - - const getData = () => { - const index = totalHours.findIndex((i: any) => i.id === id); - if (index === totalHours.length - 1) { - setIsLastpage(true); - } - if (records[totalHours[index].id]) { - setLastSeeDuration(records[totalHours[index].id].finished_duration); - } - }; - - const goNextVideo = () => { - const index = totalHours.findIndex((i: any) => i.id === currentId); - if (index === totalHours.length - 1) { - setIsLastpage(true); - } else if (index < totalHours.length - 1) { - setCurrentId(totalHours[index + 1].id); - setCurrentTitle(totalHours[index + 1].title); - if (index + 1 === totalHours.length - 1) { - setIsLastpage(true); - } - if (records[totalHours[index + 1].id]) { - setLastSeeDuration(records[totalHours[index + 1].id].finished_duration); - } - } - setVisible(true); - }; - + const navigate = useNavigate(); return ( <> - { - setVisible(false); - onChange(); - }} - goNextVideo={() => { - setVisible(false); - goNextVideo(); - }} - >
{ - setCurrentId(id); - setCurrentTitle(title); - setVisible(true); + navigate(`/course/${cid}/hour/${id}`); }} >
diff --git a/src/pages/course/compenents/video.tsx b/src/pages/course/compenents/video.tsx deleted file mode 100644 index fbd0930..0000000 --- a/src/pages/course/compenents/video.tsx +++ /dev/null @@ -1,169 +0,0 @@ -import React, { useState, useRef, useEffect } from "react"; -import styles from "./video.module.scss"; -import { course } from "../../../api/index"; -import { ArrowLeftOutlined } from "@ant-design/icons"; -import { useSelector } from "react-redux"; - -declare const window: any; - -interface PropInterface { - id: number; - cid: number; - title: string; - open: boolean; - isLastpage: boolean; - lastSeeDuration: number; - progress: number; - onCancel: () => void; - goNextVideo: () => void; -} - -export const VideoModel: React.FC = ({ - id, - cid, - title, - open, - isLastpage, - lastSeeDuration, - progress, - onCancel, - goNextVideo, -}) => { - const systemConfig = useSelector((state: any) => state.systemConfig.value); - const user = useSelector((state: any) => state.loginUser.value.user); - const [playUrl, setPlayUrl] = useState(""); - const [playDuration, setPlayDuration] = useState(0); - const [playendedStatus, setPlayendedStatus] = useState(false); - const [lastSeeValue, setLastSeeValue] = useState({}); - const [loading, setLoading] = useState(false); - const myRef = useRef(0); - - useEffect(() => { - let params = null; - if (open) { - if (lastSeeDuration > 0 && progress < 100) { - params = { - time: 5, - pos: lastSeeDuration, - }; - setLastSeeValue(params); - } - setPlayendedStatus(false); - getVideoUrl(params); - } - }, [open, id, cid, lastSeeDuration]); - - useEffect(() => { - myRef.current = playDuration; - }, [playDuration]); - - const getVideoUrl = (params: any) => { - course.playUrl(cid, id).then((res: any) => { - setPlayUrl(res.data.url); - initDPlayer(res.data.url, 0, params); - }); - }; - - const initDPlayer = (playUrl: string, isTrySee: number, params: any) => { - window.player = new window.DPlayer({ - container: document.getElementById("meedu-player-container"), - autoplay: false, - video: { - url: playUrl, - pic: systemConfig.playerPoster, - }, - try: isTrySee === 1, - bulletSecret: { - enabled: systemConfig.playerIsEnabledBulletSecret, - text: systemConfig.playerBulletSecretText - .replace("{name}", user.name) - .replace("{email}", user.email) - .replace("{idCard}", user.id_card), - size: "14px", - color: systemConfig.playerBulletSecretColor || "red", - opacity: Number(systemConfig.playerBulletSecretOpacity), - }, - ban_drag: false, - last_see_pos: params, - }); - // 监听播放进度更新evt - window.player.on("timeupdate", () => { - playTimeUpdate(parseInt(window.player.video.currentTime), false); - }); - window.player.on("ended", () => { - setPlayendedStatus(true); - playTimeUpdate(parseInt(window.player.video.currentTime), true); - window.player && window.player.destroy(); - }); - setLoading(false); - }; - - const playTimeUpdate = (duration: number, isEnd: boolean) => { - if (duration - myRef.current >= 10 || isEnd === true) { - setPlayDuration(duration); - course.record(cid, id, duration).then((res: any) => {}); - course.playPing(cid, id).then((res: any) => {}); - } - }; - - return ( - <> - {open && ( -
-
-
-
{ - window.player && window.player.destroy(); - onCancel(); - }} - > - - 返回 -
-
-
-
-
{title}
-
-
- {playendedStatus && ( -
- {isLastpage && ( -
{ - window.player && window.player.destroy(); - onCancel(); - }} - > - 恭喜你学完最后一节 -
- )} - {!isLastpage && ( -
{ - if (loading) { - return; - } - window.player && window.player.destroy(); - setLoading(true); - setLastSeeValue({}); - setPlayendedStatus(false); - goNextVideo(); - }} - > - 播放下一节 -
- )} -
- )} -
-
-
- )} - - ); -}; diff --git a/src/pages/course/index.tsx b/src/pages/course/index.tsx index 233da2d..220e1dd 100644 --- a/src/pages/course/index.tsx +++ b/src/pages/course/index.tsx @@ -15,7 +15,6 @@ const CoursePage = () => { const [hours, setHours] = useState({}); const [learnRecord, setLearnRecord] = useState({}); const [learnHourRecord, setLearnHourRecord] = useState({}); - const [totalHours, setTotalHours] = useState([]); useEffect(() => { getDetail(); @@ -35,17 +34,6 @@ const CoursePage = () => { if (res.data.learn_hour_records) { setLearnHourRecord(res.data.learn_hour_records); } - if (res.data.chapters.length === 0) { - setTotalHours(res.data.hours[0]); - } else if (res.data.chapters.length > 0) { - const arr: any = []; - for (let key in res.data.hours) { - res.data.hours[key].map((item: any) => { - arr.push(item); - }); - } - setTotalHours(arr); - } setLoading(false); }) .catch((e) => { @@ -159,14 +147,11 @@ const CoursePage = () => { cid={item.course_id} title={item.title} record={learnHourRecord[item.id]} - records={learnHourRecord} duration={item.duration} progress={ (learnHourRecord[item.id].finished_duration * 100) / learnHourRecord[item.id].total_duration } - totalHours={totalHours} - onChange={() => getDetail()} > )} {!learnHourRecord[item.id] && ( @@ -175,11 +160,8 @@ const CoursePage = () => { cid={item.course_id} title={item.title} record={null} - records={learnHourRecord} duration={item.duration} progress={0} - totalHours={totalHours} - onChange={() => getDetail()} > )}
@@ -199,14 +181,11 @@ const CoursePage = () => { cid={item.course_id} title={it.title} record={learnHourRecord[it.id]} - records={learnHourRecord} duration={it.duration} progress={ (learnHourRecord[it.id].finished_duration * 100) / learnHourRecord[it.id].total_duration } - onChange={() => getDetail()} - totalHours={totalHours} > )} {!learnHourRecord[it.id] && ( @@ -215,11 +194,8 @@ const CoursePage = () => { cid={item.course_id} title={it.title} record={null} - records={learnHourRecord} duration={it.duration} progress={0} - totalHours={totalHours} - onChange={() => getDetail()} > )}
diff --git a/src/pages/course/compenents/video.module.scss b/src/pages/course/video.module.scss similarity index 94% rename from src/pages/course/compenents/video.module.scss rename to src/pages/course/video.module.scss index c9ebaff..d94ee1f 100644 --- a/src/pages/course/compenents/video.module.scss +++ b/src/pages/course/video.module.scss @@ -1,15 +1,10 @@ .video-mask { width: 100%; - height: 100%; - top: 0; - bottom: 0; - left: 0; - right: 0; - position: fixed; + height: 100vh; + min-height: 100vh; background-color: #0e0e1e; display: flex; justify-content: center; - z-index: 100; .top-cont { position: fixed; top: 0; @@ -18,9 +13,8 @@ width: 100%; height: 60px; background: rgba(255, 255, 255, 0.1); - .box { - width: 1200px; + width: 62.5%; height: 60px; display: flex; align-items: center; diff --git a/src/pages/course/video.tsx b/src/pages/course/video.tsx new file mode 100644 index 0000000..a2c5df5 --- /dev/null +++ b/src/pages/course/video.tsx @@ -0,0 +1,206 @@ +import { useEffect, useRef, useState } from "react"; +import styles from "./video.module.scss"; +import { useParams, useNavigate } from "react-router-dom"; +import { useSelector } from "react-redux"; +import { course as Course } from "../../api/index"; +import { ArrowLeftOutlined } from "@ant-design/icons"; +import { message } from "antd"; + +declare const window: any; + +const CoursePalyPage = () => { + const navigate = useNavigate(); + const params = useParams(); + const systemConfig = useSelector((state: any) => state.systemConfig.value); + const user = useSelector((state: any) => state.loginUser.value.user); + const [playUrl, setPlayUrl] = useState(""); + const [playDuration, setPlayDuration] = useState(0); + const [playendedStatus, setPlayendedStatus] = useState(false); + const [lastSeeValue, setLastSeeValue] = useState({}); + const [course, setCourse] = useState({}); + const [hour, setHour] = useState({}); + const [loading, setLoading] = useState(false); + const [isLastpage, setIsLastpage] = useState(false); + const [totalHours, setTotalHours] = useState([]); + const myRef = useRef(0); + + useEffect(() => { + getCourse(); + getDetail(); + }, [params.courseId, params.hourId]); + + useEffect(() => { + myRef.current = playDuration; + }, [playDuration]); + + const getCourse = () => { + Course.detail(Number(params.courseId)).then((res: any) => { + let totalHours: any = []; + if (res.data.chapters.length === 0) { + setTotalHours(res.data.hours[0]); + totalHours = res.data.hours[0]; + } else if (res.data.chapters.length > 0) { + const arr: any = []; + for (let key in res.data.hours) { + res.data.hours[key].map((item: any) => { + arr.push(item); + }); + } + setTotalHours(arr); + totalHours = arr; + } + const index = totalHours.findIndex( + (i: any) => i.id === Number(params.hourId) + ); + if (index === totalHours.length - 1) { + setIsLastpage(true); + } + }); + }; + + const getDetail = () => { + if (loading) { + return true; + } + setLoading(true); + Course.play(Number(params.courseId), Number(params.hourId)) + .then((res: any) => { + setCourse(res.data.course); + setHour(res.data.hour); + document.title = res.data.hour.title; + let record = res.data.user_hour_record; + let params = null; + if (record && record.finished_duration && record.is_finished === 0) { + params = { + time: 5, + pos: record.finished_duration, + }; + setLastSeeValue(params); + setLastSeeValue(params); + } + getVideoUrl(params); + setLoading(false); + }) + .catch((e) => { + setLoading(false); + }); + }; + + const getVideoUrl = (data: any) => { + Course.playUrl(Number(params.courseId), Number(params.hourId)).then( + (res: any) => { + setPlayUrl(res.data.url); + initDPlayer(res.data.url, 0, data); + } + ); + }; + + const initDPlayer = (playUrl: string, isTrySee: number, params: any) => { + window.player = new window.DPlayer({ + container: document.getElementById("meedu-player-container"), + autoplay: false, + video: { + url: playUrl, + pic: systemConfig.playerPoster, + }, + try: isTrySee === 1, + bulletSecret: { + enabled: systemConfig.playerIsEnabledBulletSecret, + text: systemConfig.playerBulletSecretText + .replace("{name}", user.name) + .replace("{email}", user.email) + .replace("{idCard}", user.id_card), + size: "14px", + color: systemConfig.playerBulletSecretColor || "red", + opacity: Number(systemConfig.playerBulletSecretOpacity), + }, + ban_drag: false, + last_see_pos: params, + }); + // 监听播放进度更新evt + window.player.on("timeupdate", () => { + playTimeUpdate(parseInt(window.player.video.currentTime), false); + }); + window.player.on("ended", () => { + setPlayendedStatus(true); + playTimeUpdate(parseInt(window.player.video.currentTime), true); + window.player && window.player.destroy(); + }); + setLoading(false); + }; + + const playTimeUpdate = (duration: number, isEnd: boolean) => { + if (duration - myRef.current >= 10 || isEnd === true) { + setPlayDuration(duration); + Course.record( + Number(params.courseId), + Number(params.hourId), + duration + ).then((res: any) => {}); + Course.playPing(Number(params.courseId), Number(params.hourId)).then( + (res: any) => {} + ); + } + }; + + const goNextVideo = () => { + const index = totalHours.findIndex( + (i: any) => i.id === Number(params.hourId) + ); + if (index === totalHours.length - 1) { + setIsLastpage(true); + message.error("已经是最后一节了!"); + } else if (index < totalHours.length - 1) { + navigate(`/course/${params.courseId}/hour/${totalHours[index + 1].id}`, { + replace: true, + }); + } + }; + + return ( +
+
+
+
{ + window.player && window.player.destroy(); + navigate(-1); + }} + > + + 返回 +
+
+
+
+
{hour.title}
+
+
+ {playendedStatus && ( +
+ {isLastpage && ( +
恭喜你学完最后一节
+ )} + {!isLastpage && ( +
{ + window.player && window.player.destroy(); + setLastSeeValue({}); + setPlayendedStatus(false); + goNextVideo(); + }} + > + 播放下一节 +
+ )} +
+ )} +
+
+
+ ); +}; + +export default CoursePalyPage; diff --git a/src/pages/init/index.tsx b/src/pages/init/index.tsx index 7347448..de3d729 100644 --- a/src/pages/init/index.tsx +++ b/src/pages/init/index.tsx @@ -7,7 +7,7 @@ import { } from "../../store/system/systemConfigSlice"; import { loginAction } from "../../store/user/loginUserSlice"; import { Header, NoHeader, Footer } from "../../compenents"; -import { useLocation } from "react-router-dom"; +import { useParams, useLocation } from "react-router-dom"; interface Props { loginData?: any; @@ -44,14 +44,15 @@ export const InitPage = (props: Props) => { } const pathname = useLocation().pathname; + const params = useParams(); return ( <>
{pathname === "/login" && } - {pathname !== "/login" &&
} + {pathname !== "/login" && !params.hourId &&
} - {pathname !== "/login" &&
} + {pathname !== "/login" && !params.hourId &&
}
); diff --git a/src/pages/latest-learn/index.tsx b/src/pages/latest-learn/index.tsx index 523f288..c9f4010 100644 --- a/src/pages/latest-learn/index.tsx +++ b/src/pages/latest-learn/index.tsx @@ -45,8 +45,8 @@ const LatestLearnPage = () => { )} {!loading && courses.length > 0 && - courses.map((item: any) => ( -
+ courses.map((item: any, index: number) => ( +
{item.course && (
, }, + { + path: "/course/:courseId/hour/:hourId", + element: , + }, { path: "/latest-learn", element: ,