diff --git a/package-lock.json b/package-lock.json index 8c58b83..f171c5f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@vicons/ionicons5": "^0.13.0", "axios": "^1.11.0", "ckplayer": "^3.1.2", + "dplayer": "^1.27.1", "naive-ui": "^2.42.0", "pinia": "^3.0.3", "quill": "^2.0.3", @@ -20,6 +21,7 @@ "vue-router": "^4.5.1" }, "devDependencies": { + "@types/dplayer": "^1.25.5", "@types/node": "^24.0.15", "@vitejs/plugin-vue": "^6.0.0", "typescript": "^5.8.3", @@ -1398,6 +1400,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@types/dplayer": { + "version": "1.25.5", + "resolved": "https://registry.npmjs.org/@types/dplayer/-/dplayer-1.25.5.tgz", + "integrity": "sha512-p/7O94dHDo0Irn2KWIqFE+fBCA4DS7QL3jfCOjCUPBAOgppyyTjmNZjKEfiJa1M3n1oVQqG7xnPwhiIuCqOzkQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", @@ -1777,6 +1786,12 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/balloon-css": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/balloon-css/-/balloon-css-1.2.0.tgz", + "integrity": "sha512-urXwkHgwp6GsXVF+it01485Z2Cj4pnW02ICnM0TemOlkKmCNnDLmyy+ZZiRXBpwldUXO+aRNr7Hdia4CBvXJ5A==", + "license": "MIT" + }, "node_modules/birpc": { "version": "2.5.0", "resolved": "https://registry.npmmirror.com/birpc/-/birpc-2.5.0.tgz", @@ -2139,6 +2154,28 @@ "node": ">=0.4.0" } }, + "node_modules/dplayer": { + "version": "1.27.1", + "resolved": "https://registry.npmjs.org/dplayer/-/dplayer-1.27.1.tgz", + "integrity": "sha512-2laBMXs5V1B9zPwJ7eAIw/OBo+Xjvy03i4GHTk3Cg+IWbrq8rKMFO0fFr6ClAYotYOCcFGOvaJDkOZcgKllsCA==", + "license": "MIT", + "dependencies": { + "axios": "1.2.3", + "balloon-css": "^1.0.3", + "promise-polyfill": "8.3.0" + } + }, + "node_modules/dplayer/node_modules/axios": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.2.3.tgz", + "integrity": "sha512-pdDkMYJeuXLZ6Xj/Q5J3Phpe+jbGdsSzlQaFVkMQzRUL05+6+tetX8TV3p4HrU4kzuO9bt+io/yGQxuyxA/xcw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -3207,6 +3244,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/promise-polyfill": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.3.0.tgz", + "integrity": "sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg==", + "license": "MIT" + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", diff --git a/package.json b/package.json index 5645edd..7c40660 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "@vicons/ionicons5": "^0.13.0", "axios": "^1.11.0", "ckplayer": "^3.1.2", + "dplayer": "^1.27.1", "naive-ui": "^2.42.0", "pinia": "^3.0.3", "quill": "^2.0.3", @@ -25,6 +26,7 @@ "vue-router": "^4.5.1" }, "devDependencies": { + "@types/dplayer": "^1.25.5", "@types/node": "^24.0.15", "@vitejs/plugin-vue": "^6.0.0", "typescript": "^5.8.3", diff --git a/src/components/DPlayerVideo.vue b/src/components/DPlayerVideo.vue new file mode 100644 index 0000000..57eccca --- /dev/null +++ b/src/components/DPlayerVideo.vue @@ -0,0 +1,456 @@ + + + + + diff --git a/src/views/CourseDetailEnrolled.vue b/src/views/CourseDetailEnrolled.vue index ba14338..d34e4e4 100644 --- a/src/views/CourseDetailEnrolled.vue +++ b/src/views/CourseDetailEnrolled.vue @@ -34,9 +34,23 @@
- -
-
+ +
@@ -454,6 +468,7 @@ import type { Course, CourseSection, SectionVideo, VideoQuality, CourseComment, import SafeAvatar from '@/components/common/SafeAvatar.vue' import LearningProgressStats from '@/components/common/LearningProgressStats.vue' import NotesModal from '@/components/common/NotesModal.vue' +import DPlayerVideo from '@/components/DPlayerVideo.vue' // 声明全局CKPlayer类型 declare global { @@ -477,6 +492,7 @@ const FORCE_LOCAL_VIDEO = true const currentSection = ref(null) const currentVideoUrl = ref('') const ckplayer = ref(null) +const dplayerRef = ref>() // 视频相关状态 const currentVideo = ref(null) @@ -691,8 +707,9 @@ const loadCourseSections = async () => { if (firstVideo) { currentSection.value = firstVideo currentVideoUrl.value = getVideoUrl(firstVideo) - await nextTick() - initCKPlayer(currentVideoUrl.value) + // DPlayer组件会自动处理视频URL变化 + // await nextTick() + // initCKPlayer(currentVideoUrl.value) } } } else { @@ -735,7 +752,8 @@ const loadMockData = () => { if (firstVideo) { currentSection.value = firstVideo currentVideoUrl.value = getVideoUrl(firstVideo) - setTimeout(() => initCKPlayer(currentVideoUrl.value), 0) + // DPlayer组件会自动处理视频URL变化 + // setTimeout(() => initCKPlayer(currentVideoUrl.value), 0) } } } @@ -1048,8 +1066,8 @@ const handleVideoPlay = async (section: CourseSection) => { // 等待DOM更新 await nextTick() - // 初始化CKPlayer播放器 - initCKPlayer(videoUrl) + // DPlayer组件会自动处理视频URL变化 + // initCKPlayer(videoUrl) // 标记为已完成 if (!section.completed) { @@ -1061,8 +1079,11 @@ const handleVideoPlay = async (section: CourseSection) => { } } -// 初始化CKPlayer播放器 -const initCKPlayer = (url: string) => { +// 初始化CKPlayer播放器 - 已替换为DPlayer,注释掉避免冲突 +const initCKPlayer = (_url: string) => { + console.log('⚠️ CKPlayer已被DPlayer替换,跳过初始化') + return + /* // 清理之前的播放器实例 if (ckplayer.value) { try { @@ -1126,6 +1147,7 @@ const initCKPlayer = (url: string) => { } catch (error) { console.error('Failed to initialize CKPlayer:', error) } + */ } // CKPlayer回调函数 @@ -1203,18 +1225,22 @@ const handleExam = (section: CourseSection) => { }) } -// 视频事件处理 -// const handleVideoLoadStart = () => { -// console.log('视频开始加载') -// } +// DPlayer视频事件处理 +const onVideoPlay = () => { + console.log('▶️ 视频开始播放') +} -// const handleVideoCanPlay = () => { -// console.log('视频可以播放') -// } +const onVideoPause = () => { + console.log('⏸️ 视频暂停') +} -// const handleVideoError = (event: Event) => { -// console.error('视频播放错误:', event) -// } +const onVideoEnded = () => { + console.log('⏹️ 视频播放结束') +} + +const onVideoError = (error: Event) => { + console.error('🚨 视频播放错误:', error) +} // 初始化模拟状态(已报名状态) const initializeEnrolledState = () => {