视频进度代码,延时队列方案

环境:springboot、mybatis-plus、redisson-starter、hutool

方案说明

提交视频进度,这个接口很高频率。是高频率的写。

这里主要方案如下,

1.提交的进度先到redis进行保存,并且同时发送一个15秒的延时任务。

2.到了15s后,取出这个任务数据,和redis中的数据做比对。

2.1.如果进度数据一样的话,说明用户停止播放了视频,可以写库了。

这就是正常的流程了。

效果

前提代码

sql 复制代码
-- 创建视频进度表
DROP TABLE IF EXISTS `video_progress`;
CREATE TABLE IF NOT EXISTS `video_progress`
(
    `id`         BIGINT  PRIMARY KEY,
    `user_id`    BIGINT comment '用户id(关联)',
    `video_id`    BIGINT comment '视频id(关联)',
    `position`      DOUBLE comment '当前进度',
    `duration`      DOUBLE comment '视频总时长',
    `is_finish` tinyint(1) default 0 comment '是否完成',
    `update_time` datetime null on update CURRENT_TIMESTAMP comment '更新时间'
) comment '视频进度表';
java 复制代码
@TableName(value ="video_progress")
@Data
public class VideoProgress implements Serializable {
    @TableId(type = IdType.ASSIGN_ID)
    private Long id;

    private Long userId;

    private Long videoId;

    private Double position;

    private Double duration;

    private Integer isFinish;

    private Date updateTime;

    private static final long serialVersionUID = 1L;
}
java 复制代码
public interface VideoProgressMapper extends BaseMapper<VideoProgress> {

}
java 复制代码
@Getter
@ToString
public class DelayedTask<T> implements Delayed, Serializable {

    private static final long serialVersionUID = 3018458065076640934L;
    private final T taskContent;

    private final Long triggerTime;

    /**
     * @param seconds 秒
     */
    public DelayedTask(T taskContent, Long seconds) {
        this.taskContent = taskContent;
        this.triggerTime = System.currentTimeMillis() + seconds * 1000;
    }

    @Override
    public long getDelay(TimeUnit unit) {
        return unit.convert(triggerTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
    }

    @Override
    public int compareTo(Delayed o) {
        return this.triggerTime.compareTo(((DelayedTask) o).triggerTime);
    }

}
java 复制代码
@Data
public class VideoSubmitRequest implements Serializable {

    private static final long serialVersionUID = -102757973917094785L;
    long videoId;
    long userId;
    double duration;
    double position;
}

核心代码

java 复制代码
@Slf4j
@SpringBootApplication
@RestController
@RequestMapping("/")
@RequiredArgsConstructor
@MapperScan
public class VideoProgressApplication {

    private final StringRedisTemplate stringRedisTemplate;
    private final String VIDEO_PROGRESS_KEY = "video:progress";
    private final RedissonClient redissonClient;

    @PostMapping("/submit")
    public void submit(@RequestBody VideoSubmitRequest videoSubmitRequest) {
        // 1.校验参数后,直接写入到redis中
        var key = VIDEO_PROGRESS_KEY + ":" + videoSubmitRequest.userId;
        var value = JSONUtil.toJsonStr(videoSubmitRequest);
        var hashKey = String.valueOf(videoSubmitRequest.videoId);
        stringRedisTemplate.opsForHash().put(key, hashKey, value);
        // 这里要设置过期时间,但是总是报StackOverFlow,为啥啊。
        // stringRedisTemplate.opsForHash().expire(key, Duration.ofSeconds(15), Collections.singletonList(hashKey));


        // 2.发布一个延时任务15s
        RBlockingDeque<DelayedTask<VideoSubmitRequest>> blockingDeque = redissonClient.getBlockingDeque("video-progress-delay-queue");
        RDelayedQueue<DelayedTask<VideoSubmitRequest>> delayedQueue = redissonClient.getDelayedQueue(blockingDeque);
        delayedQueue.offer(new DelayedTask<>(videoSubmitRequest, 15L),15, TimeUnit.SECONDS);
        log.info("发送延时任务,{}",value);
    }

    public static void main(String[] args) {
        SpringApplication.run(VideoProgressApplication.class, args);

    }

}
java 复制代码
@Component
@RequiredArgsConstructor
@Slf4j
public class DelayQueueHandler implements CommandLineRunner {
    private final RedissonClient redissonClient;
    private final StringRedisTemplate stringRedisTemplate;
    private final VideoProgressMapper videoProgressMapper;

    private RBlockingDeque<DelayedTask<VideoSubmitRequest>> blockingDeque;
    private static volatile boolean RUNNING = true;

    @Override
    public void run(String... args) {
        blockingDeque = redissonClient.getBlockingDeque("video-progress-delay-queue");
        new Thread(() -> {
            while (RUNNING) {
                try {
                    // 1.延时任务结束,需要检查提交的数据和redis中的数据是否一致,如果不一致就可以写库了
                    var take = blockingDeque.take();
                    log.info("处理延时任务,{}",take);
                    var oldProgress = take.getTaskContent();
                    // 2.取出redis中的数据,做比对
                    var key = "video:progress:" + oldProgress.getUserId();
                    var hashKey = oldProgress.getVideoId();
                    var value = stringRedisTemplate.opsForHash().get(key, String.valueOf(hashKey));

                    if (value == null) {
                        continue;
                    }
                    // 2.1.从redis中取出最新的数据
                    var newProgress = JSONUtil.toBean((String) value, VideoSubmitRequest.class);
                    // 2.2.比对,这里不一致就直接结束
                    if (!NumberUtil.equals(oldProgress.getPosition(), newProgress.getPosition())) {
                        continue;
                    }

                    // 3.写库
                    var userId = newProgress.getUserId();
                    var videoId = newProgress.getVideoId();
                    var position = newProgress.getPosition();
                    var duration = newProgress.getDuration();

                    var videoProgress = videoProgressMapper.selectOne(Wrappers.lambdaQuery(VideoProgress.class)
                            .eq(VideoProgress::getUserId, userId)
                            .eq(VideoProgress::getVideoId, videoId));

                    if (videoProgress == null) {
                        videoProgress = new VideoProgress();
                        videoProgress.setUserId(userId);
                        videoProgress.setVideoId(videoId);
                        videoProgress.setDuration(duration);
                    }

                    videoProgress.setPosition(position);
                    if (position / duration > 0.8) {
                        videoProgress.setIsFinish(1);
                    }

                    videoProgressMapper.insertOrUpdate(videoProgress);
                    stringRedisTemplate.opsForHash().delete(key, String.valueOf(hashKey));

                } catch (Exception e) {
                    log.error("延时队列出错:{}", e);
                }
            }
        }).start();
    }

    @PreDestroy
    public void destroy() {
        RUNNING = false;
    }
}
相关推荐
美狐美颜SDK开放平台4 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
2601_955662464 天前
短剧多人对白怎么配?2026年多角色配音工具对比
大数据·人工智能·音视频·语音识别
开发笔记-阿牛4 天前
蓝牙音乐灯拆解,又一次拆到蓝牙音乐灯芯片CK6865L
人工智能·单片机·嵌入式硬件·音视频·音频
旺仔小馒头wang4 天前
AI 智能手机发布会:赋予人类的五种新能力
人工智能·学习·音视频
纳祥科技4 天前
拆机拆到芯片级:ARC功放机里的NX7026、NX8420、NX6805
单片机·音视频·智能音箱
youandyouyou4 天前
律所内部培训共享的内网部署方案:架构、参数与三步落地
音视频
爱吃提升4 天前
文生视频核心
人工智能·音视频
2601_955662464 天前
抖音视频怎么做多语言配音?2026年AI配音工具与制作流程详解
人工智能·音视频·语音识别·视频
音视频牛哥4 天前
Android与HarmonyOS NEXT如何接入GB28181:从SIP到PS/RTP的完整链路
音视频·gb28181-2022对接·鸿蒙next gb28181·gb/t 28181-2022·gb/t 28181-2016·gb28181-2022平台
美狐美颜SDK开放平台4 天前
直播APP源码与视频美颜sdk如何配合?一套完整开发思路
android·人工智能·计算机视觉·音视频·直播美颜sdk