JAVA记录媒体视频播放进度开发 (实例干货)

搭建环境:springBoot + apache-maven-3.6.3 + mysql + Redisson3.15.4

  • 项目背景

项目需要做一个记录视频播放进度的功能,有以下几点需要着重注意:

  1. 点击视频,播放到几小时几分几秒,下次同一个人点击进来依然是当前时间段
  2. 当一个维度下有多个视频可以看,分开记录当前视频或者文档是否已经看完。比如学习维度下有两个视频,一个文档,文档或者视频看完,直接显示当前视频已看完,但学习维度还显示正在学习,除非当前维度下的所有视频或者文档全部显示为已学完。
  3. 文档点击进入页面可以直接认定为已看完,视频看完必须要看完时长的三分之二,才能显示当前视频已看完。
  4. 记录当前天每个人实际学习时间总时长(额外需求开发)。
  5. 单个媒体视频学完之后,直接已学完,这种逻辑很好实现。

    多个媒体在同一个维度下,比如娱乐维度下有三个视频,学习维度下有五个学习视频,只要当前维度下的所有视频全部显示已学完,才可以让当前维度更新数据库显示已学完,怎么统计呢?

    解决办法:当前维度下的单个视频,每次第一次进入就去更新是否已学完。还有当单个媒体最新状态显示已学完,只有这两个情况才进入方法去更新当前维度下的学习状态。

  • 需求分析

会出现同一个人在不同的页面或者不同的浏览器打开同一门课的情况,当前情况可以能用redis分布式锁解决。

首先记录学习【媒体播放进度表】,应该包含:

  1. 已学习时长:用于判断当前是否已学完,当时长等于总长度三分二时。
  2. 媒体播放进度:用于返回给前端,上次已播放的位子。
  3. 媒体视频总时长:用于判断是否已学完,还是正在学。
  4. 学习状态:已学完COMPALETED,正在学LEARNING。
  5. 课程维度主键id:唯一性。
  6. 课程下媒体或者文档主键id:唯一性。
  7. 学习用户。
  8. 创建时间。
  9. 修改时间。

【课程维度】表,上面表记录单个视频是否已学完,课程维度表记录当前维度下的所有视频是否已学完。

  1. 课程主键id。
  2. 课程下媒体最后学习id。
  3. 用户id。
  4. 已学完总的视频媒体数量。
  5. 学习状态:已学完,未学完。
  6. 创建时间。
  7. 修改时间。

【学习时长】表,应该有

  1. 总学习时间。
  2. 当前日期。
  3. 记录当前天开始学习时间。
  4. 为了考虑延伸扩展性,加一个存储类型字段,当前类型可以根据天,周,月,年等维度来统计时长。
  5. 学习用户。
  6. 创建时间。
  7. 修改时间。

这样设计的目的,当前学习时长表不光在此需求中可以使用,以后在其他需求,也可以用此表。

既然是记录视频学习时长,所以前端肯定需要写一个定时器传json数据,为了防止调用频率太高,但又不能记录不到播放记录,所以定时时间定位15s一次。

  1. SectionId:代表媒体视频或者文档的id。
  2. CourseId:代表媒体视频或者文档属于哪个维度下的id,比如学习维度,娱乐维度。
  3. sectionType:类型,代表当前是媒体视频还是文档。
  4. deltaDuration:增量时间,非视频为0,视频就传新增的看视频时长。
  5. mediaProgress:视频播放节点,非视频为0,视频就传已看到的视频节点。
  6. first:是否第一次打开当前媒体。(方便以后扩展使用)
  • 代码开发

先创建学习进度表和学习总时长表:

//学习进度表
create table user_learn_stats(
	`id` int not null auto_increment,
	course_id int comment '课程id',
	section_id int comment '节id',
	user_id varchar(32) comment '用户id',
	learned_duration bigint comment '已学习时长',
	media_progress bigint comment '媒体进度',
	media_duration bigint comment '媒体总时长',
	learned_status varchar(32) comment '已学完,学习中',
	create_date datetime,
	update_date datetime,
	primary key(id)
);


//学习总时长表
create table user_learned(
		`id` int not null auto_increment,
		user_id varchar(32) comment '用户id',
		duration bigint comment '学习总时长',
		learn_status varchar(32) comment '学习类型',
		create_date datetime,
		update_date datetime,
		primary key (id)
);

建立对应两个表的实体类。


/**
 * @author keying
 */
@Data
public class UserLearned {
    private Long id;
    private Long courseId;
    private Long sectionId;
    private String userId;
    private Long duration;
    private String learnStats;
    private Date createDate;
    private Date updateDate;
}


/**
 * @author keying
 */
@Data
public class UserLearnStats {
    private Long id;
    private Long courseId;
    private Long sectionId;
    private String userId;
    private Long learnedDuration;
    private Long mediaProgress;
    /**
     * 总时长
     */
    private Long mediaDuration;
    private String learnedStatus;
    private Date createDate;
    private Date updateDate;
}

前端需要传递过来的json参数,


/**
 * 媒体播放进度
 *
 * @author keying
 */
@Data
public class ProgressRequest {

    /**
     * 节id(媒体视频id)
     */
    private Long sectionId;

    /**
     * 代表媒体视频或者文档属于哪个维度下的id,比如学习维度,娱乐维度
     */
    @NotNull
    private Long courseId;

    /**
     * 类型,代表当前是媒体视频还是文档
     */
    private String sectionType;

    /**
     * 增量时间,非视频为0,视频就传新增的看视频时长
     */
    private Long deltaDuration;

    /**
     * 视频播放节点,非视频为0,视频就传已看到的视频节点
     */
    private Long mediaProgress;

    /**
     * 是否第一次打开当前媒体。(方便以后扩展使用)
     */
    private Boolean first;

}

准备完毕开始写接口,因为是一个节的进度记录,所以接口名称这样设计更合理:


/**
 * 媒体进度
 *
 * @author keying
 */
@RestController
@RequestMapping("/section")
public class SectionController {

    @Autowired
    private ProgressService progressService;

    @PostMapping("/{id}/progress")
    public void progress(@PathVariable Long id, @RequestBody @Valid ProgressRequest request) {
        progressService.progress(id, request);
    }

}

接下来看业务层:

可以在修改时间进度的时候加一个访问过于频繁限制:

/*   if       ((DateUtils.truncatedCompareToDateUtils.addMilliseconds(learningRecordDO.getGmtModified(),(int)SectionConstants.MIN_DELTA_DURATION), new Date(), Calendar.MILLISECOND) > 0) {
                throw new BizException("访问过于频繁。");
            }*/

/**
 * @author keying
 */
@Service
public class ProgressServiceImpl implements ProgressService {

    //默认用户
    public final String NAME = "keying";

    @Autowired
    private UserLearnStatsMapper userLearnStatsMapper;

    @Autowired
    private UserLearnedMapper userLearnedMapper;

    @Override
    public void progress(Long id, ProgressRequest request) {
        //存入学习总时长
        progressUserLearned(id, request);
        //统计当前课程 & 节是否已学完
        insertUserLearnStats(id, request);
    }

    private void progressUserLearned(Long id, ProgressRequest request) {
        //总学习时长增加
        UserLearned userLearnedSelect = new UserLearned();
        userLearnedSelect.setCourseId(request.getCourseId());
        userLearnedSelect.setSectionId(id);
        userLearnedSelect.setUserId(NAME);
        //获取到历史数据
        UserLearned userLearned = userLearnedMapper.selectOne(userLearnedSelect);
        if (!Objects.isNull(userLearned)) {
            //修改学习时长
            UserLearned userLearnedUpdate = new UserLearned();
            userLearnedUpdate.setId(userLearned.getId());
            userLearnedUpdate.setUpdateDate(new Date());
            userLearnedUpdate.setDuration(userLearned.getDuration() + request.getDeltaDuration());
            userLearnedMapper.updateById(userLearnedUpdate);
            return;
        }
        //新增学习时长
        UserLearned userLearnedInsert = new UserLearned();
        userLearnedInsert.setUserId(NAME);
        userLearnedInsert.setCourseId(request.getCourseId());
        userLearnedInsert.setSectionId(id);
        userLearnedInsert.setCreateDate(new Date());
        userLearnedInsert.setUpdateDate(new Date());
        userLearnedInsert.setDuration(request.getDeltaDuration());
        userLearnedMapper.insert(userLearnedInsert);
    }

    private void insertUserLearnStats(Long id, ProgressRequest request) {
        //处理节,媒体是否已学完
        UserLearnStats userLearnStatsSelect = new UserLearnStats();
        userLearnStatsSelect.setUserId(NAME);
        userLearnStatsSelect.setSectionId(id);
        userLearnStatsSelect.setCourseId(request.getCourseId());
        //获取到历史数据
        UserLearnStats userLearnStats = userLearnStatsMapper.selectOne(userLearnStatsSelect);
        //节状态
        if (!Objects.isNull(userLearnStats)) {
            //修改
            UserLearnStats updateUserLearnStats = new UserLearnStats();
            updateUserLearnStats.setId(userLearnStats.getId());
            updateUserLearnStats.setUpdateDate(new Date());
            //获取是否已学完
            boolean flag = calculateLearned(request, updateUserLearnStats.getMediaDuration(),
                request.getMediaProgress() + request.getMediaProgress());
            updateUserLearnStats.setLearnedStatus("LEARNING");
            if (flag) {
                updateUserLearnStats.setLearnedStatus("COMPALETE");
            }
            userLearnStatsMapper.updateById(updateUserLearnStats);
            return;
        }
        //无历史数据则创建
        UserLearnStats insertUserLearnStats = new UserLearnStats();
        insertUserLearnStats.setCourseId(request.getCourseId());
        insertUserLearnStats.setCreateDate(new Date());
        insertUserLearnStats.setUpdateDate(new Date());
        insertUserLearnStats.setLearnedDuration(request.getDeltaDuration());
        //总时长默认都60*1000毫秒
        insertUserLearnStats.setMediaDuration(60 * 1000L);
        //获取是否已学完
        boolean flag = calculateLearned(request, insertUserLearnStats.getMediaDuration(),
            request.getMediaProgress() + request.getMediaProgress());
        insertUserLearnStats.setLearnedStatus("LEARNING");
        if (flag) {
            insertUserLearnStats.setLearnedStatus("COMPALETE");
        }
        insertUserLearnStats.setMediaProgress(request.getMediaProgress());
        insertUserLearnStats.setSectionId(id);
        insertUserLearnStats.setUserId(NAME);
        userLearnStatsMapper.insert(insertUserLearnStats);

        //处理课程 是否学完
    }

    private boolean calculateLearned(ProgressRequest request, Long allDuration, Long learnedDuration) {
        if (request.getSectionType().equals("TEXT")) {
            return Boolean.TRUE;
        }
        if (request.getSectionType().equals("VIDEO")) {
            if (learnedDuration >= (allDuration * 2 / 3)) {
                return Boolean.TRUE;
            }
        }
        return Boolean.FALSE;
    }
}

前面每日学习时长 和 单个视频,文档已学完都已经记录完毕,那维度下多个视频学完怎么处理呢?先建一个课程表和实体类:

mysql> create table user_course_learned(
    -> `id` int not null auto_increment,
    -> course_id int comment '课程id',
    -> last_section_id int comment '最后学习节id',
    -> learned_section_count bigint comment '学习完节数量',
    -> learn_status varchar(32) comment '已学完compalate,学习ing',
    -> create_date datetime,
    -> update_date datetime,
    -> primary key(id)
    -> );
Query OK, 0 rows affected (0.04 sec)


//实体类
/**
 * @author keying
 */
@Data
public class UserCourseLearned {
    private Long id;
    private Long courseId;
    private Long lastSectionId;
    private String userId;
    private Long learnedSectionCount;
    private String learnedStatus;
    private Date createDate;
    private Date updateDate;
}

先引入redisson包,这里就不详细说明了,有需要的可以看看之前的文章怎么用redisson。


    private void courseRedisson(Boolean aFalse, ProgressRequest request, String oldLearnedStatus,
        String newLearnedStatus) {
        // 默认连接上127.0.0.1:6379
        RedissonClient redissonClient = Redisson.create();
        // 一个分布式锁,指明锁的名称
        RLock rLock = redissonClient.getLock(NAME + request.getCourseId());
        try {
            if (rLock.tryLock(1, 1, TimeUnit.MINUTES)) {
                log.info("获取到锁");
                courseProgress(Boolean.FALSE, request, oldLearnedStatus,
                    newLearnedStatus);
            }
        } catch (Exception e) {

        } finally {
            rLock.unlock();
        }
    }

之后当获取到锁的页面会进入业务层:

package com.alibaba.first.service.lmpl;

import java.util.Date;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

import com.alibaba.first.mapper.UserCourseLearnedMapper;
import com.alibaba.first.mapper.UserLearnStatsMapper;
import com.alibaba.first.mapper.UserLearnedMapper;
import com.alibaba.first.model.ProgressRequest;
import com.alibaba.first.model.UserCourseLearned;
import com.alibaba.first.model.UserLearnStats;
import com.alibaba.first.model.UserLearned;
import com.alibaba.first.service.ProgressService;

import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @author keying
 * @date 2021/9/1
 */
@Service
@Slf4j
public class ProgressServiceImpl implements ProgressService {

    //默认用户
    public final String NAME = "keying";

    @Autowired
    private UserLearnStatsMapper userLearnStatsMapper;
    @Autowired
    private UserLearnedMapper userLearnedMapper;
    @Autowired
    private UserCourseLearnedMapper userCourseLearnedMapper;

    @Override
    public void progress(Long id, ProgressRequest request) {
        //存入学习总时长
        progressUserLearned(id, request);
        //统计当前课程 & 节是否已学完
        insertUserLearnStats(id, request);
    }

    private void progressUserLearned(Long id, ProgressRequest request) {
        //总学习时长增加
        UserLearned userLearnedSelect = new UserLearned();
        userLearnedSelect.setCourseId(request.getCourseId());
        userLearnedSelect.setSectionId(id);
        userLearnedSelect.setUserId(NAME);
        //获取到历史数据
        UserLearned userLearned = userLearnedMapper.selectOne(userLearnedSelect);
        if (!Objects.isNull(userLearned)) {
            //修改学习时长
            UserLearned userLearnedUpdate = new UserLearned();
            userLearnedUpdate.setId(userLearned.getId());
            userLearnedUpdate.setUpdateDate(new Date());
            userLearnedUpdate.setDuration(userLearned.getDuration() + request.getDeltaDuration());
            userLearnedMapper.updateById(userLearnedUpdate);
            return;
        }
        //新增学习时长
        UserLearned userLearnedInsert = new UserLearned();
        userLearnedInsert.setUserId(NAME);
        userLearnedInsert.setCourseId(request.getCourseId());
        userLearnedInsert.setSectionId(id);
        userLearnedInsert.setCreateDate(new Date());
        userLearnedInsert.setUpdateDate(new Date());
        userLearnedInsert.setDuration(request.getDeltaDuration());
        userLearnedMapper.insert(userLearnedInsert);
    }

    private void insertUserLearnStats(Long id, ProgressRequest request) {
        //处理节,媒体是否已学完
        UserLearnStats userLearnStatsSelect = new UserLearnStats();
        userLearnStatsSelect.setUserId(NAME);
        userLearnStatsSelect.setSectionId(id);
        userLearnStatsSelect.setCourseId(request.getCourseId());
        //获取到历史数据
        UserLearnStats userLearnStats = userLearnStatsMapper.selectOne(userLearnStatsSelect);
        //节状态
        if (!Objects.isNull(userLearnStats)) {
            //修改
            UserLearnStats updateUserLearnStats = new UserLearnStats();
            updateUserLearnStats.setId(userLearnStats.getId());
            updateUserLearnStats.setUpdateDate(new Date());
            //获取是否已学完
            boolean flag = calculateLearned(request, updateUserLearnStats.getMediaDuration(),
                request.getMediaProgress() + request.getMediaProgress());
            updateUserLearnStats.setLearnedStatus("LEARNING");
            if (flag) {
                updateUserLearnStats.setLearnedStatus("COMPALETE");
            }
            userLearnStatsMapper.updateById(updateUserLearnStats);

            //处理课程 是否学完
            courseRedisson(Boolean.FALSE, request, userLearnStats.getLearnedStatus(),
                updateUserLearnStats.getLearnedStatus());
            return;
        }
        //无历史数据则创建
        UserLearnStats insertUserLearnStats = new UserLearnStats();
        insertUserLearnStats.setCourseId(request.getCourseId());
        insertUserLearnStats.setCreateDate(new Date());
        insertUserLearnStats.setUpdateDate(new Date());
        insertUserLearnStats.setLearnedDuration(request.getDeltaDuration());
        //总时长默认都60*1000毫秒
        insertUserLearnStats.setMediaDuration(60 * 1000L);
        //获取是否已学完
        boolean flag = calculateLearned(request, insertUserLearnStats.getMediaDuration(),
            request.getMediaProgress() + request.getMediaProgress());
        insertUserLearnStats.setLearnedStatus("LEARNING");
        if (flag) {
            insertUserLearnStats.setLearnedStatus("COMPALETE");
        }
        insertUserLearnStats.setMediaProgress(request.getMediaProgress());
        insertUserLearnStats.setSectionId(id);
        insertUserLearnStats.setUserId(NAME);
        userLearnStatsMapper.insert(insertUserLearnStats);

        //处理课程 是否学完
        courseRedisson(Boolean.TRUE, request, null,
            insertUserLearnStats.getLearnedStatus());
    }

    private void courseRedisson(Boolean aFalse, ProgressRequest request, String oldLearnedStatus,
        String newLearnedStatus) {
        // 默认连接上127.0.0.1:6379
        RedissonClient redissonClient = Redisson.create();
        // 一个分布式锁,指明锁的名称
        RLock rLock = redissonClient.getLock(NAME + request.getCourseId());
        try {
            if (rLock.tryLock(1, 1, TimeUnit.MINUTES)) {
                log.info("获取到锁");
                courseProgress(Boolean.FALSE, request, oldLearnedStatus,
                    newLearnedStatus);
            }
        } catch (Exception e) {

        } finally {
            rLock.unlock();
        }
    }

    /**
     * @param aFalse           是否第一次进入
     * @param request
     * @param oldLearnedStatus 老的学习状态
     * @param newLearnedStatus 新的学习状态
     */
    private void courseProgress(Boolean aFalse, ProgressRequest request, String oldLearnedStatus,
        String newLearnedStatus) {
        //限制进入条件:
        // 当是第一次进入的时候,前面的百分百是false,导致全部为false。
        //当第二次进入的时候,前面的百分之百为true。后面的就必须为false,才能跳过执行业务代码。
        boolean flag = !aFalse && (newLearnedStatus.equals("LEARNING") || oldLearnedStatus.equals("COMPALETE")
            && newLearnedStatus.equals("COMPALETE"));

        //当为false的时候跳过,不执行return
        if (flag) {
            return;
        }
        Date now = new Date();
        //查询是否是第一次
        UserCourseLearned userCourseLearnedSelect = new UserCourseLearned();
        userCourseLearnedSelect.setCourseId(request.getCourseId());
        userCourseLearnedSelect.setUserId(NAME);
        UserCourseLearned userCourseLearnedOne = userCourseLearnedMapper.selectOne(userCourseLearnedSelect);
        //存在则修改
        if (!Objects.isNull(userCourseLearnedOne)) {
            UserCourseLearned updateUserCourseLearned = new UserCourseLearned();
            updateUserCourseLearned.setId(userCourseLearnedOne.getId());
            updateUserCourseLearned.setUpdateDate(now);
            updateUserCourseLearned.setLastSectionId(request.getSectionId());
            //填充学习状态和学习完的节数量
            populateCourseStatus(updateUserCourseLearned, userCourseLearnedOne.getLearnedSectionCount(), newLearnedStatus);
            userCourseLearnedMapper.updateById(updateUserCourseLearned);
            return;
        }
        //不存在则新增
        UserCourseLearned updateUserCourseInsert = new UserCourseLearned();
        updateUserCourseInsert.setUserId(NAME);
        updateUserCourseInsert.setCourseId(request.getCourseId());
        updateUserCourseInsert.setCreateDate(now);
        updateUserCourseInsert.setLastSectionId(request.getSectionId());
        updateUserCourseInsert.setUpdateDate(now);
        //填充学习状态和学习完的节数量
        populateCourseStatus(updateUserCourseInsert, 0L, newLearnedStatus);
        userCourseLearnedMapper.insert(updateUserCourseInsert);
    }

    /**
     * @param updateUserCourseInsert 参数
     * @param count                  旧的已学完节
     * @param newLearnedStatus       新的学习状态
     */
    private void populateCourseStatus(UserCourseLearned updateUserCourseInsert, Long count, String newLearnedStatus) {
        //已学完
        if (newLearnedStatus.equals("COMPALETE")) {
            //新增一节数量
            updateUserCourseInsert.setLearnedSectionCount(count + 1L);
            //先查询课程下的总节数是多少,这里代码演示,没有建立课程表,直接 写死每个课程总节数是5
            Long sectionLearnedCount = getSectionLearnedCount();
            if(updateUserCourseInsert.getLearnedSectionCount() >= sectionLearnedCount){
                updateUserCourseInsert.setLearnedStatus("COMPALETE");
            }else{
                updateUserCourseInsert.setLearnedStatus("LEARNING");
            }
            return;
        }
        //未学完
        updateUserCourseInsert.setLearnedSectionCount(count);
        updateUserCourseInsert.setLearnedStatus("LEARNING");
    }

    private Long getSectionLearnedCount() {
        return 5L;
    }

    private boolean calculateLearned(ProgressRequest request, Long allDuration, Long learnedDuration) {
        if (request.getSectionType().equals("TEXT")) {
            return Boolean.TRUE;
        }
        if (request.getSectionType().equals("VIDEO")) {
            if (learnedDuration >= (allDuration * 2 / 3)) {
                return Boolean.TRUE;
            }
        }
        return Boolean.FALSE;
    }
}

里面代码比较复杂,都写了注释详解,有不明白的可以联系本人,欢迎大家一起来沟通技术。

  • 16
    点赞
  • 97
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论
以下是一个低功耗蓝牙广播包的代码解析例子,以 Nordic nRF5 SDK 为例: ``` #include <stdbool.h> #include <stdint.h> #include "nrf.h" #include "nordic_common.h" #include "boards.h" #include "nrf_delay.h" #include "nrf_gpio.h" #include "nrf_drv_clock.h" #include "nrf_drv_power.h" #include "nrf_drv_rng.h" #include "nrf_drv_saadc.h" #include "nrfx_wdt.h" #include "app_error.h" #include "app_timer.h" #include "app_util_platform.h" #include "ble_advdata.h" #include "ble_gap.h" #include "ble_nus.h" #include "ble_hci.h" #include "ble_conn_params.h" #include "ble_conn_state.h" #include "ble_db_discovery.h" #include "ble_hci.h" #include "nrf_ble_gatt.h" #include "nrf_ble_qwr.h" #include "nrf_ble_scan.h" #include "nrf_ble_lesc.h" #include "nrf_ble_conn_params.h" #include "nrf_ble_gq.h" #include "nrf_ble_ancs_c.h" #include "nrf_ble_ans_c.h" #include "nrf_ble_bms_c.h" #include "nrf_ble_cscs_c.h" #include "nrf_ble_gattc.h" #include "nrf_ble_ias_c.h" #include "nrf_ble_lbs_c.h" #include "nrf_ble_rscs_c.h" #include "nrf_ble_tps_c.h" #include "nrf_ble_wscs_c.h" #include "bsp_btn_ble.h" #define DEVICE_NAME "LEDBlinker" /**< Name of device. Will be included in the advertising data. */ #define MANUFACTURER_NAME "NordicSemiconductor" /**< Manufacturer. Will be passed to Device Information Service. */ #define APP_BLE_CONN_CFG_TAG 1 /**< A tag identifying the SoftDevice BLE configuration. */ #define APP_ADV_INTERVAL 1600 /**< The advertising interval (in units of 0.625 ms. This value corresponds to 1 sec). */ #define APP_ADV_DURATION 0 /**< The advertising duration (180 seconds) in units of 10 milliseconds. */ #define APP_BLE_OBSERVER_PRIO 3 /**< Application's BLE observer priority. You shouldn't need to modify this value. */ #define APP_BLE_CONN_CFG_TAG 1 /**< A tag identifying the SoftDevice BLE configuration. */ #define APP_FEATURE_NOT_SUPPORTED BLE_GATT_STATUS_ATTERR_APP_BEGIN + 2 /**< Reply when unsupported features are requested. */ static ble_gap_adv_params_t m_adv_params; /**< Parameters to be passed to the stack when starting advertising. */ static uint8_t m_adv_handle = BLE_GAP_ADV_SET_HANDLE_NOT_SET; /**< Advertising handle used to identify an advertising set. */ static uint8_t m_enc_advdata[BLE_GAP_ADV_SET_DATA_SIZE_MAX]; /**< Buffer for storing an encoded advertising set. */ static uint8_t m_enc_scandata[BLE_GAP_ADV_SET_DATA_SIZE_MAX]; /**< Buffer for storing an encoded advertising set. */ static void advertising_start(void) { ret_code_t err_code; err_code = sd_ble_gap_adv_start(m_adv_handle, APP_BLE_CONN_CFG_TAG); APP_ERROR_CHECK(err_code); } static void advertising_init(void) { ret_code_t err_code; ble_advdata_t advdata; ble_advdata_manuf_data_t manuf_data; manuf_data.company_identifier = 0xFFFF; manuf_data.data.p_data = (uint8_t *) "Hello"; manuf_data.data.size = 5; memset(&advdata, 0, sizeof(advdata)); advdata.name_type = BLE_ADVDATA_FULL_NAME; advdata.include_appearance = true; advdata.flags = BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE; advdata.p_manuf_specific_data = &manuf_data; memset(m_enc_advdata, 0, sizeof(m_enc_advdata)); memset(m_enc_scandata, 0, sizeof(m_enc_scandata)); err_code = ble_advdata_encode(&advdata, m_enc_advdata, &m_adv_params.adv_data.len); APP_ERROR_CHECK(err_code); err_code = sd_ble_gap_adv_data_set(m_adv_handle, m_enc_advdata, m_adv_params.adv_data.len, m_enc_scandata, m_adv_params.scan_rsp_data.len); APP_ERROR_CHECK(err_code); } static void gap_params_init(void) { ret_code_t err_code; ble_gap_conn_params_t gap_conn_params; err_code = sd_ble_gap_tx_power_set(BLE_GAP_TX_POWER_ROLE_ADV, m_adv_handle, 4); APP_ERROR_CHECK(err_code); err_code = sd_ble_gap_conn_params_get(&gap_conn_params); APP_ERROR_CHECK(err_code); gap_conn_params.min_conn_interval = MSEC_TO_UNITS(20, UNIT_1_25_MS); gap_conn_params.max_conn_interval = MSEC_TO_UNITS(75, UNIT_1_25_MS); gap_conn_params.slave_latency = 0; gap_conn_params.conn_sup_timeout = MSEC_TO_UNITS(4000, UNIT_10_MS); err_code = sd_ble_gap_conn_params_set(&gap_conn_params); APP_ERROR_CHECK(err_code); } int main(void) { ret_code_t err_code; err_code = nrf_drv_clock_init(); APP_ERROR_CHECK(err_code); nrf_drv_clock_lfclk_request(NULL); err_code = nrf_drv_power_init(NULL); APP_ERROR_CHECK(err_code); err_code = nrf_drv_rng_init(NULL); APP_ERROR_CHECK(err_code); err_code = app_timer_init(); APP_ERROR_CHECK(err_code); err_code = bsp_init(BSP_INIT_LED, NULL); APP_ERROR_CHECK(err_code); err_code = sd_ble_gap_adv_set_configure(&m_adv_handle, &m_enc_advdata[0], &m_adv_params, &m_enc_scandata[0]); APP_ERROR_CHECK(err_code); gap_params_init(); advertising_init(); advertising_start(); while (1) { // Do nothing. } } ``` 该例子使用了 Nordic nRF5 SDK,实现了一个低功耗蓝牙广播包的发送。在 `advertising_init()` 函数中,定义了广播包的参数和内容,使用 `ble_advdata_encode()` 函数将广播包编码成二进制数据,并通过 `sd_ble_gap_adv_data_set()` 函数设置广播包数据。在 `advertising_start()` 函数中,启动了广播传输。 需要注意的是,该例子使用了 Nordic nRF5 SDK,如果您使用其他的嵌入式平台和开发工具,代码会有所不同。但是,低功耗蓝牙广播包的实现原理是相同的。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

后端从入门到精通

你的鼓励是我最大的动力~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值