HarmonyOS 5.0教育行业解决方案:基于分布式能力的沉浸式智慧课堂系统
本文基于HarmonyOS 5.0.0版本,深入讲解如何利用分布式软总线、投屏流转与实时音视频技术,构建跨设备协同的智慧课堂系统。通过完整案例演示多屏互动教学、学生终端管控、课堂数据实时采集等核心场景,为教育信息化2.0提供可落地的鸿蒙技术方案。无感接入:学生设备通过分布式软总线自动发现,无需复杂配置实时互动:笔迹同步延迟<50ms,答题结果秒级统计,支持教学决策学情洞察:多维度注意力监测,生成个
·
文章目录

每日一句正能量
努力就是光,成功就是影,没有光那么你又是哪儿来影,时间会来敲门,岁月从来无声,若是你现在还会心动,还是会愤怒,还是会悲伤,那么也就请暗自去庆幸,你还年轻,当你不能再被感动,不能再被激怒,不会再流泪,你便会知道,为了成长,你失去了什么,早安!
前言
摘要: 本文基于HarmonyOS 5.0.0版本,深入讲解如何利用分布式软总线、投屏流转与实时音视频技术,构建跨设备协同的智慧课堂系统。通过完整案例演示多屏互动教学、学生终端管控、课堂数据实时采集等核心场景,为教育信息化2.0提供可落地的鸿蒙技术方案。
一、教育信息化痛点与鸿蒙机遇
1.1 传统智慧教室困境
当前教育信息化面临设备割裂、互动低效、数据孤岛三大难题:
| 场景痛点 | 传统方案缺陷 | 鸿蒙解决思路 |
|---|---|---|
| 多屏互动 | 依赖第三方投屏协议,延迟高、兼容性差 | 分布式软总线原生支持,延迟<50ms |
| 学生终端管控 | 安装管控APP,易被卸载绕过 | 系统级分布式能力,无感接入 |
| 课堂数据采集 | 各设备数据格式不一,汇总困难 | 统一数据模型,实时汇聚分析 |
| 课后复习 | 课堂板书、录音、笔记分散 | 一键生成结构化学习档案 |
1.2 HarmonyOS 5.0教育技术栈
┌─────────────────────────────────────────────────────────────┐
│ 教师端(平板/PC) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ 课件演示 │ │ 白板书写 │ │ 学生学情看板 │ │
│ │ 分布式投屏 │ │ 笔迹同步 │ │ 实时数据汇聚 │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
└──────────────────────────┬──────────────────────────────────┘
│ 分布式软总线 + HiStreamer
┌──────────────────────────▼──────────────────────────────────┐
│ 教室中枢(智慧大屏/网关) │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 课堂会话管理 设备发现与认证 媒体流路由转发 │ │
│ │ 教学数据缓存 离线包生成 本地AI推理 │ │
│ └───────────────────────────────────────────────────────┘ │
└──────────────────────────┬──────────────────────────────────┘
│
┌──────────────────┼──────────────────┐
│ │ │
┌───────▼──────┐ ┌────────▼────────┐ ┌─────▼──────┐
│ 学生平板 │ │ 学生手机/手表 │ │ 实验设备 │
│ 互动答题 │ │ 状态监测 │ │ 数据采集 │
│ 笔记同步 │ │ 注意力提醒 │ │ 远程控制 │
└──────────────┘ └─────────────────┘ └────────────┘
二、系统架构设计
2.1 核心模块划分
entry/src/main/ets/
├── education/
│ ├── classroom/
│ │ ├── SessionManager.ts # 课堂会话管理
│ │ ├── DeviceAuthenticator.ts # 设备认证
│ │ └── TeachingActivity.ts # 教学活动编排
│ ├── interaction/
│ │ ├── ScreenCasting.ts # 分布式投屏
│ │ ├── WhiteboardSync.ts # 白板同步
│ │ ├── QuizEngine.ts # 互动答题
│ │ └── AttentionMonitor.ts # 注意力监测
│ ├── media/
│ │ ├── AudioRouter.ts # 音频路由
│ │ ├── VideoMixer.ts # 视频合成
│ │ └── StreamRecorder.ts # 流录制
│ └── analytics/
│ ├── LearningTracker.ts # 学习轨迹
│ ├── EngagementAnalyzer.ts # 参与度分析
│ └── ReportGenerator.ts # 报告生成
├── student/
│ ├── StudentClient.ts # 学生端核心
│ ├── NoteSync.ts # 笔记同步
│ └── FocusGuard.ts # 专注模式
└── pages/
├── TeacherDashboard.ets # 教师主控台
├── StudentWorkspace.ets # 学生工作区
└── ClassReplay.ets # 课堂回放
三、核心代码实现
3.1 课堂会话管理系统
基于分布式软总线实现教室设备发现与组网:
// education/classroom/SessionManager.ts
import { distributedDeviceManager } from '@kit.DistributedServiceKit'
import { distributedDataObject } from '@kit.ArkData'
interface ClassroomSession {
sessionId: string
teacherDevice: DeviceInfo
studentDevices: Map<string, StudentDeviceInfo>
startTime: number
subject: string
grade: string
status: 'preparing' | 'teaching' | 'paused' | 'ended'
}
interface StudentDeviceInfo {
deviceId: string
deviceName: string
studentId?: string
studentName?: string
seatPosition?: [number, number] // 座位坐标
capabilities: Array<'screen' | 'audio' | 'camera' | 'sensor'>
joinTime: number
status: 'connected' | 'disconnected' | 'focus_lost'
lastHeartbeat: number
}
export class SessionManager {
private deviceManager: distributedDeviceManager.DeviceManager | null = null
private currentSession: ClassroomSession | null = null
private distributedSession: distributedDataObject.DistributedObject | null = null
// 设备状态监听
private deviceStateListeners: Array<(deviceId: string, state: string) => void> = []
private heartbeatTimer: number | null = null
async initialize(): Promise<void> {
// 初始化分布式设备管理
this.deviceManager = distributedDeviceManager.createDeviceManager(
getContext(this).bundleName
)
// 注册设备状态监听
this.deviceManager.on('deviceStateChange', (data) => {
this.handleDeviceStateChange(data)
})
// 启动心跳检测
this.startHeartbeatCheck()
console.info('[SessionManager] Initialized')
}
async createSession(classInfo: { subject: string; grade: string }): Promise<string> {
const sessionId = `CLS_${Date.now()}_${Math.random().toString(36).substr(2, 6)}`
// 获取本机(教师端)信息
const localDevice = this.deviceManager!.getLocalDeviceNetworkId()
const deviceInfo = this.deviceManager!.getDeviceInfo(localDevice)
this.currentSession = {
sessionId,
teacherDevice: {
networkId: localDevice,
deviceName: deviceInfo.deviceName,
deviceType: 'teacher_tablet'
},
studentDevices: new Map(),
startTime: Date.now(),
subject: classInfo.subject,
grade: classInfo.grade,
status: 'preparing'
}
// 创建分布式数据对象,同步课堂状态
this.distributedSession = distributedDataObject.create(
getContext(this),
sessionId,
{
sessionInfo: this.currentSession,
currentSlide: 0,
whiteboardStrokes: [],
activeQuiz: null,
attendance: []
}
)
await this.distributedSession.setSessionId(sessionId)
// 广播课堂发现信息
this.broadcastSessionDiscovery(sessionId, classInfo)
console.info(`[SessionManager] Session created: ${sessionId}`)
return sessionId
}
async admitStudent(
deviceId: string,
studentInfo: { studentId: string; studentName: string; seatPosition: [number, number] }
): Promise<void> {
if (!this.currentSession) throw new Error('No active session')
const deviceInfo = this.deviceManager!.getDeviceInfo(deviceId)
const studentDevice: StudentDeviceInfo = {
deviceId,
deviceName: deviceInfo.deviceName,
studentId: studentInfo.studentId,
studentName: studentInfo.studentName,
seatPosition: studentInfo.seatPosition,
capabilities: this.detectCapabilities(deviceId),
joinTime: Date.now(),
status: 'connected',
lastHeartbeat: Date.now()
}
this.currentSession.studentDevices.set(deviceId, studentDevice)
// 更新分布式数据
this.distributedSession!.studentDevices = Array.from(
this.currentSession.studentDevices.values()
)
// 向学生端发送准入确认
await this.sendAdmissionConfirmation(deviceId, {
sessionId: this.currentSession.sessionId,
permissions: ['view_screen', 'submit_answer', 'sync_note'],
restrictions: { app_whitelist: ['com.example.education', 'com.huawei.notepad'] }
})
console.info(`[SessionManager] Student admitted: ${studentInfo.studentName}`)
}
private detectCapabilities(deviceId: string): Array<string> {
const capabilities: Array<string> = []
const deviceInfo = this.deviceManager!.getDeviceInfo(deviceId)
// 根据设备类型判断能力
if (deviceInfo.deviceType === 'tablet' || deviceInfo.deviceType === 'phone') {
capabilities.push('screen', 'audio', 'camera')
}
if (deviceInfo.deviceType === 'wearable') {
capabilities.push('sensor') // 心率、注意力监测
}
return capabilities
}
private handleDeviceStateChange(data: distributedDeviceManager.DeviceStateChangeData): void {
const { deviceId, state } = data
if (this.currentSession?.studentDevices.has(deviceId)) {
const device = this.currentSession.studentDevices.get(deviceId)!
device.status = state === 0 ? 'connected' : 'disconnected'
device.lastHeartbeat = Date.now()
// 通知业务层
this.deviceStateListeners.forEach(listener => listener(deviceId, device.status))
// 如果教师设备掉线,自动暂停课堂
if (deviceId === this.currentSession.teacherDevice.networkId && state !== 0) {
this.pauseSession('教师设备离线')
}
}
}
private startHeartbeatCheck(): void {
this.heartbeatTimer = setInterval(() => {
if (!this.currentSession) return
const now = Date.now()
const timeout = 10000 // 10秒无心跳视为离线
for (const [deviceId, device] of this.currentSession.studentDevices) {
if (now - device.lastHeartbeat > timeout && device.status === 'connected') {
device.status = 'disconnected'
console.warn(`[SessionManager] Device timeout: ${deviceId}`)
this.deviceStateListeners.forEach(listener => listener(deviceId, 'timeout'))
}
}
}, 5000)
}
async startTeaching(): Promise<void> {
if (!this.currentSession) throw new Error('No active session')
this.currentSession.status = 'teaching'
this.distributedSession!.status = 'teaching'
// 向所有学生端广播开课
await this.broadcastToStudents({ type: 'class_start', timestamp: Date.now() })
}
async endSession(): Promise<ClassroomArchive> {
if (!this.currentSession) throw new Error('No active session')
// 生成课堂档案
const archive: ClassroomArchive = {
sessionId: this.currentSession.sessionId,
duration: Date.now() - this.currentSession.startTime,
attendance: Array.from(this.currentSession.studentDevices.values()).map(d => ({
studentId: d.studentId!,
studentName: d.studentName!,
joinTime: d.joinTime,
leaveTime: d.status === 'connected' ? Date.now() : d.lastHeartbeat,
participationScore: this.calculateParticipation(d.studentId!)
})),
teachingData: await this.exportTeachingData()
}
// 清理资源
this.currentSession.status = 'ended'
await this.distributedSession!.setSessionId('') // 退出分布式组网
return archive
}
onDeviceStateChange(listener: (deviceId: string, state: string) => void): void {
this.deviceStateListeners.push(listener)
}
getCurrentSession(): ClassroomSession | null {
return this.currentSession
}
private async broadcastToStudents(message: object): Promise<void> {
// 使用分布式消息广播
for (const deviceId of this.currentSession!.studentDevices.keys()) {
// 实际实现使用软总线消息通道
}
}
private calculateParticipation(studentId: string): number {
// 基于答题、互动、笔记等计算参与度
return 85 // 示例
}
destroy(): void {
if (this.heartbeatTimer) {
clearInterval(this.heartbeatTimer)
}
this.deviceManager?.release()
}
}
3.2 分布式投屏与白板同步
实现教师端内容实时同步到学生设备:
// education/interaction/ScreenCasting.ts
import { avCastPicker } from '@kit.ArkUI'
import { media } from '@kit.MediaKit'
export class ScreenCastingManager {
private castPicker: avCastPicker.AVCastPicker | null = null
private activeCasts: Map<string, media.AVScreenCapture> = new Map()
private whiteboardStrokes: Array<StrokeData> = []
private strokeSyncInterval: number | null = null
async startCasting(targetDevices: Array<string>): Promise<void> {
// 启动屏幕采集
const captureConfig: media.AVScreenCaptureConfig = {
captureMode: media.CaptureMode.CAPTURE_HOME_SCREEN,
videoInfo: {
videoFrameWidth: 1920,
videoFrameHeight: 1080,
videoSourceType: media.VideoSourceType.VIDEO_SOURCE_TYPE_SURFACE,
videoFrameRate: 30
},
audioInfo: {
audioSampleRate: 48000,
audioChannels: 2,
audioSource: media.AudioSourceType.AUDIO_SOURCE_TYPE_SYSTEM_PLAYBACK
}
}
const screenCapture = media.createAVScreenCapture()
await screenCapture.init(captureConfig)
// 为每个目标设备建立投屏流
for (const deviceId of targetDevices) {
const castSession = await this.establishCastStream(deviceId, screenCapture)
this.activeCasts.set(deviceId, castSession)
}
await screenCapture.startRecording()
console.info(`[ScreenCasting] Started casting to ${targetDevices.length} devices`)
}
private async establishCastStream(
deviceId: string,
capture: media.AVScreenCapture
): Promise<media.AVScreenCapture> {
// 使用鸿蒙分布式媒体流能力
const streamId = `cast_${deviceId}_${Date.now()}`
// 配置编码参数(H.264硬件编码)
const encoderConfig: media.VideoEncoderConfig = {
codec: media.CodecMimeType.VIDEO_AVC,
bitrate: 4 * 1024 * 1024, // 4Mbps
frameRate: 30,
width: 1920,
height: 1080
}
// 建立到目标设备的流媒体通道
// 实际实现使用HiStreamer分布式媒体框架
return capture
}
// 白板笔迹同步(独立高频通道)
startWhiteboardSync(sessionManager: SessionManager): void {
// 每50ms同步一次笔迹数据
this.strokeSyncInterval = setInterval(() => {
if (this.whiteboardStrokes.length === 0) return
const batch = this.whiteboardStrokes.splice(0, 10) // 批量发送
sessionManager.broadcastToStudents({
type: 'whiteboard_sync',
strokes: batch,
timestamp: Date.now()
})
}, 50)
}
addStroke(stroke: StrokeData): void {
// 本地渲染
this.renderLocalStroke(stroke)
// 加入同步队列
this.whiteboardStrokes.push({
...stroke,
sequence: this.whiteboardStrokes.length
})
// 若队列过长,加速同步
if (this.whiteboardStrokes.length > 50) {
// 触发立即同步
}
}
private renderLocalStroke(stroke: StrokeData): void {
// 使用Canvas绘制
const canvas = AppStorage.get<CanvasRenderingContext2D>('whiteboardCanvas')
if (!canvas) return
canvas.beginPath()
canvas.strokeStyle = stroke.color
canvas.lineWidth = stroke.width
canvas.lineCap = 'round'
canvas.lineJoin = 'round'
stroke.points.forEach((point, index) => {
if (index === 0) {
canvas.moveTo(point.x, point.y)
} else {
canvas.lineTo(point.x, point.y)
}
})
canvas.stroke()
}
async stopCasting(): Promise<void> {
// 停止所有投屏
for (const [deviceId, capture] of this.activeCasts) {
await capture.stopRecording()
await capture.release()
}
this.activeCasts.clear()
if (this.strokeSyncInterval) {
clearInterval(this.strokeSyncInterval)
}
}
}
// 学生端笔迹接收与渲染
export class WhiteboardReceiver {
private strokeBuffer: Array<StrokeData> = []
private renderPending: boolean = false
constructor() {
// 监听教师端笔迹数据
emitter.on('whiteboard_sync', (data) => {
this.receiveStrokes(data.strokes)
})
}
private receiveStrokes(strokes: Array<StrokeData>): void {
// 按序列号排序,处理乱序到达
this.strokeBuffer.push(...strokes)
this.strokeBuffer.sort((a, b) => a.sequence - b.sequence)
// 请求渲染帧
if (!this.renderPending) {
this.renderPending = true
requestAnimationFrame(() => this.renderStrokes())
}
}
private renderStrokes(): void {
const canvas = AppStorage.get<CanvasRenderingContext2D>('studentWhiteboardCanvas')
if (!canvas) {
this.renderPending = false
return
}
// 批量渲染,控制每帧处理量避免卡顿
const batchSize = 5
const toRender = this.strokeBuffer.splice(0, batchSize)
toRender.forEach(stroke => {
canvas.beginPath()
canvas.strokeStyle = stroke.color
canvas.lineWidth = stroke.width
stroke.points.forEach((point, idx) => {
if (idx === 0) canvas.moveTo(point.x, point.y)
else canvas.lineTo(point.x, point.y)
})
canvas.stroke()
})
// 若还有数据,继续请求下一帧
if (this.strokeBuffer.length > 0) {
requestAnimationFrame(() => this.renderStrokes())
} else {
this.renderPending = false
}
}
}
3.3 互动答题引擎
实现课堂实时互动与数据采集:
// education/interaction/QuizEngine.ts
interface QuizQuestion {
id: string
type: 'single_choice' | 'multiple_choice' | 'fill_blank' | 'open_ended'
content: string
options?: Array<{ id: string; text: string }>
correctAnswer?: string | Array<string>
points: number
timeLimit?: number // 秒,0表示不限时
}
interface StudentAnswer {
studentId: string
questionId: string
answer: string | Array<string>
submitTime: number
timeSpent: number // 答题用时(毫秒)
confidence?: number // 学生自评置信度(1-5)
}
interface QuizResult {
questionId: string
totalParticipants: number
answerDistribution: Map<string, number> // 选项分布
correctRate: number
averageTime: number
topStudents: Array<{ studentId: string; timeSpent: number }> // 最快答对学生
}
export class QuizEngine {
private currentQuiz: {
questions: Array<QuizQuestion>
startTime: number
status: 'idle' | 'showing' | 'answering' | 'reviewing'
currentQuestionIndex: number
answers: Map<string, Array<StudentAnswer>> // questionId -> answers
} = {
questions: [],
startTime: 0,
status: 'idle',
currentQuestionIndex: 0,
answers: new Map()
}
private answerSubmissions: Map<string, StudentAnswer> = new Map() // 当前题答案
private timer: number | null = null
private onResultUpdate: ((result: QuizResult) => void) | null = null
loadQuiz(questions: Array<QuizQuestion>): void {
this.currentQuiz.questions = questions
this.currentQuiz.answers.clear()
questions.forEach(q => this.currentQuiz.answers.set(q.id, []))
}
async startQuestion(index: number): Promise<void> {
if (index >= this.currentQuiz.questions.length) return
this.currentQuiz.currentQuestionIndex = index
this.currentQuiz.status = 'showing'
this.answerSubmissions.clear()
const question = this.currentQuiz.questions[index]
// 广播题目到学生端
await this.broadcastQuestion(question)
// 延迟5秒后开始答题(给学生阅读时间)
setTimeout(() => {
this.openAnswerChannel(question)
}, 5000)
}
private async broadcastQuestion(question: QuizQuestion): Promise<void> {
// 通过分布式消息发送题目
const message = {
type: 'quiz_question',
question: {
id: question.id,
type: question.type,
content: question.content,
options: question.options,
timeLimit: question.timeLimit
},
displayTime: Date.now()
}
// 发送到所有学生设备
emitter.emit('broadcast_to_students', message)
// 教师端显示题目
AppStorage.setOrCreate('currentQuestion', question)
}
private openAnswerChannel(question: QuizQuestion): void {
this.currentQuiz.status = 'answering'
this.currentQuiz.startTime = Date.now()
// 启动计时器
if (question.timeLimit && question.timeLimit > 0) {
let remaining = question.timeLimit
this.timer = setInterval(() => {
remaining--
AppStorage.setOrCreate('quizCountdown', remaining)
if (remaining <= 0) {
this.closeQuestion()
}
}, 1000)
}
// 监听学生答案提交
emitter.on('student_answer', (data: StudentAnswer) => {
this.handleAnswer(data)
})
}
private handleAnswer(answer: StudentAnswer): void {
// 防重复提交
if (this.answerSubmissions.has(answer.studentId)) return
// 记录答题用时
answer.timeSpent = Date.now() - this.currentQuiz.startTime
this.answerSubmissions.set(answer.studentId, answer)
// 实时更新统计(匿名)
this.updateRealtimeStats()
// 若所有学生都已提交,提前结束
const sessionManager = AppStorage.get<SessionManager>('sessionManager')
if (this.answerSubmissions.size >= sessionManager!.getCurrentSession()!.studentDevices.size) {
this.closeQuestion()
}
}
private updateRealtimeStats(): void {
const question = this.currentQuiz.questions[this.currentQuiz.currentQuestionIndex]
const answers = Array.from(this.answerSubmissions.values())
// 统计选项分布
const distribution = new Map<string, number>()
answers.forEach(a => {
const key = Array.isArray(a.answer) ? a.answer.join(',') : a.answer
distribution.set(key, (distribution.get(key) || 0) + 1)
})
// 计算正确率
let correctCount = 0
if (question.correctAnswer) {
answers.forEach(a => {
const isCorrect = Array.isArray(question.correctAnswer)
? JSON.stringify((a.answer as Array<string>).sort()) === JSON.stringify((question.correctAnswer as Array<string>).sort())
: a.answer === question.correctAnswer
if (isCorrect) correctCount++
})
}
const result: QuizResult = {
questionId: question.id,
totalParticipants: answers.length,
answerDistribution: distribution,
correctRate: answers.length > 0 ? correctCount / answers.length : 0,
averageTime: answers.reduce((sum, a) => sum + a.timeSpent, 0) / answers.length,
topStudents: answers
.filter(a => {
if (!question.correctAnswer) return true
return Array.isArray(question.correctAnswer)
? JSON.stringify((a.answer as Array<string>).sort()) === JSON.stringify((question.correctAnswer as Array<string>).sort())
: a.answer === question.correctAnswer
})
.sort((a, b) => a.timeSpent - b.timeSpent)
.slice(0, 3)
.map(a => ({ studentId: a.studentId, timeSpent: a.timeSpent }))
}
// 更新教师端看板
this.onResultUpdate?.(result)
AppStorage.setOrCreate('quizRealtimeResult', result)
}
closeQuestion(): void {
if (this.timer) {
clearInterval(this.timer)
this.timer = null
}
this.currentQuiz.status = 'reviewing'
// 保存答案到课堂档案
const question = this.currentQuiz.questions[this.currentQuiz.currentQuestionIndex]
const answers = Array.from(this.answerSubmissions.values())
this.currentQuiz.answers.set(question.id, answers)
// 广播答题结束
emitter.emit('broadcast_to_students', { type: 'quiz_end' })
// 显示详细统计
this.updateRealtimeStats()
}
onRealtimeUpdate(callback: (result: QuizResult) => void): void {
this.onResultUpdate = callback
}
getQuizReport(): QuizReport {
const report: QuizReport = {
questions: this.currentQuiz.questions.map(q => {
const answers = this.currentQuiz.answers.get(q.id) || []
return {
question: q,
answerCount: answers.length,
correctRate: this.calculateCorrectRate(q, answers),
averageTime: answers.reduce((sum, a) => sum + a.timeSpent, 0) / answers.length,
commonMistakes: this.analyzeMistakes(q, answers)
}
}),
participationRate: this.calculateParticipationRate(),
classAverageScore: 0 // 计算略
}
return report
}
private calculateCorrectRate(question: QuizQuestion, answers: Array<StudentAnswer>): number {
if (!question.correctAnswer || answers.length === 0) return 0
// 实现略
return 0.75
}
private analyzeMistakes(question: QuizQuestion, answers: Array<StudentAnswer>): Array<string> {
// 分析常见错误选项,用于教师针对性讲解
return []
}
private calculateParticipationRate(): number {
// 计算整体参与率
return 0.95
}
}
3.4 学生端专注度监测
利用穿戴设备传感器数据评估课堂专注状态:
// education/interaction/AttentionMonitor.ts
import { sensor } from '@kit.SensorServiceKit'
interface AttentionData {
studentId: string
timestamp: number
attentionScore: number // 0-100
indicators: {
devicePosture: 'upright' | 'tilted' | 'flat' // 设备姿态
screenFocus: boolean // 是否注视屏幕(基于前置摄像头眼动追踪,可选)
heartRateVariability?: number // 心率变异性(穿戴设备)
interactionFrequency: number // 最近5分钟交互频次
}
}
export class AttentionMonitor {
private studentClients: Map<string, StudentAttentionTracker> = new Map()
private alertThreshold: number = 60 // 专注度低于60触发提醒
private onAttentionAlert: ((studentId: string, score: number) => void) | null = null
registerStudent(studentId: string, deviceCapabilities: Array<string>): void {
const tracker = new StudentAttentionTracker(studentId, deviceCapabilities)
tracker.onDataUpdate((data) => this.handleAttentionData(data))
this.studentClients.set(studentId, tracker)
}
private handleAttentionData(data: AttentionData): void {
// 更新教师端学情看板
const dashboard = AppStorage.get<Map<string, AttentionData>>('attentionDashboard')
dashboard?.set(data.studentId, data)
// 低专注度预警
if (data.attentionScore < this.alertThreshold) {
this.onAttentionAlert?.(data.studentId, data.attentionScore)
// 向学生端发送提醒
this.sendFocusReminder(data.studentId)
}
}
private sendFocusReminder(studentId: string): void {
emitter.emit('message_to_student', {
targetStudent: studentId,
message: {
type: 'focus_reminder',
content: '检测到注意力分散,请专注课堂',
gentle: true // 温和提醒,避免打扰
}
})
}
getClassAttentionOverview(): ClassAttentionStats {
const scores = Array.from(this.studentClients.values()).map(t => t.getLatestScore())
return {
averageScore: scores.reduce((a, b) => a + b, 0) / scores.length,
distribution: {
high: scores.filter(s => s >= 80).length,
medium: scores.filter(s => s >= 60 && s < 80).length,
low: scores.filter(s => s < 60).length
},
trend: this.calculateTrend(scores)
}
}
onLowAttention(callback: (studentId: string, score: number) => void): void {
this.onAttentionAlert = callback
}
}
// 单个学生的专注度追踪器(运行在平板端)
class StudentAttentionTracker {
private studentId: string
private accelerometer: sensor.Accelerometer | null = null
private gyroscope: sensor.Gyroscope | null = null
private latestScore: number = 100
private dataCallback: ((data: AttentionData) => void) | null = null
// 滑动窗口记录最近行为
private recentInteractions: Array<number> = [] // 时间戳
private postureHistory: Array<string> = []
constructor(studentId: string, capabilities: Array<string>) {
this.studentId = studentId
this.initializeSensors(capabilities)
}
private initializeSensors(capabilities: Array<string>): void {
// 加速度计:检测设备姿态
this.accelerometer = sensor.createAccelerometer()
this.accelerometer.on('change', (data) => {
this.updatePosture(data)
})
// 若支持,启用更多传感器
if (capabilities.includes('heart_rate')) {
// 连接穿戴设备心率数据
}
}
private updatePosture(accData: sensor.AccelerometerResponse): void {
// 根据重力方向判断设备姿态
const { x, y, z } = accData
const totalG = Math.sqrt(x*x + y*y + z*z)
let posture: string
if (Math.abs(z) > 8) {
posture = 'upright' // 竖直放置(正常看屏幕)
} else if (Math.abs(z) < 2 && Math.abs(x) > 8) {
posture = 'flat' // 平放桌面(可能分心)
} else {
posture = 'tilted' // 倾斜(可能走神)
}
this.postureHistory.push(posture)
if (this.postureHistory.length > 10) this.postureHistory.shift()
this.calculateAttentionScore()
}
recordInteraction(): void {
this.recentInteractions.push(Date.now())
// 清理5分钟前的记录
const fiveMinutesAgo = Date.now() - 5 * 60 * 1000
this.recentInteractions = this.recentInteractions.filter(t => t > fiveMinutesAgo)
}
private calculateAttentionScore(): void {
// 综合多维度指标计算专注度
let score = 100
// 姿态评分(直立100,倾斜70,平放40)
const postureScore = this.postureHistory.reduce((sum, p) => {
if (p === 'upright') return sum + 100
if (p === 'tilted') return sum + 70
return sum + 40
}, 0) / this.postureHistory.length || 100
score = score * 0.4 + postureScore * 0.4
// 交互活跃度(适度交互表示参与,过高可能分心玩游戏)
const interactionFreq = this.recentInteractions.length
if (interactionFreq < 2) score *= 0.9 // 过低参与
if (interactionFreq > 20) score *= 0.8 // 过高可能异常
this.latestScore = Math.round(score)
// 上报数据
this.dataCallback?.({
studentId: this.studentId,
timestamp: Date.now(),
attentionScore: this.latestScore,
indicators: {
devicePosture: this.postureHistory[this.postureHistory.length - 1] as any,
screenFocus: true, // 假设
interactionFrequency: interactionFreq
}
})
}
onDataUpdate(callback: (data: AttentionData) => void): void {
this.dataCallback = callback
}
getLatestScore(): number {
return this.latestScore
}
}
3.5 课堂数据归档与回放
// education/analytics/ReportGenerator.ts
import { fileIo } from '@kit.FileSystemKit'
interface LearningArchive {
sessionId: string
metadata: {
subject: string
grade: string
teacher: string
date: string
duration: number
}
timeline: Array<TimelineEvent>
studentRecords: Map<string, StudentLearningRecord>
resources: {
whiteboardRecording: string // 白板笔迹文件路径
screenRecording?: string // 屏幕录制路径
quizData: QuizReport
attentionTimeline: Array<{ time: number; classAverage: number }>
}
}
export class ReportGenerator {
async generateArchive(sessionManager: SessionManager, quizEngine: QuizEngine): Promise<LearningArchive> {
const session = sessionManager.getCurrentSession()!
const archive: LearningArchive = {
sessionId: session.sessionId,
metadata: {
subject: session.subject,
grade: session.grade,
teacher: session.teacherDevice.deviceName,
date: new Date(session.startTime).toISOString(),
duration: Date.now() - session.startTime
},
timeline: this.reconstructTimeline(session),
studentRecords: await this.compileStudentRecords(session),
resources: {
whiteboardRecording: await this.saveWhiteboardData(),
screenRecording: await this.finalizeScreenRecording(),
quizData: quizEngine.getQuizReport(),
attentionTimeline: this.getAttentionTimeline()
}
}
// 生成结构化学习包
await this.createLearningPackage(archive)
return archive
}
private async createLearningPackage(archive: LearningArchive): Promise<string> {
const packageDir = getContext(this).filesDir + `/archives/${archive.sessionId}`
await fileIo.mkdir(packageDir, true)
// 1. 保存元数据
await fileIo.writeText(
packageDir + '/metadata.json',
JSON.stringify(archive.metadata, null, 2)
)
// 2. 处理白板笔迹视频
await this.renderWhiteboardVideo(
archive.resources.whiteboardRecording,
packageDir + '/whiteboard.mp4'
)
// 3. 生成学生个人学习报告(PDF)
for (const [studentId, record] of archive.studentRecords) {
const report = this.generateStudentReport(studentId, record, archive)
await fileIo.writeText(
packageDir + `/report_${studentId}.json`,
JSON.stringify(report, null, 2)
)
}
// 4. 打包为HAP可识别格式
const packagePath = packageDir + '.learning'
await this.zipPackage(packageDir, packagePath)
// 5. 上传到教育云平台
await this.uploadToCloud(packagePath, archive.metadata)
return packagePath
}
private generateStudentReport(
studentId: string,
record: StudentLearningRecord,
archive: LearningArchive
): StudentReport {
return {
studentId,
summary: {
attendance: record.attendance,
participationScore: record.participationScore,
quizAccuracy: record.quizCorrectRate,
attentionAverage: record.attentionAverage,
classRanking: this.calculateRanking(studentId, archive)
},
highlights: this.extractHighlights(record, archive.timeline),
reviewPoints: this.identifyWeakPoints(record),
recommendedResources: this.suggestResources(record)
}
}
private extractHighlights(record: StudentLearningRecord, timeline: Array<TimelineEvent>): Array<Highlight> {
// 提取课堂亮点:正确回答难题、积极参与讨论等
return timeline
.filter(e => e.type === 'quiz_correct' && e.studentId === record.studentId && e.difficulty === 'hard')
.map(e => ({
type: 'excellent_answer',
timestamp: e.timestamp,
description: '正确回答难题',
screenshot: e.screenshot
}))
}
private identifyWeakPoints(record: StudentLearningRecord): Array<WeakPoint> {
// 基于答题错误和注意力低谷识别薄弱环节
return record.wrongAnswers.map(wa => ({
topic: wa.questionTopic,
mistakeType: wa.mistakePattern,
suggestion: `复习${wa.questionTopic}相关知识点`
}))
}
private suggestResources(record: StudentLearningRecord): Array<Resource> {
// 基于薄弱点推荐个性化学习资源
return []
}
private async renderWhiteboardVideo(inputPath: string, outputPath: string): Promise<void> {
// 使用鸿蒙媒体编解码将笔迹数据渲染为视频
// 实现略...
}
private async zipPackage(sourceDir: string, outputPath: string): Promise<void> {
// 压缩打包
// 实现略...
}
private async uploadToCloud(localPath: string, metadata: object): Promise<void> {
// 上传到教育云平台
// 实现略...
}
}
四、教师主控台UI实现
// pages/TeacherDashboard.ets
import { SessionManager } from '../education/classroom/SessionManager'
import { QuizEngine } from '../education/interaction/QuizEngine'
import { ScreenCastingManager } from '../education/interaction/ScreenCasting'
@Entry
@Component
struct TeacherDashboard {
@State sessionManager: SessionManager = new SessionManager()
@State quizEngine: QuizEngine = new QuizEngine()
@State screenCaster: ScreenCastingManager = new ScreenCastingManager()
@State currentView: 'whiteboard' | 'quiz' | 'students' | 'settings' = 'whiteboard'
@State isTeaching: boolean = false
@State studentList: Array<StudentDeviceInfo> = []
@State currentQuestion: QuizQuestion | null = null
@State quizResult: QuizResult | null = null
@State attentionStats: ClassAttentionStats = { averageScore: 0, distribution: { high: 0, medium: 0, low: 0 }, trend: [] }
aboutToAppear() {
this.sessionManager.initialize()
this.setupEventListeners()
}
private setupEventListeners(): void {
// 监听学生加入
this.sessionManager.onDeviceStateChange((deviceId, state) => {
if (state === 'connected') {
this.refreshStudentList()
}
})
// 监听答题结果
this.quizEngine.onRealtimeUpdate((result) => {
this.quizResult = result
})
}
build() {
Column() {
// 顶部工具栏
this.Toolbar()
// 主内容区(分栏布局)
Row() {
// 左侧导航
this.Sidebar()
.width(80)
// 中间主视图
this.MainContent()
.layoutWeight(1)
// 右侧学生状态面板
this.StudentPanel()
.width(280)
.visibility(this.currentView === 'students' ? Visibility.Visible : Visibility.Collapsed)
}
.layoutWeight(1)
// 底部控制栏
this.BottomControls()
.height(60)
}
.width('100%')
.height('100%')
.backgroundColor('#f0f2f5')
}
@Builder
Toolbar() {
Row() {
Text('智慧课堂系统')
.fontSize(20)
.fontWeight(FontWeight.Bold)
Blank()
// 课堂状态指示
Row() {
Circle()
.width(12)
.height(12)
.fill(this.isTeaching ? '#52c41a' : '#faad14')
Text(this.isTeaching ? '授课中' : '准备中')
.fontSize(14)
.margin({ left: 8 })
}
// 在线人数
Row() {
Image($r('app.media.ic_people'))
.width(20)
.height(20)
Text(`${this.studentList.length}人`)
.fontSize(14)
.margin({ left: 4 })
}
.margin({ left: 16 })
}
.width('100%')
.height(56)
.padding({ left: 16, right: 16 })
.backgroundColor('#ffffff')
.shadow({ radius: 2, color: 'rgba(0,0,0,0.1)' })
}
@Builder
Sidebar() {
Column({ space: 16 }) {
NavButton({
icon: $r('app.media.ic_whiteboard'),
label: '白板',
isActive: this.currentView === 'whiteboard',
onClick: () => this.currentView = 'whiteboard'
})
NavButton({
icon: $r('app.media.ic_quiz'),
label: '答题',
isActive: this.currentView === 'quiz',
onClick: () => this.currentView = 'quiz'
})
NavButton({
icon: $r('app.media.ic_students'),
label: '学生',
isActive: this.currentView === 'students',
onClick: () => this.currentView = 'students'
})
NavButton({
icon: $r('app.media.ic_cast'),
label: '投屏',
onClick: () => this.toggleCasting()
})
}
.width('100%')
.height('100%')
.padding({ top: 16 })
.backgroundColor('#ffffff')
}
@Builder
MainContent() {
Stack() {
// 白板视图
WhiteboardView({
onStrokeDrawn: (stroke) => this.screenCaster.addStroke(stroke)
})
.visibility(this.currentView === 'whiteboard' ? Visibility.Visible : Visibility.Hidden)
// 答题视图
QuizControlView({
engine: this.quizEngine,
currentQuestion: this.currentQuestion,
result: this.quizResult
})
.visibility(this.currentView === 'quiz' ? Visibility.Visible : Visibility.Hidden)
}
.width('100%')
.height('100%')
.backgroundColor('#ffffff')
.margin(16)
.borderRadius(8)
.shadow({ radius: 4, color: 'rgba(0,0,0,0.05)' })
}
@Builder
StudentPanel() {
Column() {
Text('学生状态')
.fontSize(16)
.fontWeight(FontWeight.Medium)
.margin({ bottom: 12 })
// 专注度统计
AttentionOverviewCard({ stats: this.attentionStats })
.margin({ bottom: 12 })
// 学生列表
List({ space: 8 }) {
ForEach(this.studentList, (student: StudentDeviceInfo) => {
ListItem() {
StudentStatusCard({
student: student,
attentionData: this.getStudentAttention(student.studentId!)
})
}
}, (student: StudentDeviceInfo) => student.deviceId)
}
.layoutWeight(1)
}
.width('100%')
.height('100%')
.padding(16)
.backgroundColor('#ffffff')
.borderRadius({ topLeft: 8, bottomLeft: 8 })
.shadow({ radius: 4, color: 'rgba(0,0,0,0.05)', offsetX: -2 })
}
@Builder
BottomControls() {
Row() {
Button(this.isTeaching ? '结束授课' : '开始授课')
.type(ButtonType.Capsule)
.backgroundColor(this.isTeaching ? '#ff4d4f' : '#1890ff')
.onClick(() => this.toggleTeaching())
Blank()
// 快速操作按钮
Button('发起答题')
.enabled(this.isTeaching)
.onClick(() => this.startQuickQuiz())
Button('随机点名')
.enabled(this.isTeaching)
.margin({ left: 8 })
.onClick(() => this.randomPick())
}
.width('100%')
.height('100%')
.padding({ left: 16, right: 16 })
.backgroundColor('#ffffff')
.shadow({ radius: 4, color: 'rgba(0,0,0,0.05)', offsetY: -2 })
}
private async toggleTeaching(): Promise<void> {
if (!this.isTeaching) {
await this.sessionManager.startTeaching()
await this.screenCaster.startCasting(
this.studentList.map(s => s.deviceId)
)
this.isTeaching = true
} else {
await this.sessionManager.endSession()
await this.screenCaster.stopCasting()
this.isTeaching = false
}
}
private refreshStudentList(): void {
const session = this.sessionManager.getCurrentSession()
if (session) {
this.studentList = Array.from(session.studentDevices.values())
}
}
private getStudentAttention(studentId: string): AttentionData | undefined {
const dashboard = AppStorage.get<Map<string, AttentionData>>('attentionDashboard')
return dashboard?.get(studentId)
}
}
五、总结与教育价值
本文构建了完整的鸿蒙智慧课堂解决方案,核心价值体现在:
- 无感接入:学生设备通过分布式软总线自动发现,无需复杂配置
- 实时互动:笔迹同步延迟<50ms,答题结果秒级统计,支持教学决策
- 学情洞察:多维度注意力监测,生成个性化学习报告
- 资源复用:课堂数据自动归档,支持课后复习与教研分析
部署场景:
- K12智慧教室:替代传统电子书包方案,降低50%硬件成本
- 高校智慧教室:支持200+设备并发的大课堂场景
- 企业培训:快速搭建移动化培训教室
后续改进方向:
- 接入盘古教育大模型,实现AI助教自动答疑
- 基于星闪技术实现更低延迟的VR/AR教学
- 构建区域级教育数据中台,支持教育均衡监测
转载自:https://blog.csdn.net/u014727709/article/details/159632383
欢迎 👍点赞✍评论⭐收藏,欢迎指正
更多推荐


所有评论(0)