useStudyRecordTracker.js 4.45 KB
import { ref, onUnmounted } from 'vue';
import { v4 as uuidv4 } from 'uuid';
import { addStudyRecordAPI } from '@/api/record';

/**
 * 学习记录埋点跟踪器
 * @module useStudyRecordTracker
 * @description
 * 自动追踪用户的学习行为(视频播放、音频播放),定期上报学习进度。
 * 
 * @param {Object} params - 参数对象
 * @param {import('vue').Ref<Object>} params.course - 课程对象
 * @param {import('vue').Ref<string|number>} params.courseId - 课程ID
 * @param {import('vue').Ref<Object>} params.videoPlayerRef - 视频播放器组件引用
 * @param {import('vue').Ref<Object>} params.audioPlayerRef - 音频播放器组件引用
 * @returns {Object} 包含开始/结束追踪和手动添加记录的方法
 */
export const useStudyRecordTracker = ({ course, courseId, videoPlayerRef, audioPlayerRef }) => {
    /** @type {import('vue').Ref<number|null>} 定时器ID */
    const action_timer = ref(null);
    /** @type {import('vue').Ref<string>} 当前播放会话ID (UUID) */
    const playback_id = ref('');

    /**
     * 清除定时器
     */
    const clear_timer = () => {
        if (action_timer.value) {
            clearInterval(action_timer.value);
            action_timer.value = null;
        }
    };

    /**
     * 手动上报学习记录
     * @param {Object} paramsObj - 上报参数
     * @returns {Promise<Object>} API响应
     */
    const addRecord = async (paramsObj) => {
        if (!paramsObj || !paramsObj.schedule_id) return;
        return await addStudyRecordAPI(paramsObj);
    };

    /**
     * 获取规范化的课程ID
     * @returns {string|number}
     */
    const get_schedule_id = () => {
        if (typeof courseId === 'object' && courseId && 'value' in courseId) return courseId.value;
        return courseId || '';
    };

    /**
     * 获取视频播放状态载荷
     * @description 从 videoPlayerRef 获取当前播放时间、总时长和 meta_id
     * @returns {Object|null}
     */
    const get_video_payload = () => {
        const player = videoPlayerRef?.value?.getPlayer?.();
        if (!player) return null;

        const duration = typeof player.duration === 'function' ? player.duration() : (player.duration || 0);
        const position = typeof player.currentTime === 'function' ? player.currentTime() : (player.currentTime || 0);
        const meta_id = videoPlayerRef?.value?.getId?.();
        if (!meta_id) return null;

        return {
            meta_id,
            media_duration: duration,
            playback_position: position,
            playback_id: playback_id.value,
        };
    };

    /**
     * 获取音频播放状态载荷
     * @param {Object} item - 音频项数据
     * @returns {Object|null}
     */
    const get_audio_payload = (item) => {
        const player = audioPlayerRef?.value?.getPlayer?.();
        if (!player) return null;

        const meta_id = item?.meta_id;
        if (!meta_id) return null;

        return {
            meta_id,
            media_duration: player.duration || 0,
            playback_position: player.currentTime || 0,
            playback_id: playback_id.value,
        };
    };

    /**
     * 开始追踪
     * @param {Object} [item] - 当前播放项(用于音频)
     * @description
     * 1. 生成新的 playback_id
     * 2. 启动定时器 (3秒间隔)
     * 3. 根据课程类型 (video/audio) 获取对应的播放状态
     * 4. 调用 addRecord 上报
     */
    const startAction = (item) => {
        clear_timer();

        const schedule_id = get_schedule_id();
        if (!schedule_id) return;

        playback_id.value = uuidv4();

        action_timer.value = setInterval(() => {
            const is_video = course?.value?.course_type === 'video';
            const is_audio = course?.value?.course_type === 'audio';

            let payload = null;
            if (is_video) payload = get_video_payload();
            if (is_audio) payload = get_audio_payload(item);
            // 兜底尝试
            if (!payload) payload = get_video_payload() || get_audio_payload(item);
            
            if (!payload) return;

            addRecord({
                schedule_id,
                ...payload,
            });
        }, 3000);
    };

    /**
     * 结束追踪
     */
    const endAction = () => {
        clear_timer();
    };

    // 组件卸载时自动清理
    onUnmounted(() => {
        clear_timer();
    });

    return {
        startAction,
        endAction,
        addRecord,
    };
};