|
@@ -3,6 +3,7 @@ package com.fs.live.service.impl;
|
|
import com.alibaba.fastjson.JSON;
|
|
import com.alibaba.fastjson.JSON;
|
|
import com.alibaba.fastjson.JSONObject;
|
|
import com.alibaba.fastjson.JSONObject;
|
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
|
|
+import com.fs.ad.yk.utils.Md5Util;
|
|
import com.fs.common.core.domain.R;
|
|
import com.fs.common.core.domain.R;
|
|
import com.fs.common.exception.base.BaseException;
|
|
import com.fs.common.exception.base.BaseException;
|
|
import com.fs.common.utils.DateUtils;
|
|
import com.fs.common.utils.DateUtils;
|
|
@@ -16,6 +17,8 @@ import com.fs.live.mapper.LiveMapper;
|
|
import com.fs.live.service.ILiveDataService;
|
|
import com.fs.live.service.ILiveDataService;
|
|
import com.fs.live.service.ILiveService;
|
|
import com.fs.live.service.ILiveService;
|
|
import com.fs.live.service.ILiveVideoService;
|
|
import com.fs.live.service.ILiveVideoService;
|
|
|
|
+import com.fs.live.utils.ProcessManager;
|
|
|
|
+import com.fs.live.vo.LiveListVo;
|
|
import com.fs.system.domain.SysConfig;
|
|
import com.fs.system.domain.SysConfig;
|
|
import com.fs.system.service.ISysConfigService;
|
|
import com.fs.system.service.ISysConfigService;
|
|
import lombok.AllArgsConstructor;
|
|
import lombok.AllArgsConstructor;
|
|
@@ -52,6 +55,9 @@ public class LiveServiceImpl extends ServiceImpl<LiveMapper, Live> implements IL
|
|
@Autowired
|
|
@Autowired
|
|
private IFsUserTalentService fsUserTalentService;
|
|
private IFsUserTalentService fsUserTalentService;
|
|
|
|
|
|
|
|
+ @Autowired
|
|
|
|
+ private ProcessManager processManager;
|
|
|
|
+
|
|
/**
|
|
/**
|
|
* 查询直播
|
|
* 查询直播
|
|
*
|
|
*
|
|
@@ -217,9 +223,9 @@ public class LiveServiceImpl extends ServiceImpl<LiveMapper, Live> implements IL
|
|
return R.error("缺失直播配置");
|
|
return R.error("缺失直播配置");
|
|
}
|
|
}
|
|
String rtmpPushUrl = generateRtmpPushUrl(livingConfigMap.get("domain"), livingConfigMap.get("app"), liveId.toString());
|
|
String rtmpPushUrl = generateRtmpPushUrl(livingConfigMap.get("domain"), livingConfigMap.get("app"), liveId.toString());
|
|
- String flvPlayUrl = generateFlvPlayUrl(livingConfigMap.get("http"), livingConfigMap.get("app"), liveId.toString());
|
|
|
|
|
|
+ String hlvPlayUrl = generateHlvPlayUrl(livingConfigMap.get("http"), livingConfigMap.get("app"), liveId.toString());
|
|
live.setRtmpUrl(rtmpPushUrl);
|
|
live.setRtmpUrl(rtmpPushUrl);
|
|
- live.setFlvHlsUrl(flvPlayUrl);
|
|
|
|
|
|
+ live.setFlvHlsUrl(hlvPlayUrl);
|
|
live.setStartTime(LocalDateTime.now());
|
|
live.setStartTime(LocalDateTime.now());
|
|
live.setStatus(2);
|
|
live.setStatus(2);
|
|
live.setLiveType(1);
|
|
live.setLiveType(1);
|
|
@@ -276,6 +282,102 @@ public class LiveServiceImpl extends ServiceImpl<LiveMapper, Live> implements IL
|
|
liveMapper.updateLive(live);
|
|
liveMapper.updateLive(live);
|
|
return R.ok();
|
|
return R.ok();
|
|
}
|
|
}
|
|
|
|
+ /**
|
|
|
|
+ * 循环播放
|
|
|
|
+ * @param live 直播
|
|
|
|
+ * @return R
|
|
|
|
+ */
|
|
|
|
+ @Override
|
|
|
|
+ public R startLoopPlay(Live live) {
|
|
|
|
+ try {
|
|
|
|
+ Live curLive = liveMapper.selectLiveByLiveId(live.getLiveId());
|
|
|
|
+ if (curLive == null) {
|
|
|
|
+ return R.error("直播间不存在");
|
|
|
|
+ }
|
|
|
|
+ LiveVideo curLiveVideo = liveVideoService.selectLiveVideoByLiveId(live.getLiveId());
|
|
|
|
+ if (curLiveVideo == null) {
|
|
|
|
+ return R.error("录播不存在");
|
|
|
|
+ }
|
|
|
|
+ Date now = new Date();
|
|
|
|
+
|
|
|
|
+ // 生成唯一的流密钥
|
|
|
|
+ String streamKey = "stream_" + live.getLiveId() + "_" + System.currentTimeMillis();
|
|
|
|
+
|
|
|
|
+ // 构建FFmpeg推流命令
|
|
|
|
+ String ffmpegCmd = buildFFmpegCommand(curLiveVideo.getVideoUrl(), streamKey);
|
|
|
|
+
|
|
|
|
+ // 启动推流进程
|
|
|
|
+ Process process = processManager.startProcess(ffmpegCmd);
|
|
|
|
+
|
|
|
|
+ // 保存流信息到数据库
|
|
|
|
+ curLive.setLiveType(2);
|
|
|
|
+ curLive.setRtmpUrl("rtmp://your-srs-server/live/" + streamKey);
|
|
|
|
+ curLive.setUpdateTime(now);
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ liveMapper.updateLive(curLive);
|
|
|
|
+
|
|
|
|
+ return R.ok();
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
+ return R.error("启动推流失败: " + e.getMessage());
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ /**
|
|
|
|
+ * 停止循环播放
|
|
|
|
+ * @param live 直播
|
|
|
|
+ * @return R
|
|
|
|
+ */
|
|
|
|
+ @Override
|
|
|
|
+ public R stopLoopPlay(Live live) {
|
|
|
|
+ try {
|
|
|
|
+ Live curLive = liveMapper.selectLiveByLiveId(live.getLiveId());
|
|
|
|
+ if (curLive == null) {
|
|
|
|
+ return R.error("直播间不存在");
|
|
|
|
+ }
|
|
|
|
+ // 停止推流进程
|
|
|
|
+ boolean stopped = processManager.stopProcess(curLive.getRemark());
|
|
|
|
+ if (!stopped) {
|
|
|
|
+ return R.error("停止播放失败");
|
|
|
|
+ }
|
|
|
|
+ // 更新流状态
|
|
|
|
+ curLive.setStatus(2);
|
|
|
|
+ curLive.setUpdateTime(new Date());
|
|
|
|
+ liveMapper.updateLive(curLive);
|
|
|
|
+
|
|
|
|
+ return R.ok();
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
+ return R.error("停止播放失败: " + e.getMessage());
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public R handleShelfOrUn(LiveListVo listVo) {
|
|
|
|
+ int updatedCount = liveMapper.updateBatchLiveList(listVo);
|
|
|
|
+ if (updatedCount > 0) {
|
|
|
|
+ return R.ok("操作成功");
|
|
|
|
+ }
|
|
|
|
+ return R.error();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public R handleDeleteSelected(LiveListVo listVo) {
|
|
|
|
+ int deleteCount = liveMapper.deleteBatchLiveList(listVo);
|
|
|
|
+ if (deleteCount > 0) {
|
|
|
|
+ return R.ok("操作成功");
|
|
|
|
+ }
|
|
|
|
+ return R.error();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 构建FFmpeg推流命令
|
|
|
|
+ */
|
|
|
|
+ private String buildFFmpegCommand(String videoPath, String streamKey) {
|
|
|
|
+ return String.format(
|
|
|
|
+ "ffmpeg -re -stream_loop -1 -i \"%s\" -c:v libx264 -preset ultrafast -b:v 1000k " +
|
|
|
|
+ "-c:a aac -b:a 128k -f flv rtmp://your-srs-server/live/%s",
|
|
|
|
+ videoPath, streamKey
|
|
|
|
+ );
|
|
|
|
+ }
|
|
|
|
|
|
/**
|
|
/**
|
|
* 身份证信息验证 阿里商品购买接口
|
|
* 身份证信息验证 阿里商品购买接口
|
|
@@ -291,9 +393,8 @@ public class LiveServiceImpl extends ServiceImpl<LiveMapper, Live> implements IL
|
|
if (live == null) {
|
|
if (live == null) {
|
|
return R.error("直播间不存在");
|
|
return R.error("直播间不存在");
|
|
}
|
|
}
|
|
- String url = "https://puhui.shumaidata.com/idcard-ocr/photo/puhui";
|
|
|
|
- // todo 买的产品 appcode 个人 需要更换为公司的
|
|
|
|
- String appCode = "ed8f0f22564b48c5abdb18a02c1d701c";
|
|
|
|
|
|
+ String url = "https://idcardside.market.alicloudapi.com/idcard_ocr";
|
|
|
|
+ String appCode = "0a01beb5322e49ca802423fe29df9582";
|
|
try {
|
|
try {
|
|
Map<String, String> params = new HashMap<>();
|
|
Map<String, String> params = new HashMap<>();
|
|
//image 和 url 二选一 ,选择其中一个时,请注释另外一个参数,默认使用image
|
|
//image 和 url 二选一 ,选择其中一个时,请注释另外一个参数,默认使用image
|
|
@@ -302,7 +403,6 @@ public class LiveServiceImpl extends ServiceImpl<LiveMapper, Live> implements IL
|
|
String result = postForm(appCode, url, params);
|
|
String result = postForm(appCode, url, params);
|
|
JSONObject jsonObject = JSONObject.parseObject(result);
|
|
JSONObject jsonObject = JSONObject.parseObject(result);
|
|
if (200 == jsonObject.getIntValue("code") && jsonObject.getBooleanValue("success")) {
|
|
if (200 == jsonObject.getIntValue("code") && jsonObject.getBooleanValue("success")) {
|
|
- // todo 后面修改先写死
|
|
|
|
live.setIdCardUrl(payload.get("idCardUrl"));
|
|
live.setIdCardUrl(payload.get("idCardUrl"));
|
|
liveMapper.updateLive(live);
|
|
liveMapper.updateLive(live);
|
|
return R.ok();
|
|
return R.ok();
|
|
@@ -338,19 +438,51 @@ public class LiveServiceImpl extends ServiceImpl<LiveMapper, Live> implements IL
|
|
|
|
|
|
|
|
|
|
public static String generateRtmpPushUrl(String domain, String app, String liveId) {
|
|
public static String generateRtmpPushUrl(String domain, String app, String liveId) {
|
|
- long timestamp = System.currentTimeMillis() / 1000; // 秒级时间戳
|
|
|
|
- String SECRET_KEY = liveId + timestamp;
|
|
|
|
- String sign = Base64Utils.encodeToString(Md5Utils.md5(liveId + SECRET_KEY + timestamp)).replace("/","") ;
|
|
|
|
- return String.format("rtmp://%s/%s/%s?timestamp=%d&sign=%s", domain, app, liveId,timestamp,sign);
|
|
|
|
|
|
+ Date now = new Date();
|
|
|
|
+ now.setTime(now.getTime() + 24 * 3600 * 1000);
|
|
|
|
+
|
|
|
|
+ return domain +
|
|
|
|
+ "/" +
|
|
|
|
+ app +
|
|
|
|
+ "/" +
|
|
|
|
+ liveId +
|
|
|
|
+ "?" +
|
|
|
|
+ "txSecret=" +
|
|
|
|
+ Md5Util.MD5("a61df75ae15271d7ef922f308e61e92e" + liveId + to16Hex(now)) +
|
|
|
|
+ "&" +
|
|
|
|
+ "txTime=" +
|
|
|
|
+ to16Hex(now);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
* 生成 FLV 播放地址(用于前端播放)
|
|
* 生成 FLV 播放地址(用于前端播放)
|
|
*/
|
|
*/
|
|
- public static String generateFlvPlayUrl(String domain, String app, String liveId) {
|
|
|
|
- long timestamp = System.currentTimeMillis() / 1000;
|
|
|
|
- String SECRET_KEY = liveId + timestamp;
|
|
|
|
- String sign = Base64Utils.encodeToString(Md5Utils.md5(liveId + SECRET_KEY + timestamp)).replace("/","");
|
|
|
|
- return String.format("http://%s/%s/%s.m3u8?timestamp=%d&sign=%s", domain, app, liveId, timestamp, sign);
|
|
|
|
|
|
+ public static String generateHlvPlayUrl(String domain, String app, String liveId) {
|
|
|
|
+ Date now = new Date();
|
|
|
|
+ now.setTime(now.getTime() + 24 * 3600 * 1000);
|
|
|
|
+
|
|
|
|
+ return domain +
|
|
|
|
+ "/" +
|
|
|
|
+ app +
|
|
|
|
+ "/" +
|
|
|
|
+ liveId +
|
|
|
|
+ ".m3u8?" +
|
|
|
|
+ "txSecret=" +
|
|
|
|
+ Md5Util.MD5("NiMfMYG5HQxZQ5bk88ri" + liveId + to16Hex(now)) +
|
|
|
|
+ "&" +
|
|
|
|
+ "txTime=" +
|
|
|
|
+ to16Hex(now);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 将传入的时间转换为 16进制
|
|
|
|
+ *
|
|
|
|
+ * @param date
|
|
|
|
+ * @return
|
|
|
|
+ */
|
|
|
|
+ public static String to16Hex(Date date) {
|
|
|
|
+ Long aLong = date.getTime() / 1000;
|
|
|
|
+ String hexString = Long.toHexString(aLong);
|
|
|
|
+ return hexString.toUpperCase();
|
|
}
|
|
}
|
|
}
|
|
}
|