加载中...
avatar

阿里云视频直播

阿里云视频直播

官网概念:

视频直播服务(ApsaraVideo Live)是基于领先的内容接入与分发网络和大规模分布式实时转码技术打造的音视频直播平台,提供便捷接入、高清流畅、低延迟、高并发的音视频直播服务。

  1. 通过采集设备将直播流推送至阿里云直播中心,推送的视频流通过CDN边缘节点进行加速保证上行传输的稳定性。移动端的推流工具可以集成阿里云提供的直播推流SDK进行开发。
  2. 视频流推送至阿里云直播中心后,可按需对视频流进行转码,录制,截图,安全审核等处理。
  3. 处理好的视频流通过CDN内容分发网络下发至观众的设备中进行播放。移动端的播放设备可以集成阿里云提供的播放器SDK进行开发。
  4. 直播视频除了可以进行转码截图等操作外,还可以进行直播转点播的操作,将录制下来的视频转至点播系统中再进行点播播放。
操作流程:

前提条件

  • 如果您要进行国内的直播活动,需提供已备案的域名才可使用直播服务。
  • 如果您的域名还未备案,请先将域名备案

操作步骤

  1. 登录视频云直播控制台

  2. 单击域名管理>添加域名

  3. 配置播流域名,并单击提交

  4. 单击返回域名列表

  5. 单击添加域名

  6. 配置推流域名,并单击提交

    推流域名添加成功。

低延时直播

产品背景

  • “直播+”模式向各垂直领域加速延伸,常见的直播大部分采用RTMP、HLS、FLV协议,技术成熟、兼容性较好、支持大规模并发等优点,但端到端延时最低只能控制在3-6秒,降低了直播的互动体验,也阻碍了直播在一些场景的落地推广。
  • 低延时直播RTS(Real-time Streaming),是在阿里云视频直播(ApsaraVideo Live)的基础上,进行全链路延时监控、CDN传输协议改造成UDP等底层技术优化,通过集成直播播放端SDK的方式,支持千万级并发场景下的毫秒级延时直播能力,保障低延时、低卡顿、秒开流畅的极致直播观看体验。

产品架构

  • 只需要在现有的直播业务新增一个RTS播流域名即可实现多种协议拉流,标准直播拉流(RTMP/FLV/HLS)沿用原有格式,低延时直播拉流(UDP)使用新协议头artc。
  • 主播端推流不用改造,继续沿用RTMP方式推流。

代码实现:

首先里面用到了hutool工具包:baby_chick:国产精品

1
2
3
4
5
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>4.0.12</version>
</dependency>

推拉流工具类:用来生成推流以及加密工作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
/**
* 阿里云直播工具类
*/
public class AliyunLiveUtil {

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 aliyunLiveConfig
* @return
*/
public static String createPushUrl(Integer sourceId, LiveTypeEnum liveTypeEnum, AliyunLiveConfig aliyunLiveConfig) {

// 推流域名
String pushDomain = aliyunLiveConfig.getAliyunLivePushDomain();
// 应用名称
String appName = aliyunLiveConfig.getAliyunLiveAppName();
// 流名称
String streamName = StrUtil.format(aliyunLiveConfig.getAliyunLiveStreamName(), liveTypeEnum.getValue(), sourceId);
// 推流签名key
String pushIdentKey = aliyunLiveConfig.getAliyunLivePushIdentKey();
// 签名url有效时间
Integer identUrlValidTime = aliyunLiveConfig.getAliyunLiveIdentUrlValidTime();

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

// 组合推流域名前缀
// rtmp://{pushDomain}/{appName}/{streamName}
String rtmpUrl = StrUtil.format("rtmp://{}/{}/{}", pushDomain, appName, streamName);
log.debug("推流域名前缀,rtmpUrl=" + rtmpUrl);

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

// md5加密
String md5Str = DigestUtil.md5Hex(md5Url);
log.debug("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.debug("最终鉴权过的推流域名=" + finallyPushUrl);

return finallyPushUrl;
}

/**
* 创建拉流域名,key=rtmpUrl、flvUrl、m3u8Url,代表三种拉流类型域名
*
* @param sourceId
* @param aliyunLiveConfig
* @return
*/
public static Map<String, String> createPullUrl(Integer sourceId, LiveTypeEnum liveTypeEnum, AliyunLiveConfig aliyunLiveConfig) {

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

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

// 组合通用域名
// {pullDomain}/{appName}/{streamName}
String pullUrl = StrUtil.format("{}/{}/{}", pullDomain, appName, streamName);
log.debug("组合通用域名,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.debug("md5加密串,md5Url =" + md5Url + " ------ md5加密结果,md5Str=" + md5Str);
log.debug("md5加密串,md5FlvUrl =" + md5FlvUrl + " ------ md5加密结果,md5FlvStr=" + md5FlvStr);
log.debug("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.debug("最终鉴权过的拉流rtmp域名=" + rtmpUrl);
log.debug("最终鉴权过的拉流flv域名 =" + flvUrl);
log.debug("最终鉴权过的拉流m3u8域名=" + m3u8Url);

HashMap<String, String> urlMap = new HashMap<>();
urlMap.put("rtmpUrl", rtmpUrl);
urlMap.put("flvUrl", flvUrl);
urlMap.put("m3u8Url", m3u8Url);

return urlMap;
}
}

阿里云直播配置参数类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/**
* 阿里云直播配置参数
*/
@Component
@Data
public class AliyunLiveConfig {

/**
* 推流域名
*/
@Value("${aliyun.live.push.domain}")
private String aliyunLivePushDomain;

/**
* 拉流域名
*/
@Value("${aliyun.live.pull.domain}")
private String aliyunLivePullDomain;

/**
* 直播测试appName
*/
@Value("${aliyun.live.appName}")
private String aliyunLiveAppName;

/**
* 直播测试streamName{直播类型}_{类型id}
*/
@Value("${aliyun.live.streamName}")
private String aliyunLiveStreamName;

/**
* 推流鉴权url key
*/
@Value("${aliyun.live.push.ident.key}")
private String aliyunLivePushIdentKey;

/**
* 拉流鉴权url key
*/
@Value("${aliyun.live.pull.ident.key}")
private String aliyunLivePullIdentKey;

/**
* 鉴权url的有效时间(秒),默认30分钟,1800秒 key
*/
@Value("${aliyun.live.ident.url.validTime}")
private Integer aliyunLiveIdentUrlValidTime;

}

直播类型的枚举类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/**
* 直播类型
*/
@ApiModel(description = "直播类型")
public enum LiveTypeEnum {

course("course", "课程");

LiveTypeEnum(String value, String text) {
this.value = value;
this.text = text;
}

private String value;
private String text;

public String getText() {
return text;
}

public void setText(String text) {
this.text = text;
}

public String getValue() {
return value;
}

public void setValue(String value) {
this.value = value;
}

/**
* 根据value获取text
*
* @param value
* @return
*/
public static String getTextByValue(String value) {
LiveTypeEnum[] dictTypeEnums = LiveTypeEnum.values();
for (LiveTypeEnum dictTypeEnum : dictTypeEnums) {
if (dictTypeEnum.getValue().equals(value)) {
return dictTypeEnum.getText();
}

}
return "";
}
}

application.properties配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#阿里云直播配置 begin
#推流域名
aliyun.live.push.domain=www.xxx.xxx.com
#推流鉴权url key
aliyun.live.push.ident.key=xxx
#拉流域名
aliyun.live.pull.domain=www.xxx.xxx.com
#拉流鉴权url key
aliyun.live.pull.ident.key=xxx
#直播测试appName
aliyun.live.appName=ttest
#直播测试streamName{直播类型}{类型id}
aliyun.live.streamName={}{}
#鉴权url的有效时间(秒),默认30分钟,1800秒
aliyun.live.ident.url.validTime=1800
#阿里云直播配置 end

AliyunLiveController api:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@RestController
@RequestMapping("/aliyunLive")
@Slf4j
public class AliyunLiveController {

@Autowired
private AliyunLiveConfig aliyunLiveConfig;

@GetMapping("addLive")
@ResponseBody
public String addLive(Integer courseId) {

/**
* 注意,推流要在播流域名里面生成
*/

System.out.println(aliyunLiveConfig.toString());

String pushUrl = AliyunLiveUtil.createPushUrl(courseId, LiveTypeEnum.course, aliyunLiveConfig);
log.debug("推流域名=" + pushUrl);

Map<String, String> pullUrl = AliyunLiveUtil.createPullUrl(courseId, LiveTypeEnum.course, aliyunLiveConfig);
log.debug("拉流域名=" + pullUrl.toString());


return "hello login";
}

public static void main(String[] args) {
String s = UUID.randomUUID().toString().replaceAll("-", "");
System.out.println(s);
}

}
END:

访问上面的API控制台就会返回推流与拉流的地址,正式上线的时候,可以返回一个封装的类,里面包含推拉流地址.让主播得知推流地址进行直播,用户拉流地址观看即可.如果是使用的阿里云的服务,后端只需要生成推拉流地址就好了,把地址生成好给移动端,其他都是移动端的工作了

@copyright ————baijianruoliorz@Github——————————–

文章作者: liqiqiorz
文章链接: https://yangxiangrui.site/posts/17537/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Blog-YXR
打赏
  • 微信
    微信
  • 支付寶
    支付寶

评论