接下来,我将围绕一个核心的、功能丰富的应用场景------"沉浸式在线课程学习平台" ,来为您详细讲解 video
组件的这些关键属性和方法。我们将一步步构建这个应用,并在每个阶段自然地引入和解释相关的属性。
核心使用场景:构建一个功能完善的在线课程播放器
想象一下,我们正在为一款 HarmonyOS 上的在线教育应用开发一个新的课程播放页面。这个页面需要提供极致的用户体验,不仅要能播放视频,还要支持互动、提供良好的操作手感,并兼顾性能和各种网络环境。

第一阶段:基础播放器搭建与内容呈现
我们首先需要让视频能够播放,并展示课程的基本信息。
html
<!-- 在 pages/course-player/course-player.vue -->
<video
id="courseVideoPlayer"
:src="currentLesson.videoUrl"
:poster="currentLesson.coverImage"
:initial-time="userProgress.lastPlaybackTime"
:duration="currentLesson.videoDuration"
:title="currentLesson.title"
object-fit="contain"
@loadedmetadata="onVideoMetadataLoaded"
@error="onVideoError"
>
</video>
场景化属性说明:
src
: (String) 这是最核心的属性,我们将其绑定到currentLesson.videoUrl
。在实际应用中,这个 URL 是从服务器API获取的,代表当前正在学习的这节课的视频资源地址。poster
: (String) 视频加载时,我们不希望用户看到黑屏。这里我们使用currentLesson.coverImage
作为视频封面,通常是这节课精心设计的宣传图,给用户一个专业的初印象。initial-time
: (Number) 这是提升用户体验的关键。如果用户上次没有看完这节课,userProgress.lastPlaybackTime
会记录他上次播放到的秒数。当用户再次进入页面时,视频会从他上次离开的地方继续播放,实现无缝学习体验。duration
: (Number) 虽然播放器通常能自动获取视频时长,但有时在视频元数据加载完成前,我们就想在界面上展示总时长(例如:05:32 / 15:00
)。通过API预先获取并设置currentLesson.videoDuration
,可以优化界面的初始加载显示。title
: (String) 当用户将视频全屏播放时,我们不希望他忘记当前在看什么。title
属性会在全屏模式的顶部显示课程标题,如"第一章:HarmonyOS应用开发入门",时刻保持上下文清晰。object-fit
: (String) 对于教学视频,内容的完整性至关重要。我们设置为contain
,确保视频的每一寸画面都能被看见,即使视频的宽高比与我们的播放器容器不完全匹配,也不会出现裁剪。如果这是个宣传视频,我们可能会用cover
来获得更具冲击力的全屏填充视觉效果。
相关事件:
@loadedmetadata
: 当视频的元数据(如真实时长、宽高)加载完毕后,此事件被触发。我们可以在onVideoMetadataLoaded
方法中获取event.detail.duration
,并用它来校准我们界面上显示的总时长,确保精确性。@error
: 网络波动、视频源损坏等问题都可能导致播放失败。onVideoError
回调可以捕获这些错误,然后我们可以给用户一个友好的提示("哎呀,视频加载失败了,请检查网络后重试"),并上报错误日志给后台,便于我们定位问题。
第二阶段:增强用户交互与控制
一个光秃秃的视频并不友好,我们需要为用户提供丰富的控制选项和交互体验。
html
<!-- 完整版 video 组件 -->
<video
... <!-- 沿用第一阶段的属性 -->
controls
:autoplay="false"
:loop="false"
:muted="isMuted"
:show-play-btn="true"
:show-center-play-btn="true"
:show-fullscreen-btn="true"
:show-progress="true"
:show-loading="true"
play-btn-position="bottom"
@play="onPlay"
@pause="onPause"
@ended="onEnded"
@timeupdate="onTimeUpdate"
>
</video>
场景化属性说明:
controls
: (Boolean) 我们将其设置为true
,为用户提供默认的播放控件,包括播放/暂停按钮、进度条和时间显示。这是播放器的基本功能。autoplay
: (Boolean) 在一个课程应用中,我们通常不希望视频自动播放,因为用户可能只是想进来看看课程介绍。所以设置为false
,让用户主动点击播放,掌控自己的学习节奏。loop
: (Boolean) 教学视频一般不需要循环播放,所以设置为false
。课程结束后,用户应该手动选择重看或进入下一节。muted
: (Boolean) 我们可以有一个静音按钮,用户点击后改变isMuted
这个变量的值。比如在图书馆等安静环境下,用户可以一键静音,只看字幕学习。show-play-btn
,show-center-play-btn
,show-fullscreen-btn
,show-progress
: 这些都设为true
,确保控制栏功能齐全,用户可以方便地控制播放、全屏和查看进度。show-loading
: (Boolean) 设为true
。当网络不好视频卡顿时,显示一个加载中的loading
动画,明确告知用户"正在缓冲",而不是让用户误以为应用卡死了。play-btn-position
: (String) 我们选择bottom
,让播放/暂停按钮集成在底部的控制栏上,这是一种常见且符合用户习惯的设计。如果想突出播放按钮,也可以设为center
。
相关事件:
@play
/@pause
: 当视频开始播放或暂停时触发。我们可以在onPlay
和onPause
方法中记录用户的行为,用于数据分析,了解用户的学习习惯。同时,onPause
也是保存当前播放进度的绝佳时机(更新userProgress.lastPlaybackTime
)。@ended
: 当视频播放结束时,onEnded
事件被触发。在这里我们可以实现自动连播功能:弹窗询问用户"是否要学习下一节?",或者直接在3秒后自动跳转到下一节课。@timeupdate
: 这个事件会以约250ms的频率持续触发,返回{currentTime, duration}
。这是我们实现学习进度跟踪 的核心。我们可以每隔几秒就通过onTimeUpdate
将currentTime
上报给服务器,这样即使用户意外关闭App,也能恢复到几乎相同的位置。同时,我们也可以用它来实现"学习到特定时间点,弹出随堂测验"的功能。
第三阶段:高级交互与沉浸式体验
为了让我们的应用脱颖而出,我们需要加入一些更高级的功能,比如弹幕互动和手势操作。
html
<!-- 最终的"导演剪辑版" video 组件 -->
<video
... <!-- 沿用前两阶段的属性 -->
:danmu-list="danmuData"
:enable-danmu="isDanmuEnabled"
danmu-btn
:enable-play-gesture="true"
:enable-progress-gesture="true"
:vslide-gesture="true"
:vslide-gesture-in-fullscreen="true"
:direction="90"
:page-gesture="true"
@fullscreenchange="onFullscreenChange"
>
</video>
场景化属性说明:
danmu-list
,enable-danmu
,danmu-btn
: 为了增加学习的互动性和趣味性,我们引入了弹幕功能。danmu-list
: 绑定一个从服务器获取的弹幕数组danmuData
,格式为[{text: '这个知识点很重要!', color: '#ff0000', time: 125}]
。enable-danmu
: 绑定一个布尔值isDanmuEnabled
,用户可以通过一个开关来决定是否显示弹幕。danmu-btn
: 在控制栏上显示一个弹幕开关按钮,方便用户操作。
- 手势操作系列 :
enable-play-gesture
: (Boolean) 设为true
,用户现在可以双击视频区域来切换播放/暂停,这是现代短视频应用中非常流行的手势,非常便捷。enable-progress-gesture
: (Boolean) 设为true
,用户可以在视频上左右滑动来快进或快退,比拖动细小的进度条更加方便。vslide-gesture
/page-gesture
: (Boolean) 都设为true
。用户可以在视频区域上下滑动来调节音量和亮度(通常是左侧屏幕亮度,右侧屏幕音量),在任何环境下都能快速调整,极大地提升了观看体验。vslide-gesture-in-fullscreen
: (Boolean) 设为true
,确保调节音量和亮度的手势在全屏模式下依然有效。
direction
: (Number) 假设我们有一门"手机摄影"课,其中一节课是专门讲解如何拍竖构图照片的。为了获得最佳观看效果,我们可以将direction
设为90
或-90
,强制视频在全屏时横屏显示,即使用户的手机锁定了竖屏。
相关事件:
@fullscreenchange
: 当用户进入或退出全屏时触发。我们可以在onFullscreenChange
方法中获取event.detail.fullScreen
状态。例如,当用户进入全屏时,我们可以隐藏页面的其他元素(如课程列表),让用户完全沉浸在视频中;退出全屏时再将它们显示出来。
第四阶段:性能优化与特殊配置
作为开发者,我们不仅要关注功能,更要关注性能、兼容性和特殊场景。
html
<video
... <!-- 沿用所有用户可见的属性 -->
codec="hardware"
http-cache
play-strategy="2"
:header="requestHeaders"
mobilenet-hint-type="1"
:auto-pause-if-navigate="true"
:auto-pause-if-open-native="true"
is-live="false"
ad-unit-id="your-ad-id"
poster-for-crawler="currentLesson.seoCoverImage"
>
</video>
场景化属性说明:
codec
: (String) 我们选择hardware
(硬解码)。这能利用设备的硬件加速能力,在播放高清视频时更流畅、更省电。只有在遇到特定老旧设备兼容性问题时,我们才会考虑降级为software
(软解码)。http-cache
: (Boolean) 设置为true
。对于可以反复学习的课程视频,开启本地缓存意义重大。用户第一次观看后,视频文件会缓存到本地。当他再次学习或回顾时,视频会直接从本地加载,秒开且不消耗流量,极大提升了复习体验。play-strategy
: (Number) 如果我们的课程视频源是M3U8
格式,我们应将此项设为2
(M3U8优化模式)。这会优化缓冲区策略,提升在线流媒体的加载速度和播放流畅度,减少卡顿。header
: (Object) 假设我们的视频资源存放于需要鉴权的服务器上,我们可以将认证token
放入requestHeaders
对象中,例如{ Authorization: 'Bearer your-jwt-token' }
。这样video
组件在请求视频流时,会带上这个请求头,确保只有付费用户才能播放。mobilenet-hint-type
: (Number) 设为1
。当用户未使用Wi-Fi时,播放前会提醒用户"当前为移动网络,继续播放将消耗流量",避免用户在不知情的情况下产生高额流量费,非常贴心。auto-pause-if-navigate
/auto-pause-if-open-native
: (Boolean) 都设为true
。这是优秀应用必备的细节。当用户在观看视频时,如果跳转到应用的其他页面(如查看讲师介绍),或者接听电话,视频会自动暂停。这可以防止声音干扰,也方便用户回来后继续观看。is-live
: (Boolean) 我们的课程是点播,所以设为false
。但如果我们的平台未来要上线"名师直播课",只需将这个属性设为true
,播放器就会进入直播模式,通常会隐藏进度条,并优化延迟。ad-unit-id
: (String) 如果我们想通过广告盈利,可以在这里配置一个"视频前贴广告"的单元ID。这样在课程视频播放前,会先播放一段广告。poster-for-crawler
: (String) 这个属性用于搜索引擎优化(SEO)。我们可以提供一个不带播放按钮的、干净的封面图currentLesson.seoCoverImage
,让搜索引擎能更好地抓取和展示我们的课程内容。
总结与代码示例
通过上述四个阶段的构建,我们已经将一个简单的 video
标签,变成了一个功能强大、体验优秀、架构合理的在线课程播放器。学习者不仅知道了每个API的作用,更重要的是理解了它们是如何在一个真实、复杂的场景中协同工作,共同服务于最终的用户体验和业务目标的。
最终完整代码片段示例:
js
<template>
<view class="course-player-container">
<!-- 视频播放器 -->
<video
id="courseVideoPlayer"
class="video-player"
:src="currentLesson.videoUrl"
:poster="currentLesson.coverImage"
:title="currentLesson.title"
:initial-time="userProgress.lastPlaybackTime"
:duration="currentLesson.videoDuration"
controls
:autoplay="false"
:loop="false"
:muted="isMuted"
object-fit="contain"
:danmu-list="danmuData"
:enable-danmu="isDanmuEnabled"
vslide-gesture-in-fullscreen
danmu-btn
:enable-play-gesture="true"
:enable-progress-gesture="true"
:vslide-gesture="true"
http-cache
codec="hardware"
play-strategy="0"
:header="requestHeaders"
@play="onPlay"
@pause="onPause"
@ended="onEnded"
@timeupdate="onTimeUpdate"
@error="onVideoError"
@fullscreenchange="onFullscreenChange"
></video>
<view class="danmu-controls">
<input class="danmu-input" v-model="danmuValue" type="text" placeholder="发个弹幕,见证你的学习" />
<button class="send-btn" @click="sendDanmu">发送</button>
</view>
<!-- 其他UI,如课程列表、笔记区、弹幕输入框等 -->
<view class="controls-extra">
<button @click="toggleMute">{{ isMuted ? '取消静音' : '静音' }}</button>
<button @click="toggleDanmu">{{ isDanmuEnabled ? '关闭弹幕' : '开启弹幕' }}</button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
currentLesson: {
videoUrl: 'https://lf-cdn.trae.com.cn/obj/trae-com-cn/bannerIntro425.mp4',
coverImage: 'jpg',
title: '第一章:HarmonyOS应用开发入门',
videoDuration: 60 // 15分钟
},
userProgress: {
lastPlaybackTime: 30 // 用户上次看到第2分钟
},
isMuted: false,
isDanmuEnabled: true,
danmuData: [
{ text: '老师讲得真好', color: '#ffffff', time: 10 },
{ text: '这个知识点很重要!', color: '#ff0000', time: 125 }
],
requestHeaders: {
Authorization: 'Bearer your-jwt-token'
},
danmuData: [
{ text: '终于开课了,前排占座!', color: '#ffffff', time: 1 },
{ text: '老师好帅啊哈哈哈', color: '#33ff55', time: 3 },
{ text: '注意!这里是第一个知识点', color: '#ffcc00', time: 15 },
{ text: '有点没听懂,有同学解释下吗?', color: '#ff8888', time: 45 },
{ text: '我懂了,就是把 A 和 B 关联起来', color: '#88ddff', time: 52 },
{ text: '高能预警!这段代码是重点', color: '#ff0000', time: 120 },
{ text: '暂停一下,我记个笔记', color: '#ffffff', time: 125 },
{ text: '感谢楼上同学的解释,茅塞顿开!', color: '#33ff55', time: 130 },
{ text: '坚持就是胜利!大家加油!', color: '#ffcc00', time: 300 }
],
danmuValue: '', // 用于绑定输入框的内容
videoContext: null // 用于存放 video 上下文对象
};
},
onReady: function (res) {
// 通过 id 获取 video 组件的上下文 videoContext
this.videoContext = uni.createVideoContext('courseVideoPlayer', this);
},
methods: {
toggleMute() {
this.isMuted = !this.isMuted;
},
toggleDanmu() {
this.isDanmuEnabled = !this.isDanmuEnabled;
},
onPlay() {
console.log('视频开始播放');
},
onPause() {
console.log('视频暂停,保存当前进度');
// 此处应有API调用,将 this.userProgress.lastPlaybackTime 上传服务器
},
onEnded() {
console.log('视频播放结束');
// 弹出提示,询问是否播放下一节
},
onTimeUpdate(e) {
// 节流处理,避免过于频繁的更新
this.userProgress.lastPlaybackTime = e.detail.currentTime;
// console.log(`当前播放到:${e.detail.currentTime}s`);
},
onVideoError(e) {
uni.showToast({ title: '视频加载失败,请重试', icon: 'none' });
console.error('视频错误:', e.detail.errMsg);
},
onFullscreenChange(e) {
console.log('全屏状态改变:', e.detail.fullScreen);
// 在此可以根据全屏状态调整页面UI
},
// 生成一个随机颜色
getRandomColor() {
const rgb = [];
for (let i = 0; i < 3; ++i) {
let color = Math.floor(Math.random() * 256).toString(16);
color = color.length == 1 ? '0' + color : color;
rgb.push(color);
}
return '#' + rgb.join('');
},
// 发送弹幕的方法
sendDanmu() {
// 1. 输入内容校验
if (!this.danmuValue || this.danmuValue.trim() === '') {
uni.showToast({
title: '弹幕内容不能为空哦',
icon: 'none'
});
return;
}
// 2. 调用 videoContext 的 sendDanmu 方法
this.videoContext.sendDanmu({
text: this.danmuValue,
color: this.getRandomColor() // 为了好玩,我们用随机颜色
});
// 3. 清空输入框
this.danmuValue = '';
}
}
};
</script>
<style>
.course-player-container {
width: 100%;
}
.video-player {
width: 100%;
}
.danmu-controls {
display: flex;
flex-direction: row;
align-items: center;
padding: 10px;
background-color: #f1f1f1;
}
.danmu-input {
flex-grow: 1;
height: 35px;
line-height: 35px;
padding: 0 10px;
border-radius: 5px;
background-color: #ffffff;
border: 1px solid #e0e0e0;
}
.send-btn {
margin-left: 10px;
background-color: #007aff;
color: white;
font-size: 14px;
padding: 0 15px;
height: 35px;
line-height: 35px;
}
</style>