java SpringBoot 集成 阿里云视频直播 完成直播功能

经历了几天的周折,近期才把项目完成,在这里与大家分享一下踩坑之路,也方便日后有类似项目,可以借阅一番

该项目是一个H5直播,采用m3u8格式完成直播的展示。通过推流地址,借助第三方推流工具实现直播效果,比如:OBS 或者一些微信小程序 小推流。。等等

项目介绍:根据域名生成推流、播流地址,推流回调,检查推流状态。直播视频存储至OSS,视频回调,统计当前在线人数等等

项目需要:需要现在阿里云上配置你的域名,推流域名,播流域名,推流回调地址,OSS存储,播流跨域问题等等

进入正题:
根据推拉域名生成推流地址与播流地址

配置pom文件

  <!--阿里云直播-->
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>aliyun-java-sdk-core</artifactId>
            <version>4.4.6</version>
        </dependency>
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>aliyun-java-sdk-live</artifactId>
            <version>3.8.0</version>
        </dependency>
            <!-- Hutool工具包 -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>4.0.12</version>
        </dependency>
配置你的阿里云参数AliYunConfig  streamName这里是自定义的直播类型  根据你的需求而定,我的直播类型这里就没写
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
@Data
public class AliYunConfig {
    /**
     * 推流域名
     */
    @Value("")
    private String aliyunLivePushDomain;
    /**
     * 拉流域名
     */
    @Value("")
    private String aliyunLivePullDomain;
    /**
     * 直播测试appName
     */
    @Value("")
    private String aliyunLiveAppName;
    /**
     * 直播测试streamName{直播类型}_{类型id}
     */
    @Value("")
    private String aliyunLiveStreamName;
    /**
     * 推流鉴权url key
     */
    @Value("")
    private String aliyunLivePushIdentKey;
    /**
     * 拉流鉴权url key
     */
    @Value("")
    private String aliyunLivePullIdentKey;

    /**
     * 鉴权url的有效时间(秒),默认30分钟,1800秒 key
     */
    @Value("1800")
    private Integer aliyunLiveIdentUrlValidTime;
}
配置阿里云AliYunUtil
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.gs.body.alilive.AliyunLiveUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class AliYunUtil {
    private static final Logger log = LoggerFactory.getLogger(AliyunLiveUtil.class);
    /**
     * 推拉流地址示例:
     * rtmp://www.ttest.ygdjonline.com/a/a?auth_key=1558065152-0-0-c3cb54d946c0590ca9aeee63573201ee
     * 播流地址
     * 原画
     * rtmp://www.btest.ygdjonline.com/a/a?auth_key=1558065152-0-0-fc711455c0815aeb581385f33451d5b4
     * http://www.btest.ygdjonline.com/a/a.flv?auth_key=1558065152-0-0-221abff1da1ee32151e365cf0dd42a53
     * http://www.btest.ygdjonline.com/a/a.m3u8?auth_key=1558065152-0-0-72124fcc3aee3404b0d65dcc114e207f
     */

    /**
     * 根据源id创建该id的推流url
     *
     * @param sourceId
     * @param aliyunConfig
     * @return
     */
    public static String createPushUrl(String sourceId, AliYunConfig aliyunConfig) {

        // 推流域名
        String pushDomain = aliyunConfig.getAliyunLivePushDomain();
        // 应用名称
        String appName = aliyunConfig.getAliyunLiveAppName();
        // 流名称
        String streamName = StrUtil.format(sourceId);
        // 推流签名key
        String pushIdentKey = aliyunConfig.getAliyunLivePushIdentKey();
        // 签名url有效时间
        Integer identUrlValidTime = aliyunConfig.getAliyunLiveIdentUrlValidTime();
        //log.info("签名url有效时间" + identUrlValidTime);
        // 计算过期时间
        String timestamp = String.valueOf((System.currentTimeMillis() / 1000) + identUrlValidTime);
        // log.info("计算过期时间" + timestamp);
        // 组合推流域名前缀
        //rtmp://{pushDomain}/{appName}/{streamName}
        String rtmpUrl = StrUtil.format("rtmp://{}/{}/{}", pushDomain, appName, streamName);

        // 组合md5加密串
        ///{appName}/{streamName}-{timestamp}-0-0-{pushIdentKey}
        String md5Url = StrUtil.format("/{}/{}-{}-0-0-{}", appName, streamName, timestamp, pushIdentKey);
         log.info("组合md5加密串"+md5Url);
        // md5加密
        String md5Str = DigestUtil.md5Hex(md5Url);
        // log.info("md5加密串,md5Url=" + md5Url + "------md5加密结果,md5Str=" + md5Str);

        // 组合最终鉴权过的推流域名
//      {rtmpUrl}?auth_key={timestamp}-0-0-{md5Str}
        String finallyPushUrl = StrUtil.format("{}?auth_key={}-0-0-{}", rtmpUrl, timestamp, md5Str);
         log.info("最终鉴权过的推流域名=" + finallyPushUrl);

        return finallyPushUrl;
    }

    /**
     * 创建拉流域名,key=rtmpUrl、flvUrl、m3u8Url,代表三种拉流类型域名
     *
     * @param sourceId
     * @param aliyunConfig
     * @return
     */
    public static String createPullUrl(String sourceId, AliYunConfig aliyunConfig) {

        // 拉流域名
        String pullDomain = aliyunConfig.getAliyunLivePullDomain();
        // 应用名称
        String appName = aliyunConfig.getAliyunLiveAppName();
        // 流名称
        String streamName = StrUtil.format(sourceId);
        // 拉流签名key
        String pullIdentKey = aliyunConfig.getAliyunLivePullIdentKey();
        // 签名url有效时间
        Integer identUrlValidTime = aliyunConfig.getAliyunLiveIdentUrlValidTime();

        // 计算过期时间
        String timestamp = String.valueOf((System.currentTimeMillis() / 1000) + identUrlValidTime);

        // 组合通用域名
//      {pullDomain}/{appName}/{streamName}
        String pullUrl = StrUtil.format("{}/{}/{}", pullDomain, appName, streamName);
        //log.info("组合通用域名,pullUrl=" + pullUrl);

        // 组合md5加密串
//      /{appName}/{streamName}-{timestamp}-0-0-{pullIdentKey}
        String md5Url = StrUtil.format("/{}/{}-{}-0-0-{}", appName, streamName, timestamp, pullIdentKey);
        String md5FlvUrl = StrUtil.format("/{}/{}.flv-{}-0-0-{}", appName, streamName, timestamp, pullIdentKey);
        String md5M3u8Url = StrUtil.format("/{}/{}.m3u8-{}-0-0-{}", appName, streamName, timestamp, pullIdentKey);

        // md5加密
        String md5Str = DigestUtil.md5Hex(md5Url);
        String md5FlvStr = DigestUtil.md5Hex(md5FlvUrl);
        String md5M3u8Str = DigestUtil.md5Hex(md5M3u8Url);
        //log.info("md5加密串,md5Url    =" + md5Url + "       ------     md5加密结果,md5Str=" + md5Str);
        //log.info("md5加密串,md5FlvUrl =" + md5FlvUrl + "    ------    md5加密结果,md5FlvStr=" + md5FlvStr);
        //log.info("md5加密串,md5M3u8Url=" + md5M3u8Url + "   ------    md5加密结果,md5M3u8Str=" + md5M3u8Str);

        // 组合三种拉流域名前缀
//        rtmp://{pullUrl}?auth_key={timestamp}-0-0-{md5Str}
        String rtmpUrl = StrUtil.format("rtmp://{}?auth_key={}-0-0-{}", pullUrl, timestamp, md5Str);
//        http://{pullUrl}.flv?auth_key={timestamp}-0-0-{md5FlvStr}
        String flvUrl = StrUtil.format("http://{}.flv?auth_key={}-0-0-{}", pullUrl, timestamp, md5FlvStr);
//        http://{pullUrl}.m3u8?auth_key={timestamp}-0-0-{md5M3u8Str}
        String m3u8Url = StrUtil.format("http://{}.m3u8?auth_key={}-0-0-{}", pullUrl, timestamp, md5M3u8Str);

        log.info("最终鉴权过的拉流rtmp域名=" + rtmpUrl);
        log.info("最终鉴权过的拉流flv域名 =" + flvUrl);
        log.info("最终鉴权过的拉流m3u8域名=" + m3u8Url);
        return m3u8Url;
    }
}

通过GetMapping 生成推流 拉流地址

  @Resource
    private AliYunConfig aliyunConfig;
  /**
     * 生成推流播流地址
     * sourceId  在这里我将它设置为房间号
     */
    @GetMapping("/save_Live")
    public void save_Live(HttpServletRequest request, @RequestParam("sourceId") String sourceId){
        try {
            //生成推流地址
            String pushUrl = AliYunUtil.createPushUrl(sourceId, aliyunConfig);
            //生成播流地址
            String pullUrl = AliYunUtil.createPullUrl(sourceId, aliyunConfig);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

如果你的播流地址,存在跨域问题,请在阿里云上配置HTTP响应头,这里添加一个*号即可
在这里插入图片描述
现在开始走回调接口,这里需要你在阿里云上配置你的回调地址,是以http开头的

在这里插入图片描述
在这里插入图片描述
推流地址接口 同样也是GET形式的 阿里云返回的参数定义可以参考:https://help.aliyun.com/document_detail/84943.html?spm=a2c4g.11186623.2.27.44c63dd286Dtq1#concept-84943-zh

    /**
     * 推流地址回调接口 根据返回状态值进行业务处理
     */
    @GetMapping("/callBackPath")
    public void test(HttpServletRequest request){
        /**
         * 返回参数
         * action:[publish].......
         */
        try {
            Map<String, String[]> parameterMap = request.getParameterMap();
            ApiLiveModel model = JSONObject.parseObject(JSON.toJSONString(parameterMap),ApiLiveModel.class);
            // 实现效果   根据回调接口 publish_done:关闭直播 publish 开启直播
            if (model != null){
                String action = model.getAction().get(0); //获取直播状态值
                String houseId = model.getId().get(0); //获取直播房间号

                if (action.equals("publish")){
                    log.info("开启直播状态");
                    //业务处理
                }else if (action.equals("publish_done")){

                    log.info("关闭直播状态");
                    //业务处理
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

定义返回参数的model类

import java.util.List;

public class ApiLiveModel {

    private List<String> action;
    private List<String> ip;
    private List<String> id;
    private List<String> app;
    private List<String> appname;
    private List<String> time;
    private List<String> usrargs;
    private List<String> node;

    public List<String> getAction() {
        return action;
    }

    public void setAction(List<String> action) {
        this.action = action;
    }

    public List<String> getIp() {
        return ip;
    }

    public void setIp(List<String> ip) {
        this.ip = ip;
    }

    public List<String> getId() {
        return id;
    }

    public void setId(List<String> id) {
        this.id = id;
    }

    public List<String> getApp() {
        return app;
    }

    public void setApp(List<String> app) {
        this.app = app;
    }

    public List<String> getAppname() {
        return appname;
    }

    public void setAppname(List<String> appname) {
        this.appname = appname;
    }

    public List<String> getTime() {
        return time;
    }

    public void setTime(List<String> time) {
        this.time = time;
    }

    public List<String> getUsrargs() {
        return usrargs;
    }

    public void setUsrargs(List<String> usrargs) {
        this.usrargs = usrargs;
    }

    public List<String> getNode() {
        return node;
    }

    public void setNode(List<String> node) {
        this.node = node;
    }
}

视频存储至阿里云OSS,需要配置Bucket,然后在域名管理,录制配置中添加配置
在这里插入图片描述
这样你的直播视频,就存放至阿里云上了,在这里有一个问题需要解决,m3u8格式的在线预览,需要配置一下跨域问题,不然无法播放。参考地址:https://help.aliyun.com/document_detail/31903.html?spm=a2c4g.11174283.6.1124.369f7da2kWLpA2

接下来就是视频回调了,同样也需要在阿里云配置你的视频回调接口,这里也是http开头的
在这里插入图片描述
接下来就是代码片段了,具体的返回参数,请参考:https://help.aliyun.com/document_detail/84935.html?spm=5176.13910061.0.0.6b2b7018cRAX1h&aly_as=qJRVAQTxQ

   //录制视频存储到OSS 回调接口
    @PostMapping("/api_p/addLive")
    public void addLive(@RequestBody ApiSaveLiveModel model) throws IOException {
        try{
            /**
             * 返回参数
             * {
             *   "domain": "live.aliyunlive.com",
             *   "app": "live",
             *   "stream": "hello",
             *   "uri": "live/hello/0_2017-03-08-23:09:46_2017-03-08-23:10:40.flv",
             *   "duration": 69.403,
             *   "start_time": 1488985786,
             *   "stop_time": 1488985840
             * }
             * 具体参数详情请参考:https://help.aliyun.com/document_detail/84935.html?spm=5176.13910061.0.0.6b2b7018cRAX1h&aly_as=qJRVAQTxQ
             */
            log.info("保存作品成功");
        }catch (Exception e){
            log.info("保存作品失败");
            e.printStackTrace();
        }
    }

回调参数model

public class ApiSaveLiveModel {

    /**
     * domain : live.aliyunlive.com
     * app : live
     * stream : hello
     * uri : live/hello/0_2017-03-08-23:09:46_2017-03-08-23:10:40.flv
     * duration : 69.403
     * start_time : 1488985786
     * stop_time : 1488985840
     */

    private String domain;
    private String app;
    private String stream;
    private String uri;
    private double duration;
    private int start_time;
    private int stop_time;

    public String getDomain() {
        return domain;
    }

    public void setDomain(String domain) {
        this.domain = domain;
    }

    public String getApp() {
        return app;
    }

    public void setApp(String app) {
        this.app = app;
    }

    public String getStream() {
        return stream;
    }

    public void setStream(String stream) {
        this.stream = stream;
    }

    public String getUri() {
        return uri;
    }

    public void setUri(String uri) {
        this.uri = uri;
    }

    public double getDuration() {
        return duration;
    }

    public void setDuration(double duration) {
        this.duration = duration;
    }

    public int getStart_time() {
        return start_time;
    }

    public void setStart_time(int start_time) {
        this.start_time = start_time;
    }

    public int getStop_time() {
        return stop_time;
    }

    public void setStop_time(int stop_time) {
        this.stop_time = stop_time;
    }

    @Override
    public String toString() {
        return "ApiSaveLiveModel{" +
                "domain='" + domain + '\'' +
                ", app='" + app + '\'' +
                ", stream='" + stream + '\'' +
                ", uri='" + uri + '\'' +
                ", duration=" + duration +
                ", start_time=" + start_time +
                ", stop_time=" + stop_time +
                '}';
    }
}

接下来就是 根据当前播流地址,统计在线人数,统计HLS直播的在线人数, 我们需要客户端在请求的参数中带上唯一标识用户的uuid,在阿里云cdn使用的默认参数是aliyun_uuid,言下之意就是每当用户访问的时候,会携带这个uuid,通过这个uuid 来获取当前播流域名的在线人数

重要说明:统计m3u8格式的在线人数,需要通过阿里云工单申请

具体的参数可以找阿里云人工要一份,是PDF版,网上也有博客,但是我找不到了

   //根据播流地址 查询当前在线人数
    public static Integer getPeopleSum() throws ClientException {
        /**
         * 返回参数
         *
         *  time : 2020-04-20T03:40:00Z
         *  requestId : A76B853C-AB5A-4F02-96C5-BDAB1A485FAC
         *  usageData : [{"domainName":"boliu.comwinwin.com","streamInfos":[{"streamName":"/jingbei/82889.m3u8","infos":[{"downFlow":609.5467,"rate":"origin","online":1}]}]}]
         *
         *  online:指的是当前的在线人数
         *
         */
        try{
            DescribeHlsLiveStreamRealTimeBpsDataRequest listRequest2 = new DescribeHlsLiveStreamRealTimeBpsDataRequest();
            listRequest2.setDomainName("boliu.comwinwin.com"); //播流域名
            listRequest2.setTime(getTime());  //UTC格式当前时间           归属地
            DefaultProfile profile = DefaultProfile.getProfile("cn-beijing", "LTAI4G84ApyG4K1x4v9JHaXC", "d4iWslw08vidnqtYB8IpaHLLfsVp9e");
            IAcsClient iAcsClient = new DefaultAcsClient(profile);//配置的参数
            DescribeHlsLiveStreamRealTimeBpsDataResponse response = iAcsClient.getAcsResponse(listRequest2);//获取直播人数       List<DescribeHlsLiveStreamRealTimeBpsDataResponse.UsageDataPerDomain> usageDatas = usageDatasResponse.getUsageData();//获取直播状态        for (DescribeHlsLiveStreamRealTimeBpsDataResponse.UsageDataPerDomain usageData : usageDatas) {     if (usageData.getDomainName().equals("播流域名(补全)")) {
            //获取阿里云返回的结果,根据结果处理得到当前播放流下在线人数
            String str = new Gson().toJson(response);
            ApiPersonSumModel mode = JSON.parseObject(str, ApiPersonSumModel.class);

            return 0;
        }catch (Exception e){
            return 0;
        }

    }
    /**
     * 获取当前在线时间  UTC 格式时间
     * @return
     */
    public static String getTime(){
        Date date = new Date();// 获取当前时间
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        int zoneOffset = calendar.get(Calendar.ZONE_OFFSET);
        int dstOffset = calendar.get(Calendar.DST_OFFSET);
        calendar.add(Calendar.MILLISECOND, -(zoneOffset + dstOffset));
        long timeInMillis = calendar.getTimeInMillis();
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
        return df.format(timeInMillis);
    }

返回参数的model类

import java.util.List;

public class ApiPersonSumModel {

    /**
     * time : 2020-04-20T03:40:00Z
     * requestId : A76B853C-AB5A-4F02-96C5-BDAB1A485FAC
     * usageData : [{"domainName":"boliu.comwinwin.com","streamInfos":[{"streamName":"/jingbei/82889.m3u8","infos":[{"downFlow":609.5467,"rate":"origin","online":1}]}]}]
     */

    private String time;
    private String requestId;
    private List<UsageDataApiPersonSumModel> usageData;

    public String getTime() {
        return time;
    }

    public void setTime(String time) {
        this.time = time;
    }

    public String getRequestId() {
        return requestId;
    }

    public void setRequestId(String requestId) {
        this.requestId = requestId;
    }

    public List<UsageDataApiPersonSumModel> getUsageData() {
        return usageData;
    }

    public void setUsageData(List<UsageDataApiPersonSumModel> usageData) {
        this.usageData = usageData;
    }

    public static class UsageDataApiPersonSumModel {
        /**
         * domainName : boliu.comwinwin.com
         * streamInfos : [{"streamName":"/jingbei/82889.m3u8","infos":[{"downFlow":609.5467,"rate":"origin","online":1}]}]
         */

        private String domainName;
        private List<StreamInfosApiPersonSumModel> streamInfos;

        public String getDomainName() {
            return domainName;
        }

        public void setDomainName(String domainName) {
            this.domainName = domainName;
        }

        public List<StreamInfosApiPersonSumModel> getStreamInfos() {
            return streamInfos;
        }

        public void setStreamInfos(List<StreamInfosApiPersonSumModel> streamInfos) {
            this.streamInfos = streamInfos;
        }

        public static class StreamInfosApiPersonSumModel {
            /**
             * streamName : /jingbei/82889.m3u8
             * infos : [{"downFlow":609.5467,"rate":"origin","online":1}]
             */

            private String streamName;
            private List<InfosApiPersonSumModel> infos;

            public String getStreamName() {
                return streamName;
            }

            public void setStreamName(String streamName) {
                this.streamName = streamName;
            }

            public List<InfosApiPersonSumModel> getInfos() {
                return infos;
            }

            public void setInfos(List<InfosApiPersonSumModel> infos) {
                this.infos = infos;
            }

            public static class InfosApiPersonSumModel {
                /**
                 * downFlow : 609.5467
                 * rate : origin
                 * online : 1
                 */

                private double downFlow;
                private String rate;
                private int online;

                public double getDownFlow() {
                    return downFlow;
                }

                public void setDownFlow(double downFlow) {
                    this.downFlow = downFlow;
                }

                public String getRate() {
                    return rate;
                }

                public void setRate(String rate) {
                    this.rate = rate;
                }

                public int getOnline() {
                    return online;
                }

                public void setOnline(int online) {
                    this.online = online;
                }
            }
        }
    }
}

至此阿里云视频直播,推拉流、推流回调、视频存储、存储回调、各项配置、统计在线人数,就搞定了!

经过跟阿里云工单讨教的答案,以及自己在博客上找的资源,最终整理了一篇关于阿里云开发直播的东西。

脱坑不易,与大家分享一下,如有错误的地方,请大佬指教。

  • 11
    点赞
  • 84
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值