视频直播服务(ApsaraVideo Live)是基于领先的内容接入、分发网络和大规模分布式实时转码技术打造的音视频直播平台,提供便捷接入、高清流畅、低延迟、高并发的音视频直播服务。本教程指引您使用视频直播服务搭建视频直播,并将直播录制视频保存到阿里云OSS上。

前提条件

在使用本教程之前,请您务必完成以下操作:

  1. 确保您已开通了视频直播服务。请参见开通视频直播服务
  2. 使用Alibaba Cloud SDK for Java,您需要一个阿里云账号和访问密钥(AccessKey)。 请在阿里云控制台中的AccessKey管理页面上创建和查看您的AccessKey。

注意事项

本文有以下两点需要您注意:

阿里云2000元代金券免费领,最新优惠1折抢购,2核4G云服务器仅799元/3年,新老用户同享,立即抢购>>>

  1. 在本文示例代码参数中推流域名拉流(播流)域名AppName请保持全局唯一。
  2. 本文基于Alibaba Cloud SDK for java完成,代码示例中所涉及的maven依赖有aliyun-java-sdk-corealiyun-java-sdk-live
    <dependencies>     <!-- https://mvnrepository.com/artifact/com.aliyun/aliyun-java-sdk-core -->     <dependency&gt;         <groupId>com.aliyun</groupId>         <artifactId>aliyun-java-sdk-core</artifactId>         <version>4.4.3</version>     </dependency>     <!-- https://mvnrepository.com/artifact/com.aliyun/aliyun-java-sdk-live -->     <dependency>         <groupId>com.aliyun</groupId>         <artifactId>aliyun-java-sdk-live</artifactId>         <version>3.8.0</version>     </dependency> </dependencies>

步骤一:添加视频直播域名

添加视频直播域名,您需要调用AddLiveDomain接口添加一个推流域名(业务类型为liveEdge)和一个播放域名(业务类型为liveVideo),然后再调用AddLiveDomainMapping接口将已创建的推流域名和播放域名关联。

完整代码示例如下:

import com.aliyuncs.DefaultAcsClient; import com.aliyuncs.IAcsClient; import com.aliyuncs.exceptions.ClientException; import com.aliyuncs.exceptions.ServerException; import com.aliyuncs.live.model.v20161101.*; import com.aliyuncs.profile.DefaultProfile; import com.google.gson.Gson;  public class TestStartLive {      // 推流域名     private static String liveEdge = "www.yourLiveEdge.club";     // 播流域名     private static String liveVideo = "www.yourLiveVideo.club";      /**      * Initialization  初始化公共请求参数      */     public IAcsClient initialization() {         // 初始化请求参数         DefaultProfile profile = DefaultProfile.getProfile(                 "<your-region-id>", // 您的可用区ID                 "<your-access-key-id>", // 您的AccessKey ID                 "<your-access-key-secret>"); // 您的AccessKey Secret         return new DefaultAcsClient(profile);     }      /**      * 添加直播域名 AddLiveDomain      *      * @param client         公共请求参数      * @param domainName     需要接入直播的域名。支持泛域名,以符号“.”开头,如:.a.com。      * @param liveDomainType 域名业务类型。取值:liveVideo:播流域名  liveEdge:边缘推流域名      * @param scope          加速区域。国际用户、国内L3及以上用户设置有效。      */     public AddLiveDomainResponse addLiveDomain(IAcsClient client, String domainName, String liveDomainType, String scope, String region) throws ClientException {         AddLiveDomainRequest request = new AddLiveDomainRequest();         System.out.println("--------------------addLiveDomain--------------------");         request.setDomainName(domainName);         request.setRegion(region);         request.setLiveDomainType(liveDomainType);         request.setScope(scope);         return client.getAcsResponse(request);     }      /**      * AddLiveDomainMapping  配置推流和拉流的映射关系      *      * @param client     公共请求参数      * @param pullDomain 播流域名,域名类型为liveVideo。      * @param pushDomain 推流域名,域名类型为liveEdge。      */     public AddLiveDomainMappingResponse addLiveDomainMapping(IAcsClient client, String pullDomain, String pushDomain) throws ClientException {         AddLiveDomainMappingRequest request = new AddLiveDomainMappingRequest();         System.out.println("--------------------addLiveDomainMapping--------------------");         request.setPullDomain(pullDomain);         request.setPushDomain(pushDomain);         return client.getAcsResponse(request);     }      /**      * DescribeLiveUserDomains 查询用户名下所有的直播域名      *      * @param client 公共请求参数      */     public DescribeLiveUserDomainsResponse describeLiveUserDomains(IAcsClient client) throws ClientException {         DescribeLiveUserDomainsRequest request = new DescribeLiveUserDomainsRequest();         System.out.println("--------------------describeLiveUserDomains--------------------");         // request.setDomainName(domainName);         return client.getAcsResponse(request);     }      public static void main(String[] args) {         TestStartLive live = new TestStartLive();         Gson gson = new Gson();         IAcsClient client = live.initialization();         try {             // 添加推流域名。             AddLiveDomainResponse liveEdgeResponse = live.addLiveDomain(client, liveEdge, "liveEdge", "domestic","cn-shanghai");             System.out.println(gson.toJson(liveEdgeResponse));             // 添加播流域名。             AddLiveDomainResponse liveVideoResponse = live.addLiveDomain(client, liveVideo, "liveVideo", "domestic","cn-shanghai");             System.out.println(gson.toJson(liveVideoResponse));             // 查询直播域名列表。             // 判断确认域名是否添加成功,且状态为online。             while (true) {                 DescribeLiveUserDomainsResponse userDomains = live.describeLiveUserDomains(client);                 String liveEdgeStatus = null;                 String liveVideoStatus = null;                 for (DescribeLiveUserDomainsResponse.PageData pageData : userDomains.getDomains()) {                     String domainName = pageData.getDomainName();                     // 提取推流域名状态                     if (domainName.equals(liveEdge)) {                         liveEdgeStatus = pageData.getLiveDomainStatus();                     }                     // 提取拉流域名状态                     if (domainName.equals(liveVideo)) {                         liveVideoStatus = pageData.getLiveDomainStatus();                     }                 }                 // 判断确认域名是否添加成功,且状态为online。                 if (liveEdgeStatus.equals("online") && liveVideoStatus.equals("online")) {                     System.out.println("直播配置域名!");                     live.addLiveDomainMapping(client, liveVideo, liveEdge);                     System.out.println("域名关联成功!");                     break;                 }else {                     System.out.println("域名配置中,请稍后...");                     Thread.sleep(5000);                 }             }         } catch (InterruptedException e) {             e.printStackTrace();         } catch (ServerException e) {             e.printStackTrace();         } catch (ClientException e) {             System.out.println("ErrCode:" + e.getErrCode());             System.out.println("ErrMsg:" + e.getErrMsg());             System.out.println("RequestId:" + e.getRequestId());         }     } }

步骤二:配置直播录制

您可以通过调用AddLiveAppRecordConfig接口添加App录制配置,将录制的视频保存到阿里云OSS上。在配置视频录制时,您可以通过配置OnDemand参数选择录制方式,本教程中以手动控制录制为例。

OnDemand参数值说明如下:

  • 0:表示关闭。
  • 1:表示通过HTTP回调方式。
  • 7:表示默认不录制,可以通过RealTimeRecordCommand接口手动控制录制启停。
    说明 手动启动录制的直播流一旦发生了断流,就会停止录制。重新推流后,如果没有配置自动录制,将不会自动启动录制。

完整代码示例如下:

import com.aliyuncs.DefaultAcsClient; import com.aliyuncs.IAcsClient; import com.aliyuncs.exceptions.ClientException; import com.aliyuncs.exceptions.ServerException; import com.aliyuncs.live.model.v20161101.AddLiveAppRecordConfigRequest; import com.aliyuncs.live.model.v20161101.AddLiveAppRecordConfigResponse; import com.aliyuncs.live.model.v20161101.RealTimeRecordCommandRequest; import com.aliyuncs.live.model.v20161101.RealTimeRecordCommandResponse; import com.aliyuncs.profile.DefaultProfile; import com.google.gson.Gson; import java.util.ArrayList; import java.util.List;  /**  * 1.配置直播录制内容保存到OSS中  * 2.开启(关闭)直播录制  *  说明:本示例设置的录制规则为默认不开启录制,而是通过手动调用RealTimeRecordCommand接口来控制录制的启动和停止  */ public class AddLiveAppRecordConfig {      private static final Gson gson = new Gson();     // 推流域名。     private static String streamName = "www.yourLiveEdge.club";     // 直播流所属应用名称。支持通配符(*),代表该域名下所有的AppName。     private static String App = "testApp";     // 加速域名,指播放域名。     private static String domainName = "www.yourDomainName.com";     // OssBucket名称。     private static String ossBucket = "oss-***-****-****";     // OssEndpoint域名。     private static String ossEndpoint = "oss-*****.aliyuncs.com";     // 按需录制。     // 0表示关闭。     // 1表示通过HTTP回调方式。     // 7表示默认不录制,通过RealTimeRecordCommand接口手动控制录制启停。     private static Integer onDemand = 7;      /**      * Initialization  初始化公共请求参数      */     public IAcsClient initialization() {         // 初始化请求参数         DefaultProfile profile = DefaultProfile.getProfile(                 "<your-region-id>", // 您的可用区ID                 "<your-access-key-id>", // 您的AccessKey ID                 "<your-access-key-secret>"); // 您的AccessKey Secret         return new DefaultAcsClient(profile);     }      /**      * 配置APP录制,输出内容保存到OSS中      *      * @param client     公共请求参数      * @param appName    直播流所属应用名称      * @param domainName 加速域名,指播放域名      * @param ossBucket  OssBucket名称      * @param endpoint   OssEndpoint域名      * @param onDemand   按需录制 0表示关闭。1表示通过HTTP回调方式。7表示默认不录制。      * @param streamName 流名称      */     public void addLiveAppRecordConfig(IAcsClient client, String appName, String domainName, String ossBucket, String endpoint, Integer onDemand, String streamName, List recordFormatList) throws ClientException {         AddLiveAppRecordConfigRequest request = new AddLiveAppRecordConfigRequest();         System.out.println("--------------------addLiveAppRecordConfig--------------------");         request.setAppName(appName);         request.setDomainName(domainName);         request.setOssBucket(ossBucket);         request.setOssEndpoint(endpoint);         request.setOnDemand(onDemand);         request.setStreamName(streamName);         request.setRecordFormats(recordFormatList);         AddLiveAppRecordConfigResponse response = client.getAcsResponse(request);         System.out.println(gson.toJson(response));     }      /**      * 按需完成手动录制。例如,动态地启动、停止录制      *      * @param client     公共请求参数      * @param appName    App名      * @param command    操作行为。支持start、stop两种类型      * @param domainName 您的加速域名      * @param streamName 直播流名      */     public void realTimeRecordCommand(IAcsClient client, String appName, String command, String domainName, String streamName) throws ClientException {         RealTimeRecordCommandRequest request = new RealTimeRecordCommandRequest();         System.out.println("--------------------addLiveAppRecordConfig--------------------");         request.setAppName(appName);         request.setDomainName(domainName);         request.setStreamName(streamName);         request.setCommand(command);         RealTimeRecordCommandResponse response = client.getAcsResponse(request);         System.out.println(gson.toJson(response));     }       /**      * 组装RecordFormat参数      *      * @param cycleDuration        周期录制时长。单位:秒。不填则默认为6小时。      * @param format               格式。目前支持m3u8、flv或mp4。      * @param ossObjectPrefix      OSS存储的录制文件名,小于256 byte,支持变量匹配      *                             包含 {AppName}、{StreamName}、{Sequence}、{StartTime}、{EndTime}、{EscapedStartTime}、{EscapedEndTime}      *                             参数值必须要有{StartTime}或{EscapedStartTime}和{EndTime}或{EscapedEndTime}变量。默认支持1小时周期录制,最小      *                             周期时间15分钟,最多6小时。      * @param sliceOssObjectPrefix 当format格式是m3u8录制,则需要配置,表示ts切片名称。默认30秒一片,小于256byte,支持变量匹配      *                             包含{AppName}、{StreamName}、{UnixTimestamp}、{Sequence}      */     public List<AddLiveAppRecordConfigRequest.RecordFormat> recordFormat(Integer cycleDuration, String format, String ossObjectPrefix, String sliceOssObjectPrefix) {         List<AddLiveAppRecordConfigRequest.RecordFormat> recordFormatList = new ArrayList<AddLiveAppRecordConfigRequest.RecordFormat>();          AddLiveAppRecordConfigRequest.RecordFormat recordFormat1 = new AddLiveAppRecordConfigRequest.RecordFormat();         recordFormat1.setFormat(format);         recordFormat1.setOssObjectPrefix(ossObjectPrefix);         recordFormat1.setCycleDuration(cycleDuration);         recordFormat1.setSliceOssObjectPrefix(sliceOssObjectPrefix);         recordFormatList.add(recordFormat1);         return recordFormatList;     }      public static void main(String[] args) {         AddLiveAppRecordConfig liveAppRecordConfig = new AddLiveAppRecordConfig();         IAcsClient client = liveAppRecordConfig.initialization();         List<AddLiveAppRecordConfigRequest.RecordFormat> recordFormats = liveAppRecordConfig.recordFormat(                 1,                 "m3u8",                 "record/{AppName}/{StreamName}/{Sequence}{EscapedStartTime}{EscapedEndTime}",                 "record/{AppName}/{StreamName}/{UnixTimestamp}_{Sequence}");         try {             liveAppRecordConfig.addLiveAppRecordConfig(client, App, domainName, ossBucket, ossEndpoint, 7, streamName, recordFormats);             liveAppRecordConfig.realTimeRecordCommand(client, App, "start", domainName, streamName);         } catch (ServerException e) {             e.printStackTrace();         } catch (ClientException e) {             System.out.println("ErrCode:" + e.getErrCode());             System.out.println("ErrMsg:" + e.getErrMsg());             System.out.println("RequestId:" + e.getRequestId());         }     } }

步骤三:配置直播鉴权

阿里云视频直播默认开启URL鉴权功能,防止直播被盗录、盗播。您可以使用默认的鉴权规则,也可以使用自定义鉴权规则。本教程基于默认鉴权规则指导您如何进行直播鉴权。

  1. 拼接推流地址。
    直播只支持RTMP格式推流。流地址格式为RTMP://推流域名/AppName/StreamName?鉴权串。
    例如,推流域名是push.aliyunlive.com,AppName为testApp,StreamName为testStream,鉴权key是123,则推流地址为RTMP://push.aliyunlive.com/app/stream?auth_key=timestamp-rand-uid-md5hash

    说明 计算md5的鉴权值规则是:/appname/streamname-unix时间戳-0-0-鉴权KEY -> md5sum 得到鉴权值 。

    例如:/testApp/testStream-1568979058-0-0-u61XUjAZiM -> md5后等于 ed8e6df060d103b6cbfb10359a2cf089

  2. 拼接播流地址。
    播流地址支持RMTP、FLV、HLS格式。格式如下所示:

    • RTMP:rtmp://播流域名/AppName/StreamName?鉴权串
    • FLV:http://播流域名/AppName/StreamName.flv?鉴权串
    • HLS:http://播流域名/AppName/StreamName.m3u8?鉴权串
      说明 M3u8转码地址已支持。如果您有需要,请您 提交工单 申请。

    例如:播流域名是play.aliyunlive.com,AppName为app,StreamName为stream,鉴权key是 456,则播流地址为:

    • RTMP:rtmp://play.aliyunlive.com/app/stream?auth_key=timestamp-rand-uid-md5hash
    • FLV:http://play.aliyunlive.com/app/stream.flv?auth_key=timestamp-rand-uid-md5hash
    • HLS:/http://play.aliyunlive.com/app/stream.m3u8?auth_key=timestamp-rand-uid-md5hash

完整代码示例如下:

import cn.hutool.core.util.StrUtil; import cn.hutool.crypto.digest.DigestUtil; import java.util.HashMap; import java.util.Map;  /**  * 推拉流地址示例:  * rtmp://www.ttest.liveTest.com/a/a?auth_key=1558065152-0-0-*****  * 播流地址  * 原画  * rtmp://www.btest.liveTest.com/a/a?auth_key=1558065152-0-0-*****  * http://www.btest.liveTest.com/a/a.flv?auth_key=1558065152-0-0-*****  * http://www.btest.liveTest.com/a/a.m3u8?auth_key=1558065152-0-0-*****  *   * hutool工具包地址,请参见https://hutool.cn/docs/#/  */ public class AliyunLiveUtil {      // 鉴权url的有效时间(秒),默认30分钟,1800秒     private static Integer identUrlValidTime = 1800;     // 直播测试appName     private static String appName = "testApp";     // 直播测试streamName     private static String streamName = "testStranm";     // 推流域名     private static String pushDomain = "www.yourLiveEdge.club";     // 推流鉴权url     private static String pushIdentKey = "******";     // 拉流域名     private static String pullDomain = "www.yourLiveVideo.club";     // 拉流鉴权url     private static String pullIdentKey = "******";      /**      * 根据源id创建该id的推流url      *      * @param identUrlValidTime 鉴权url的有效时间(秒),默认30分钟,1800秒      * @param pushDomain        推流域名      * @param appName           直播测试appName      * @param streamName        直播测试streamName      * @param pushIdentKey      推流鉴权url key      * @return      */     public String createPushUrl(Integer identUrlValidTime, String pushDomain, String appName, String streamName, String pushIdentKey) {          // 计算过期时间         String timestamp = String.valueOf((System.currentTimeMillis() / 1000) + identUrlValidTime);          // 组合推流域名前缀         // rtmp://{pushDomain}/{appName}/{streamName}         String rtmpUrl = StrUtil.format("rtmp://{}/{}/{}", pushDomain, appName, streamName);         System.out.println("推流域名前缀,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);         System.out.println("md5加密串,md5Url=" + md5Url + "------md5加密结果,md5Str=" + md5Str);          // 组合最终鉴权过的推流域名         // {rtmpUrl}?auth_key={timestamp}-0-0-{md5Str}         String finallyPushUrl = StrUtil.format("{}?auth_key={}-0-0-{}", rtmpUrl, timestamp, md5Str);         System.out.println("最终鉴权过的推流域名=" + finallyPushUrl);          return finallyPushUrl;     }      /**      * 创建拉流域名,key=rtmpUrl、flvUrl、m3u8Url,代表三种拉流类型域名      *      * @param pullDomain        拉流域名      * @param appName           应用名称      * @param streamName        流名称      * @param pullIdentKey      拉流鉴权url key      * @param identUrlValidTime 鉴权url的有效时间(秒),默认30分钟,1800秒      * @return      */     public Map<String, String> createPullUrl(String pullDomain, String appName, String streamName, String pullIdentKey, Integer identUrlValidTime) {          // 计算过期时间         String timestamp = String.valueOf((System.currentTimeMillis() / 1000) + identUrlValidTime);          // 组合通用域名         // {pullDomain}/{appName}/{streamName}         String pullUrl = StrUtil.format("{}/{}/{}", pullDomain, appName, streamName);         System.out.println("组合通用域名,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);         System.out.println("md5加密串,md5Url    =" + md5Url + "       ------     md5加密结果,md5Str=" + md5Str);         System.out.println("md5加密串,md5FlvUrl =" + md5FlvUrl + "    ------    md5加密结果,md5FlvStr=" + md5FlvStr);         System.out.println("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);          System.out.println("最终鉴权过的拉流rtmp域名=" + rtmpUrl);         System.out.println("最终鉴权过的拉流flv域名 =" + flvUrl);         System.out.println("最终鉴权过的拉流m3u8域名=" + m3u8Url);          HashMap<String, String> urlMap = new HashMap<String, String>();         urlMap.put("rtmpUrl", rtmpUrl);         urlMap.put("flvUrl", flvUrl);         urlMap.put("m3u8Url", m3u8Url);          return urlMap;     }      public static void main(String[] args) {         AliyunLiveUtil aliyunLiveUtil = new AliyunLiveUtil();         aliyunLiveUtil.createPushUrl(identUrlValidTime, pushDomain, appName, streamName, pushIdentKey);         aliyunLiveUtil.createPullUrl(pullDomain, appName, streamName, pullIdentKey, identUrlValidTime);     } }            

步骤四:开始直播

在完成直播鉴权之后,您就可以使用鉴权后的推流地址进行直播推流了。

  1. 下载并安装OBS推流工具。

    关于OBS播放器的使用,请参见OBS推流工具

  2. 推流配置中输入服务器地址和串流密匙。
    • 服务器:填写包含AppName前的地址。
    • 串流密匙:填写包含StreamName后的地址。
    例如,推流地址为rtmp://push.aliyunlive.com/testApp/testStream?auth_key=1543302081-0-0-9c6e7c8190c10bdfb3c0************,则服务器地址为rtmp://push.aliyunlive.com/testApp/串流密匙testStream?auth_key=1543302081-0-0-9c6e7c8190c10bdfb3c0************搭建直播_最佳实践_视频直播_调用示例_Java SDK

    说明 以上推流地址示例由推流域名、AppName、StreamName和鉴权串组成,您需要根据实际情况,替换成您自己的AppName、StreamName和相应的鉴权串。

  3. 完成以下操作进行播流:
    1. 下载并安装VLC播放器。

      关于VLC播放器的使用,请参见VLC播放器

    2. 打开VLC播放器,然后选择媒体 > 打开网络串流(N)
      搭建直播_最佳实践_视频直播_调用示例_Java SDK
    3. 请输入网络URL 中,输入播流地址并单击播放
      搭建直播_最佳实践_视频直播_调用示例_Java SDK