java SpringBoot 集成 阿里云视频直播 完成直播功能_第三方直播阿里云spirngboot-程序员宅基地

技术标签: java  

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

开发直播前先满足已下条件

1.开通视频直播功能

2.购买好了OSS存储

3.购买两个域名并且备案好,一个用来拉流,一个用来播流,

4.建议CNAM加速也设置好这样直播没那么卡

该项目是一个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>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.48</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;
     /**
     * OSS-区域代码
     */
    @Value("cn-shanghai")
    private String regionId;
    
    /**
     * OSS-RAM 访问控制-人员管理-用户 AccessKey
     */
    @Value("")
    private String accessKeyId;
 
    /**
     * OSS-RAM 访问控制-人员管理-用户 secret
     */
    @Value("")
    private String secret;
}
配置阿里云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;
                }
            }
        }
    }
}

定时器查询录制保存的视频

/**
	 * 每隔30分钟执行一次 直播录制视频没有回调的
	 * 要重新调用阿里云录制视频查询接口:
	 * 参考地址 https://help.aliyun.com/document_detail/35421.html?spm=a2c4g.11186623.6.757.30f151e7YjXnFO
	 */
	@Scheduled(cron = "0 */30 * * * ?")
	public void updatePublisPushTime() {
		List<CourseItem> courseItemList = courseItemDao.getLiveList();
		if(courseItemList != null && !courseItemList.isEmpty()){
			
			for(CourseItem courseItem :courseItemList){
				//封装录制视频查询接口需要的参数
				DefaultProfile profile = DefaultProfile.getProfile(aliYunLiveConfig.getRegionId(), 
						aliYunLiveConfig.getAccessKeyId(), aliYunLiveConfig.getSecret());
			    IAcsClient client = new DefaultAcsClient(profile);
			    DescribeLiveStreamRecordIndexFilesResponse response = null;
			    List<RecordIndexInfo> recordIndexInfoList = null;
			    DescribeLiveStreamRecordIndexFilesRequest request = new DescribeLiveStreamRecordIndexFilesRequest();
			    request.setRegionId(aliYunLiveConfig.getRegionId());
			    request.setAppName(aliYunLiveConfig.getAliyunLiveAppName()); //直播流所属应用名称。
			    request.setStreamName(courseItem.getId().toString()); 	//直播流名称。 也是课节id
			    /**
			     *  开始时间,格式:UTC时间。示例:2015-12-01T17:36:00Z
			     */
			    request.setStartTime(DateUtil.utcDateStr(courseItem.getBeginDate()));
			    /**
			     * 结束时间。与StartTime的间隔时间不能超过4天。格式:UTC时间。示例:2015-12-01T17:36:00Z。
			     */
			    request.setEndTime(DateUtil.utcDateStr(courseItem.getEndDate()));
			    request.setDomainName(aliYunLiveConfig.getAliyunLivePullDomain());	//您的加速域名  
			    try {
			    	 //调用查询接口
			    	 response = client.getAcsResponse(request);
			    	 log.info("调用阿里云录制视频查询接口返回: " + new Gson().toJson(response));
			    	 recordIndexInfoList = response.getRecordIndexInfoList();
			    	 if(recordIndexInfoList != null && !recordIndexInfoList.isEmpty() ){
			    		 courseItem.setItemUrl(recordIndexInfoList.get(0).getRecordUrl());
			    		 courseItemDao.update(courseItem);
			    	 }
			     } catch (ClientException e) {
			    	log.error("调用阿里云录制视频查询接口错误ErrCode: " + e.getErrCode());
			    	log.error("调用阿里云录制视频查询接口错误ErrMsg: " + e.getErrMsg());
			    	log.error("调用阿里云录制视频查询接口错误RequestId: " + e.getRequestId());
			     } catch (Exception e1){
			    	log.error("调用阿里云录制视频查询接口错误: " + e1.getMessage());
			     }
			}
		
		}

	}
/**
     * 
     * <p>Description: 北京时间转化为UTC时间</p>
     * @param date 指定时间
     * @return
     *
     */
    public static String utcDateStr(Date date) {
    	try{
    		SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
            long localTimeInMillis=date.getTime();
            /** long时间转换成Calendar */
            Calendar calendar= Calendar.getInstance();
            calendar.setTimeInMillis(localTimeInMillis);
            /** 取得时间偏移量 */
            int zoneOffset = calendar.get(java.util.Calendar.ZONE_OFFSET);
            /** 取得夏令时差 */
            int dstOffset = calendar.get(java.util.Calendar.DST_OFFSET);
            /** 从本地时间里扣除这些差量,即可以取得UTC时间*/
            calendar.add(java.util.Calendar.MILLISECOND, -(zoneOffset + dstOffset));
            /** 取得的时间就是UTC标准时间 */
            Date utcDate=new Date(calendar.getTimeInMillis());
            String utcDateStr = format.format(utcDate);
            return utcDateStr;
    	}catch(Exception e){
    		e.printStackTrace();
    		return null;
    	}
    	
    }
/**
	 * 同一个推流 多个视频文件合并成一个 只支持m3u8录制格式
	 * 创建某个时间范围的M3U8索引文件
	 * 参考地址  https://help.aliyun.com/document_detail/35417.html
	 */
	public void createLiveStreamRecordIndexFiles(){
		DefaultProfile profile = DefaultProfile.getProfile(aliYunLiveConfig.getRegionId(), 
				aliYunLiveConfig.getAccessKeyId(), aliYunLiveConfig.getSecret());
	    IAcsClient client = new DefaultAcsClient(profile);
	    CreateLiveStreamRecordIndexFilesRequest request = new CreateLiveStreamRecordIndexFilesRequest();
	    request.setRegionId(aliYunLiveConfig.getRegionId());
	    request.setAppName(aliYunLiveConfig.getAliyunLiveAppName());
	    request.setStreamName("61");
	    request.setOssEndpoint("oss-cn-shenzhen.aliyuncs.com");
	    request.setOssBucket("fssh-video");
	    request.setOssObject("course/live/record/live/record/61.m3u8");
	    request.setStartTime("2020-07-12T17:36:00Z");
	    request.setEndTime("2020-07-14T17:36:00Z");
	    request.setDomainName(aliYunLiveConfig.getAliyunLivePullDomain());

	    try {
	         CreateLiveStreamRecordIndexFilesResponse response = client.getAcsResponse(request);
	         log.info(new Gson().toJson(response));
	    } catch (ServerException e) {
	         e.printStackTrace();
	    } catch (ClientException e) {
	    	 log.info("ErrCode:" + e.getErrCode());
	    	 log.info("ErrMsg:" + e.getErrMsg());
	    	 log.info("RequestId:" + e.getRequestId());
	    }

	    }

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

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

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

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/zengsange/article/details/106813293

智能推荐

js-选项卡原理_选项卡js原理-程序员宅基地

文章浏览阅读90次。【代码】js-选项卡原理。_选项卡js原理

设计模式-原型模式(Prototype)-程序员宅基地

文章浏览阅读67次。原型模式是一种对象创建型模式,它采用复制原型对象的方法来创建对象的实例。它创建的实例,具有与原型一样的数据结构和值分为深度克隆和浅度克隆。浅度克隆:克隆对象的值类型(基本数据类型),克隆引用类型的地址;深度克隆:克隆对象的值类型,引用类型的对象也复制一份副本。UML图:具体代码:浅度复制:import java.util.List;/*..._prototype 设计模式

个性化政府云的探索-程序员宅基地

文章浏览阅读59次。入选国内首批云计算服务创新发展试点城市的北京、上海、深圳、杭州和无锡起到了很好的示范作用,不仅促进了当地产业的升级换代,而且为国内其他城市发展云计算产业提供了很好的借鉴。据了解,目前国内至少有20个城市确定将云计算作为重点发展的产业。这势必会形成新一轮的云计算基础设施建设的**。由于云计算基础设施建设具有投资规模大,运维成本高,投资回收周期长,地域辐射性强等诸多特点,各地在建...

STM32问题集之BOOT0和BOOT1的作用_stm32boot0和boot1作用-程序员宅基地

文章浏览阅读9.4k次,点赞2次,收藏20次。一、功能及目的 在每个STM32的芯片上都有两个管脚BOOT0和BOOT1,这两个管脚在芯片复位时的电平状态决定了芯片复位后从哪个区域开始执行程序。BOOT1=x BOOT0=0 // 从用户闪存启动,这是正常的工作模式。BOOT1=0 BOOT0=1 // 从系统存储器启动,这种模式启动的程序_stm32boot0和boot1作用

C语言函数递归调用-程序员宅基地

文章浏览阅读3.4k次,点赞2次,收藏22次。C语言函数递归调用_c语言函数递归调用

明日方舟抽卡模拟器wiki_明日方舟bilibili服-明日方舟bilibili服下载-程序员宅基地

文章浏览阅读410次。明日方舟bilibili服是一款天灾驾到战斗热血的创新二次元废土风塔防手游,精妙的二次元纸片人设计,为宅友们源源不断更新超多的纸片人老婆老公们,玩家将扮演废土正义一方“罗德岛”中的指挥官,与你身边的感染者们并肩作战。与同类塔防手游与众不同的几点,首先你可以在这抽卡轻松获得稀有,同时也可以在战斗体系和敌军走位机制看到不同。明日方舟bilibili服设定:1、起因不明并四处肆虐的天灾,席卷过的土地上出..._明日方舟抽卡模拟器

随便推点

Maven上传Jar到私服报错:ReasonPhrase: Repository version policy: SNAPSHOT does not allow version: xxx_repository version policy snapshot does not all-程序员宅基地

文章浏览阅读437次。Maven上传Jar到私服报错:ReasonPhrase: Repository version policy: SNAPSHOT does not allow version: xxx_repository version policy snapshot does not all

斐波那契数列、素数、质数和猴子吃桃问题_斐波那契日-程序员宅基地

文章浏览阅读1.2k次。斐波那契数列(Fibonacci Sequence)是由如下形式的一系列数字组成的:0, 1, 1, 2, 3, 5, 8, 13, 21, 34, …上述数字序列中反映出来的规律,就是下一个数字是该数字前面两个紧邻数字的和,具体如下所示:示例:比如上述斐波那契数列中的最后两个数,可以推导出34后面的数为21+34=55下面是一个更长一些的斐波那契数列:0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584,_斐波那契日

PHP必会面试题_//该层循环用来控制每轮 冒出一个数 需要比较的次数-程序员宅基地

文章浏览阅读363次。PHP必会面试题1. 基础篇1. 用 PHP 打印出前一天的时间格式是 2017-12-28 22:21:21? //&gt;&gt;1.当前时间减去一天的时间,然后再格式化echo date('Y-m-d H:i:s',time()-3600*24);//&gt;&gt;2.使用strtotime,可以将任何字符串时间转换成时间戳,仅针对英文echo date('Y-m-d H:i:s',str..._//该层循环用来控制每轮 冒出一个数 需要比较的次数

windows用mingw(g++)编译opencv,opencv_contrib,并install安装_opencv mingw contrib-程序员宅基地

文章浏览阅读1.3k次,点赞26次,收藏26次。windows下用mingw编译opencv貌似不支持cuda,选cuda会报错,我无法解决,所以没选cuda,下面两种编译方式支持。打开cmake gui程序,在下面两个框中分别输入opencv的源文件和编译目录,build-mingw为你创建的目录,可自定义命名。1、如果已经安装Qt,则Qt自带mingw编译器,从Qt安装目录找到编译器所在目录即可。1、如果已经安装Qt,则Qt自带cmake,从Qt安装目录找到cmake所在目录即可。2、若未安装Qt,则安装Mingw即可,参考我的另外一篇文章。_opencv mingw contrib

5个高质量简历模板网站,免费、免费、免费_hoso模板官网-程序员宅基地

文章浏览阅读10w+次,点赞42次,收藏309次。今天给大家推荐5个好用且免费的简历模板网站,简洁美观,非常值得收藏!1、菜鸟图库https://www.sucai999.com/search/word/0_242_0.html?v=NTYxMjky网站主要以设计类素材为主,办公类素材也很多,简历模板大部个偏简约风,各种版式都有,而且经常会更新。最重要的是全部都能免费下载。2、个人简历网https://www.gerenjianli.com/moban/这是一个专门提供简历模板的网站,里面有超多模板个类,找起来非常方便,风格也很多样,无须注册就能免费下载,_hoso模板官网

通过 TikTok 联盟提高销售额的 6 个步骤_tiktok联盟-程序员宅基地

文章浏览阅读142次。你听说过吗?该计划可让您以推广您的产品并在成功销售时支付佣金。它提供了新的营销渠道,使您的产品呈现在更广泛的受众面前并提高品牌知名度。此外,TikTok Shop联盟可以是一种经济高效的产品或服务营销方式。您只需在有人购买时付费,因此不存在在无效广告上浪费金钱的风险。这些诱人的好处是否足以让您想要开始您的TikTok Shop联盟活动?如果是这样,本指南适合您。_tiktok联盟