最近一段时间比较忙,没更新。今天项目发版,把这一期做的项目做个总结。今天讲一下直播服务吧。
目前除了自己开发,市面上的提供直播服务的主要有阿里云,乐视云,还有展视互动。经过前期的综合考虑,还有成本原因,决定采用购买阿里云的直播录播业务。(展示互动好像是目前做的比较好的,但是收费稍贵。)
因为前期的业务并不是很复杂,将阿里云直播点播的文档看了一遍,很多也很全,但是总感觉有些杂乱。试了下demo,很简单,最后自己动手还是发现了一些坑。
由于我们的业务不是很复杂,主要还是调阿里云的回调和SDK(SDK是封装了的API),其实后来SDK我们也很少用到,用到的地方是获取录播视频时长。其实SDK是很丰富的,我们业务比较简单,所以用到的少。
说下工时吧,差不多两星期,一个星期熟悉文档,一个星期开发完成,后期BUG修复。
现在详细说下主要流程:其实就是一个推流,一个播流的过程。这其中有三个回调:
中心推流回调URL:http://XXX:8082/videoLiveCallBack/pushCallBack
录制回调 URL: http://XXX:8082/videoLiveCallBack/recordCallBack
视频点播回调URL:http://XXX:8082/videoLiveCallBack/playCallBack
中心推流回调阿里会监听推流状态,然后返回状态。
录制回调返回录制状态
视频点播回调返回点播的相关状态。(例如转码状态)
直接上代码吧:(参考了前辈网上文章,非常感谢同行的无私分享)
第一步: 是后天新增直播,生成推流地址:
/**
* addVideoLive:新增直播
*
* @param dto
* @return
* @throws Exception
* @exception
* @author Ken
* @date 2018年5月18日 下午3:36:19
*/
@RequestMapping(value = "/add")
@ResponseBody
public Json add(VideoLiveDto videoLiveDto) throws Exception {
Json j = new Json();
// 拼接直播推流地址
// Example:
// 推流地址(鉴权后的)
// rtmp://video-center.alivecdn.com/AppName/StreamName?vhost=live.i-yuwen.com&auth_key=1529051804-0-0-3339f3cb647bad0fc60882d058f9b5d9
// 播放地址:
// rtmp://live.i-yuwen.com/AppName/StreamName?auth_key=1529054783-0-0-da50433e403d8b5856143af8bb5ea1f9
// http://live.i-yuwen.com/AppName/StreamName.flv?auth_key=1529054783-0-0-899cbdd4f759860b46eb621555692847
// http://live.i-yuwen.com/AppName/StreamName.m3u8?auth_key=1529054783-0-0-f4a347b2e04d15f006d66be488725487
String videoLiveId = UUIDGenerator.getUUID32Bit();
videoLiveDto.setId(videoLiveId);
// 传入自定义参数,即传入应用名称和流名称
String appName = GlobalUtils.getConfig(DataStatus.LIVE_APP_NAME);
String streamName = "";
if (videoLiveDto != null && StringUtils.isNotEmpty(videoLiveDto.getTeacherId())) {
streamName = GlobalUtils.getConfig(DataStatus.LIVE_STREAM_NAME) + DataStatus.UNDERLINE + DataStatus.STUDENT + DataStatus.UNDERLINE + videoLiveId;
}
String vhost = GlobalUtils.getConfig(DataStatus.LIVE_VHOST_NAME);
// 时间戳,有效时间
// long time = System.currentTimeMillis() + 1800
// long time = (System.currentTimeMillis() + 30 * 60 * 1000)/1000;
//默认开播时间往后30分钟
long time = (videoLiveDto.getVideoStartTime().getTime() + 30 * 60 * 1000) / 1000;
// 加密key,即直播后台鉴权里面自行设置
String key = GlobalUtils.getConfig(DataStatus.LIVE_KEY_NAME);
String strPush = DataStatus.BACKSLASH + appName + DataStatus.BACKSLASH + streamName + DataStatus.IN_THE_LINE + time + DataStatus.SPLICING_OPERATOR + key;
// 拼接推流地址
// String pushAddr = "rtmp://video-center.alivecdn.com" + DataStatus.BACKSLASH + appName
// + DataStatus.BACKSLASH + streamName + "?vhost=" + vhost;
String pushAddr = "rtmp://video-center.alivecdn.com/" + appName + DataStatus.BACKSLASH + streamName + "?vhost=" + vhost
+ "&auth_key=" + time + DataStatus.SPLICING_OPERATOR + MD5.md5(strPush);
// 拼接播放地址
// String playAddr = "rtmp://" + vhost + DataStatus.BACKSLASH + appName + DataStatus.BACKSLASH +
// streamName;
// String flvAddr = "rtmp://" + vhost + DataStatus.BACKSLASH + appName + DataStatus.BACKSLASH + streamName
// + ".flv";
// String m3u8Addr = "rtmp://" + vhost + DataStatus.BACKSLASH + appName + DataStatus.BACKSLASH +
// streamName + ".m3u8";
// 原画播放地址:
String playMd5 = DataStatus.BACKSLASH + appName + DataStatus.BACKSLASH + streamName + DataStatus.IN_THE_LINE + time + DataStatus.SPLICING_OPERATOR + key;
String flvMd5 = DataStatus.BACKSLASH + appName + DataStatus.BACKSLASH + streamName + ".flv-" + time + DataStatus.SPLICING_OPERATOR + key;
String m3u8Md5 = DataStatus.BACKSLASH + appName + DataStatus.BACKSLASH + streamName + ".m3u8-" + time + DataStatus.SPLICING_OPERATOR + key;
String playAddr = "rtmp://" + vhost + DataStatus.BACKSLASH + appName + DataStatus.BACKSLASH + streamName + "?auth_key=" + time + DataStatus.SPLICING_OPERATOR
+ MD5.md5(playMd5);
String flvAddr = "http://" + vhost + DataStatus.BACKSLASH + appName + DataStatus.BACKSLASH + streamName + ".flv?auth_key=" + time + DataStatus.SPLICING_OPERATOR
+ MD5.md5(flvMd5);
String m3u8Addr = "http://" + vhost + DataStatus.BACKSLASH + appName + DataStatus.BACKSLASH + streamName + ".m3u8?auth_key=" + time + DataStatus.SPLICING_OPERATOR
+ MD5.md5(m3u8Md5);
logger.info("推流地址pushAddr==>" + pushAddr);
logger.info("原画播放地址playAddr==>" + playAddr);
logger.info("原画播放地址flvAddr==>" + flvAddr);
logger.info("原画播放地址m3u8Addr==>" + m3u8Addr);
// 流畅(LLD):RTMP 格式;FLV 格式
String playLldRtmpMd5 = DataStatus.BACKSLASH + appName + DataStatus.BACKSLASH + streamName + "_lld-" + time + DataStatus.SPLICING_OPERATOR + key;
String playLldFlvMd5 = DataStatus.BACKSLASH + appName + DataStatus.BACKSLASH + streamName + "_lld.flv-" + time + DataStatus.SPLICING_OPERATOR + key;
String playLldRtmp = "rtmp://" + vhost + DataStatus.BACKSLASH + appName + DataStatus.BACKSLASH + streamName + "_lld?auth_key=" + time + DataStatus.SPLICING_OPERATOR
+ MD5.md5(playLldRtmpMd5);
String playLldFlv = "http://" + vhost + DataStatus.BACKSLASH + appName + DataStatus.BACKSLASH + streamName + "_lld.flv?auth_key=" + time
+ DataStatus.SPLICING_OPERATOR + MD5.md5(playLldFlvMd5);
System.out.println("流畅播放地址playLldRtmp==" + playLldRtmp);
System.out.println("流畅播放地址playLldFlv==" + playLldFlv);
logger.info("流畅播放地址playLldRtmp==>" + playLldRtmp);
logger.info("流畅播放地址playLldFlv==>" + playLldFlv);
// 标清(LSD):RTMP 格式;FLV 格式
String playLsdRtmpMd5 = DataStatus.BACKSLASH + appName + DataStatus.BACKSLASH + streamName + "_lsd-" + time + DataStatus.SPLICING_OPERATOR + key;
String playLsdFlvMd5 = DataStatus.BACKSLASH + appName + DataStatus.BACKSLASH + streamName + "_lsd.flv-" + time + DataStatus.SPLICING_OPERATOR + key;
String playLsdRtmp = "rtmp://" + vhost + DataStatus.BACKSLASH + appName + DataStatus.BACKSLASH + streamName + "_lsd?auth_key=" + time + DataStatus.SPLICING_OPERATOR
+ MD5.md5(playLsdRtmpMd5);
String playLsdFlv = "http://" + vhost + DataStatus.BACKSLASH + appName + DataStatus.BACKSLASH + streamName + "_lsd.flv?auth_key=" + time
+ DataStatus.SPLICING_OPERATOR + MD5.md5(playLsdFlvMd5);
// 高清(LHD):RTMP 格式;FLV 格式
String playLhdRtmpMd5 = DataStatus.BACKSLASH + appName + DataStatus.BACKSLASH + streamName + "_lhd-" + time + DataStatus.SPLICING_OPERATOR + key;
String playLhdFlvMd5 = DataStatus.BACKSLASH + appName + DataStatus.BACKSLASH + streamName + "_lhd.flv-" + time + DataStatus.SPLICING_OPERATOR + key;
String playLhdRtmp = "rtmp://" + vhost + DataStatus.BACKSLASH + appName + DataStatus.BACKSLASH + streamName + "_lhd?auth_key=" + time + DataStatus.SPLICING_OPERATOR
+ MD5.md5(playLhdRtmpMd5);
String playLhdFlv = "http://" + vhost + DataStatus.BACKSLASH + appName + DataStatus.BACKSLASH + streamName + "_lhd.flv?auth_key=" + time
+ DataStatus.SPLICING_OPERATOR + MD5.md5(playLhdFlvMd5);
// 超清(LUD):RTMP 格式;FLV 格式
String playLudRtmpMd5 = DataStatus.BACKSLASH + appName + DataStatus.BACKSLASH + streamName + "_lud-" + time + DataStatus.SPLICING_OPERATOR + key;
String playLudFlvMd5 = DataStatus.BACKSLASH + appName + DataStatus.BACKSLASH + streamName + "_lud.flv-" + time + DataStatus.SPLICING_OPERATOR + key;
String playLudRtmp = "rtmp://" + vhost + DataStatus.BACKSLASH + appName + DataStatus.BACKSLASH + streamName + "_lud?auth_key=" + time + DataStatus.SPLICING_OPERATOR
+ MD5.md5(playLudRtmpMd5);
String playLudFlv = "http://" + vhost + DataStatus.BACKSLASH + appName + DataStatus.BACKSLASH + streamName + "_lud.flv?auth_key=" + time
+ DataStatus.SPLICING_OPERATOR + MD5.md5(playLudFlvMd5);
// 保存播放地址到数据库
videoLiveDto.setPushAddr(pushAddr);
videoLiveDto.setPlayAddr(playAddr);
videoLiveDto.setFlvAddr(flvAddr);
videoLiveDto.setM3u8Addr(m3u8Addr);
videoLiveDto.setPlayLldRtmp(playLldRtmp);
videoLiveDto.setPlayLldFlv(playLldFlv);
videoLiveDto.setPlayLsdRtmp(playLsdRtmp);
videoLiveDto.setPlayLsdFlv(playLsdFlv);
videoLiveDto.setPlayLhdRtmp(playLhdRtmp);
videoLiveDto.setPlayLhdFlv(playLhdFlv);
videoLiveDto.setPlayLudRtmp(playLudRtmp);
videoLiveDto.setPlayLudFlv(playLudFlv);
// 默认未开播
videoLiveDto.setLiveStatus(DataStatus.INT_ONE);
// 直播状态:1:未开播,2:直播中.3:直播结束 要求排序为2:直播中 (对应排序:1) => 1:未开播 (对应排序:2) =>
// 3:直播结束 (对应排序:3)
videoLiveDto.setOrderNo(DataStatus.INT_TWO);
// 预约人数
Integer virtualAppointmentNum = videoLiveDto.getVirtualAppointmentNum();
if (videoLiveDto != null && virtualAppointmentNum == null) {
Integer appointmentMin = DataStatus.APPOINTMENT_MIN;
Integer appointmentMax = DataStatus.APPOINTMENT_MAX;
Random r = new Random();
virtualAppointmentNum = r.nextInt(appointmentMax - appointmentMin + DataStatus.INT_ONE) + appointmentMin;
videoLiveDto.setVirtualAppointmentNum(virtualAppointmentNum);
}
videoLiveDto.setAppointmentNum(virtualAppointmentNum);
Integer flag = videoLiveService.addVideoLive(videoLiveDto);
if (flag > 0) {
// 若isToAll=0,则该授课老师的学员能看到,其他学员不能(班级暂不限定,按理应对应到班级),默认将该老师下学员加到预约,暂时不能取消预约
if (videoLiveDto != null && videoLiveDto.getIsToAll() != null) {
// 班级ID
Integer cid = videoLiveDto.getCid();
ClassStudentDto classStudentDto = new ClassStudentDto();
classStudentDto.setCid(cid);
List<ClassStudentDto> classStudentList = classStudentService
.findClassStudentListByParam(classStudentDto);
if (CollectionUtils.isNotEmpty(classStudentList)) {
// 加入预约
VideoLiveReservationDto videoLiveReservationDto = new VideoLiveReservationDto();
for (ClassStudentDto cs : classStudentList) {
Integer sid = cs.getSid();
if (sid != 0) {
String studentId = studentService.findStudentIdBySid(sid);
if (StringUtils.isNotEmpty(studentId)) {
videoLiveReservationDto.setStudentId(studentId);
}
}
// 预约类型(线上:1;线下:2)
videoLiveReservationDto.setReservationType(DataStatus.INT_TWO);
videoLiveReservationDto.setSid(sid);
videoLiveReservationDto.setVideoLiveId(videoLiveId);
videoLiveReservationDto.setVideoLiveName(videoLiveDto.getTitle());
videoLiveReservationService.addVideoLiveReservation(videoLiveReservationDto);
}
}
}
j.setSuccess(true);
j.setMsg("添加成功");
j.setObj(videoLiveDto);
} else {
j.setSuccess(false);
j.setMsg("添加失败");
}
return j;
}
第二步: app 的三个回调,由于我们业务是教师端和学生端都要开直播。所以这里用到了策略模式(考虑到阿里云回调只能配一个IP)
package com.ygw.app.controller.v2.aliyun;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.BooleanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ygw.app.config.version.ApiVersion;
import com.ygw.app.controller.common.BaseController;
import com.ygw.app.service.IVideoLiveReplayService;
import com.ygw.common.app.dto.VideoLiveReplayDto;
import com.ygw.common.teacher.dto.LiveCourseReplayDto;
import com.ygw.thirdparty.dto.aliyun.CallBackDto;
import com.ygw.thirdparty.service.impl.aliyun.LiveCallBackContext;
import com.ygw.thirdparty.service.teacher.ILiveCourseReplayService;
import com.ygw.utils.AliUtil;
import com.ygw.utils.StringUtils;
import com.ygw.utils.constants.Consts;
import com.ygw.utils.constants.DataStatus;
import com.ygw.utils.constants.VideoCallBackType;
import com.ygw.utils.token.Luna;
/**
*
* <p>Title: VideoLiveCallBackController </p>
* <p>Description: 视频直播点播回调接口控制器(策略模式)</p>
* <p>Copyright (c) 2018 </p>
* <p>Company: 上海阳光喔教育科技有限公司</p>
* @author lb
* @date 2018年7月19日 下午2:52:44
* @version 1.0
* <p>修改人:Administrator</p>
* <p>修改时间:2018年7月19日 下午2:52:44</p>
* <p>修改备注:</p>
*/
@ApiVersion(1.2)
@RestController("VideoLiveBackController-v1.2")
@RequestMapping(value = "/v1.2/videoLiveBack")
public class VideoLiveBackController extends BaseController {
@Autowired
private LiveCallBackContext callBackContext;
@Autowired
private IVideoLiveReplayService videoLiveReplayService;
@Autowired
private ILiveCourseReplayService liveCourseReplayService;
@RequestMapping(value = "/pushBack")
@Luna
public void pushCallBack(CallBackDto callBackDto, HttpServletRequest request, HttpServletResponse response)
throws Exception {
logger.info("=======================access pushBack=======================");
logger.info("pushBack入参: " + callBackDto);
String callBackType = callBackDto.getId().split("_")[1];
String videoLiveId = callBackDto.getId().split("_")[2];
callBackDto.setVideoLiveId(videoLiveId);
switch (callBackType) {
case Consts.STUDENT_CALL_BACK:// 学生端回调
callBackContext.contextPushCallBack(VideoCallBackType.STUDENT.getValue(), callBackDto);
break;
case Consts.TEACHER_CALL_BACK:// 老师端回调
callBackContext.contextPushCallBack(VideoCallBackType.TEACHER.getValue(), callBackDto);
break;
}
logger.info("=======================out pushBack=======================");
AliUtil.response(request, response, DataStatus.ALIYUN_STATUS_OK, HttpServletResponse.SC_OK);
}
@RequestMapping(value = "/recordBack")
@Luna
public void recordCallBack(HttpServletRequest request, HttpServletResponse response) throws Exception {
logger.info("=======================access recordBack=======================");
// 解析阿里回调服务器的接口数据
String result = AliUtil.GetPostBody(request.getInputStream(),
Integer.parseInt(request.getHeader("content-length")));
JSONObject jsonObject = JSON.parseObject(result);
logger.info("jsonObject: " + jsonObject);
Boolean isFinished = jsonObject.getBoolean("is_finished");
String stream = jsonObject.getString("stream");
if (BooleanUtils.isNotTrue(isFinished) || StringUtils.isEmpty(stream)) {// 没有完成
AliUtil.response(request, response, DataStatus.ALIYUN_STATUS_NOT_OK, HttpServletResponse.SC_BAD_REQUEST);
return;
}
CallBackDto callBackDto = new CallBackDto();
callBackDto.setStreamName(stream);
// 保存录播信息
String callBackType = stream.split("_")[1];
switch (callBackType) {
case Consts.STUDENT_CALL_BACK:// 学生端回调
callBackContext.recordCallBack(VideoCallBackType.STUDENT.getValue(), ObjectCovert(jsonObject, callBackDto));
break;
case Consts.TEACHER_CALL_BACK:// 老师端回调
callBackContext.recordCallBack(VideoCallBackType.TEACHER.getValue(), ObjectCovert(jsonObject, callBackDto));
break;
}
logger.info("=======================out recordBack=======================");
AliUtil.response(request, response, DataStatus.ALIYUN_STATUS_OK, HttpServletResponse.SC_OK);
}
@RequestMapping(value = "/playBack")
@Luna
public void playCallBack(HttpServletRequest request, HttpServletResponse response) throws Exception {
logger.info("=======================access playBack=======================");
String c_length = request.getHeader("content-length");
int c_i = Integer.parseInt(c_length);
// 解析阿里回调服务器的接口数据
String result = AliUtil.GetPostBody(request.getInputStream(), c_i);
logger.info("playCallBack==" + result);
JSONObject jsonObject = JSON.parseObject(result);
VideoLiveReplayDto replayDto = JSON.toJavaObject(jsonObject, VideoLiveReplayDto.class);
String status = jsonObject.getString("Status");
/**
* 五种状态: 1.视频上传完成 EventType String 事件类型,固定为FileUploadComplete
* 2.单个清晰度转码完成 事件类型,固定为StreamTranscodeComplete 3.视频转码完成
* 事件类型,固定为TranscodeComplete 4.视频截图完成 事件类型,固定为SnapshotComplete
* 5.直转点视频录制完成 事件类型,固定为AddLiveRecordVideoComplete 6.直转点录制视频合成开始
* 事件类型,固定为LiveRecordVideoComposeStart
*/
if (!DataStatus.HTTP_STATE_SUCCESS.equals(status)) {
AliUtil.response(request, response, "{\"Status\":\"verdify not ok\"}", HttpServletResponse.SC_BAD_REQUEST);
}
String streamName = jsonObject.getString("StreamName");
String callBackType = "";
String videoLiveId = "";
if (StringUtils.isNotEmpty(streamName)) {
callBackType = streamName.split("_")[1];
videoLiveId = streamName.split("_")[2];
} else {
// 根据VideoId判断callBackType 状态
String videoId = jsonObject.getString("VideoId");
VideoLiveReplayDto videoLiveReplayDto = videoLiveReplayService.findByVideoId(videoId);
if (videoLiveReplayDto != null) {
callBackType = Consts.STUDENT_CALL_BACK;
}
LiveCourseReplayDto liveCourseReplayDto = liveCourseReplayService.findByVideoId(videoId);
if (liveCourseReplayDto != null) {
callBackType = Consts.TEACHER_CALL_BACK;
}
}
switch (callBackType) {
case Consts.STUDENT_CALL_BACK:// 学生端回调
replayDto.setVideoLiveId(videoLiveId);
callBackContext.playCallBack(VideoCallBackType.STUDENT.getValue(), replayDto, jsonObject);
break;
case Consts.TEACHER_CALL_BACK:// 老师端回调
replayDto.setLiveCourseId(videoLiveId);
callBackContext.playCallBack(VideoCallBackType.TEACHER.getValue(), replayDto, jsonObject);
break;
}
AliUtil.response(request, response, DataStatus.ALIYUN_STATUS_OK, HttpServletResponse.SC_OK);
}
private CallBackDto ObjectCovert(JSONObject jsonObject, CallBackDto callBackDto) {
String videoLiveId = jsonObject.getString("stream").split("_")[2];
// 录制域名
callBackDto.setDomainName(jsonObject.getString("domain"));
// 应用名
callBackDto.setAppName(jsonObject.getString("app"));
// 目标录制文件在用户录制 OSS Bucket 下的路径
callBackDto.setUri(jsonObject.getString("uri"));
callBackDto.setRecordDuration(String.valueOf(jsonObject.getDouble("duration")));
callBackDto.setOssEndpoint(jsonObject.getString("oss_endpoint"));
callBackDto.setOssBucket(jsonObject.getString("oss_bucket"));
callBackDto.setVideoLiveId(videoLiveId);
callBackDto.setLiveCourseId(videoLiveId);
return callBackDto;
}
}
/**
* <p>Title: LiveCallBackContext </p>
* <p>Description: 直播回调容器类</p>
* <p>Copyright (c) 2018 </p>
* <p>Company: 上海阳光喔教育科技有限公司</p>
* @author lb
* @date 2018年7月19日 下午1:10:04
* @version 1.0
* <p>修改人:Administrator</p>
* <p>修改时间:2018年7月19日 下午1:10:04</p>
* <p>修改备注:</p>
*/
@Component
public class LiveCallBackContext {
@Autowired
private final Map<String, LiveCallBackStrategy> contextStrategy = new ConcurrentHashMap<>();
public Map<String, LiveCallBackStrategy> getContextStrategy() {
return contextStrategy;
}
@Autowired
public void setContextStrategy(Map<String, LiveCallBackStrategy> contextStrategy) {
this.contextStrategy.clear();
contextStrategy.forEach((k, v) -> this.contextStrategy.put(k, v));
}
/**
* 直播推流回调策略方法
*/
public void contextPushCallBack(String type, CallBackDto callBackDto) throws Exception {
this.contextStrategy.get(type).pushCallBack(callBackDto);
}
/**
* 直播录制回调策略方法
*/
public void recordCallBack(String type, CallBackDto callBackDto) throws Exception {
this.contextStrategy.get(type).recordCallBack(callBackDto);
}
/**
* playCallBack:点播回调
* @param replayDto
* @exception
* @author lb
* @date 2018年7月19日 下午6:21:06
*/
public void playCallBack(String type, VideoLiveReplayDto replayDto, JSONObject jsonObject) throws Exception {
this.contextStrategy.get(type).playCallBack(replayDto, jsonObject);
}
}
package com.ygw.thirdparty.service.impl.aliyun;
/**
* <p>Title: StudentLiveCallBackStrategy </p>
* <p>Description: 类描述</p>
* <p>Copyright (c) 2018 </p>
* <p>Company: 上海阳光喔教育科技有限公司</p>
* @author Administrator
* @date 2018年7月19日 下午1:08:55
* @version 1.0
* <p>修改人:Administrator</p>
* <p>修改时间:2018年7月19日 下午1:08:55</p>
* <p>修改备注:</p>
*/
@Service
public class StudentLiveCallBackStrategy implements LiveCallBackStrategy {
protected Logger logger = Logger.getLogger(StudentLiveCallBackStrategy.class);
@Autowired
private IVideoLiveService videoLiveService;
@Autowired
private IVideoLiveReplayService videoLiveReplayService;
@Override
public void pushCallBack(CallBackDto callBackDto) throws Exception {
// 直播状态:1:未开播,2:直播中.3:直播结束
int liveStatus = DataStatus.INT_ONE;
if (DataStatus.PUBLISH.equals(callBackDto.getAction())) {
liveStatus = DataStatus.INT_TWO;
callBackDto.setOrderNo(DataStatus.INT_ONE);
} else if (DataStatus.PUBLISH_DONE.equals(callBackDto.getAction())) {
liveStatus = DataStatus.INT_THREE;
callBackDto.setOrderNo(DataStatus.INT_THREE);
// 直播结束时间
Long now = System.currentTimeMillis();
callBackDto.setVideoEndTime(new Date(now));
}
callBackDto.setLiveStatus(liveStatus);
VideoLiveDto videoLiveDto = new VideoLiveDto();
BeanUtils.copyProperties(callBackDto, videoLiveDto);
videoLiveDto.setId(callBackDto.getVideoLiveId());// 放入直播id
videoLiveService.editVideoLive(videoLiveDto);
}
@Override
public void recordCallBack(CallBackDto callBackDto) throws Exception {
VideoLiveDto videoLiveDto = videoLiveService.findById(callBackDto.getVideoLiveId());
if (ObjectUtils.isEmpty(videoLiveDto)) {
return;
}
VideoLiveReplayDto replayDto = BeanUtils.createBeanByTarget(videoLiveDto, VideoLiveReplayDto.class);
BeanUtils.copyProperties(callBackDto, replayDto);
videoLiveReplayService.addVideoLiveReplay(replayDto);
}
@Override
public void playCallBack(VideoLiveReplayDto replayDto,JSONObject jsonObject) throws Exception {
logger.info("replayDto = " + replayDto);
switch (replayDto.getEventType()) {
// 第一种情况,直转点视频录制完成
case DataStatus.ADD_LIVE_RECORD_VIDEO_COMPLETE:
VideoLiveReplayDto dto = new VideoLiveReplayDto();
dto.setVideoLiveId(replayDto.getVideoLiveId());
List<VideoLiveReplayDto> videoLiveReplayList = videoLiveReplayService
.findVideoLiveReplayListByParam(dto);
if (CollectionUtils.isNotEmpty(videoLiveReplayList)) {
VideoLiveReplayDto videoLiveReplayDto = videoLiveReplayList.get(0);
videoLiveReplayDto.setEventType(DataStatus.ADD_LIVE_RECORD_VIDEO_COMPLETE);
videoLiveReplayDto.setVideoType(DataStatus.INT_FOUR);
videoLiveReplayDto.setVideoId(replayDto.getVideoId());
videoLiveReplayService.editVideoLiveReplay(videoLiveReplayDto);
}
break;
// 第二种情况,直播录制转点播成功
case DataStatus.TRANSCODE_COMPLETE:
String videoId = jsonObject.getString("VideoId");
JSONArray streamInfos = jsonObject.getJSONArray("StreamInfos");
logger.info("videoId==" + videoId);
logger.info("streamInfos==" + streamInfos);
VideoLiveReplayDto dtos = new VideoLiveReplayDto();
dtos.setVideoId(videoId);
List<VideoLiveReplayDto> videoLiveReplayLists = videoLiveReplayService
.findVideoLiveReplayListByParam(dtos);
logger.info("videoLiveReplayList==" + videoLiveReplayLists);
if (CollectionUtils.isNotEmpty(videoLiveReplayLists)) {
VideoLiveReplayDto videoLiveReplayDto = videoLiveReplayLists.get(0);
videoLiveReplayDto.setEventType(DataStatus.TRANSCODE_COMPLETE);
videoLiveReplayDto.setVideoType(DataStatus.INT_FIVE);
if (streamInfos != null && streamInfos.size() > 0) {
for (int i = 0; i < streamInfos.size(); i++) {
String definition = streamInfos.getJSONObject(i)
.getString("Definition");
String fileUrl = streamInfos.getJSONObject(i)
.getString("FileUrl");
String format = streamInfos.getJSONObject(i)
.getString("Format");
if (DataStatus.DEFINITION_LD.equals(definition) && DataStatus.FORMAT_MP4.equals(format)) {
videoLiveReplayDto.setMp4Ld(fileUrl);
} else if (DataStatus.DEFINITION_LD.equals(definition) && DataStatus.FORMAT_M3U8.equals(format)) {
videoLiveReplayDto.setM3u8Ld(fileUrl);
} else if (DataStatus.DEFINITION_SD.equals(definition) && DataStatus.FORMAT_MP4.equals(format)) {
videoLiveReplayDto.setMp4Sd(fileUrl);
} else if (DataStatus.DEFINITION_SD.equals(definition) && DataStatus.FORMAT_M3U8.equals(format)) {
videoLiveReplayDto.setM3u8Sd(fileUrl);
} else if (DataStatus.DEFINITION_HD.equals(definition) && DataStatus.FORMAT_MP4.equals(format)) {
videoLiveReplayDto.setMp4Hd(fileUrl);
} else if (DataStatus.DEFINITION_HD.equals(definition) && DataStatus.FORMAT_M3U8.equals(format)) {
videoLiveReplayDto.setM3u8Hd(fileUrl);
}
}
videoLiveReplayService.editVideoLiveReplay(videoLiveReplayDto);
}
}
break;
}
}
}
这样就简单实现了阿里云的直播服务,可以在后台新建直播直播吃鸡啦。
上面第二个何地三个回调会有点小坑。第三个回调会调 很多次转码,调的时候需要注意阿里返回参数。
下面简单说下调阿里云SDK,这个看起来比较麻烦,里面方法很多,找到你需要的调用即可,下面举个例子(获取通知在线人数,这个结果大概有三秒钟的延迟):
IClientProfile profile = DefaultProfile.getProfile(DataStatus.ALIYUN_ZONE, DataStatus.ALIYUN_ACCESSKEY, DataStatus.ALIYUN_ACCESSSECRET);
IAcsClient client = new DefaultAcsClient(profile);
DescribeLiveStreamOnlineUserNumRequest describeLiveStreamOnlineUserNumRequest = new DescribeLiveStreamOnlineUserNumRequest();
describeLiveStreamOnlineUserNumRequest.setDomainName(DataStatus.ALIYUN_DO_MAIN);
describeLiveStreamOnlineUserNumRequest.setAppName(DataStatus.ALIYUN_APP_NAME);
String streamName = DataStatus.YGWO_STR + DataStatus.UNDERLINE + DataStatus.TEACHER + DataStatus.UNDERLINE + dto.getId();
describeLiveStreamOnlineUserNumRequest.setStreamName(streamName);
try {
DescribeLiveStreamOnlineUserNumResponse response = client.getAcsResponse(describeLiveStreamOnlineUserNumRequest);
dto.setTotalUserNumber(response.getTotalUserNumber());
} catch (ClientException e) {
e.printStackTrace();
}
最后有什么问题可以咨询我,邮箱:jamhihi@126.com QQ:841973440
感谢同行的无私分享,让我们一起学习,共同进步。
最后说下,有啥问题可以在阿里云提工单,一般都能解决,就是反馈有点慢。