/****************************************************************************** * Copyright (C) 2015 Shenzhen Penguin Network Technology Co., Ltd * All Rights Reserved. * 本软件为深圳市企鹅网络科技有限公司开发研制。未经本公司正式书面同意,其他任何个人、团体 * 不得使用、复制、修改或发布本软件. *****************************************************************************/ package com.qxueyou.scc.teach.live.service.impl; import java.io.File; import java.io.FilenameFilter; import java.util.Arrays; import java.util.Calendar; import java.util.Comparator; import java.util.GregorianCalendar; import java.util.List; import java.util.concurrent.TimeUnit; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import com.qxueyou.scc.base.service.ICacheService; import com.qxueyou.scc.base.service.impl.CommonAppService; import com.qxueyou.scc.base.util.CollectionUtils; import com.qxueyou.scc.base.util.TraceUtils; import com.qxueyou.scc.base.util.UUIDUtils; import com.qxueyou.scc.config.SccConfig; import com.qxueyou.scc.teach.live.model.MediaVideoLive; import com.qxueyou.scc.teach.live.model.MediaVideoLiveReplay; import com.qxueyou.scc.teach.live.service.IMediaVideoLivePlayBackService; import com.qxueyou.scc.teach.live.utils.FfmpegFileVO; import com.qxueyou.scc.teach.live.utils.FfmpegMediaHelper; import com.qxueyou.scc.teach.live.utils.FfmpegVideoInfo; @Service public class MediaVideoLivePlayBackService extends CommonAppService implements IMediaVideoLivePlayBackService { private static final Logger log = LogManager.getLogger(MediaVideoLivePlayBackService.class); //直播回放队列 public static final String LIVE_PLAYBACK_LST = "LIVE_PLAYBACK_LST"; //直播回放全局锁 public static final String LIVE_PLAYBACK_LOCK = "LIVE_PLAYBACK_LOCK"; @Autowired SccConfig cfg; @Autowired ICacheService cacheService; @Autowired private StringRedisTemplate redisTemplate; @Override public List queryVideoLiveReplay(String videoLiveId){ StringBuffer hql = new StringBuffer(500); hql.append("from MediaVideoLiveReplay where videoLiveId=? and deleteFlag is false "); List params = CollectionUtils.newList(videoLiveId); hql.append(" order by orderNo asc"); return this.find(hql.toString(),params, MediaVideoLiveReplay.class); } //生成回放(需修改为晚上2:00 开始执行) @Scheduled(cron = " 0 0/2 * * * ?") protected void doTimer(){ if(this.lock()){ String liveId = null; try { liveId = cacheService.lstLeftPop(LIVE_PLAYBACK_LST); if(StringUtils.isEmpty(liveId)){ return ; } //判断是否是回放状态 MediaVideoLive live = this.read(MediaVideoLive.class, liveId); if(live.getStatus()==MediaVideoLive.STATUS_LIVE_DOWNLOAD){ log.info("doTimer live: "+live.getVideoLiveId()); this.doVideoLivePlayBack(live); //更新状态 live.setStatus(MediaVideoLive.STATUS_LIVE_REVIEW); this.save(live); } } catch (Exception e) { //重新处理 if(StringUtils.isNotEmpty(liveId)){ cacheService.lstRightPush(LIVE_PLAYBACK_LST,liveId); } }finally{ this.releaseLock(); } } } @Override public void testPlayBack(String liveId){ //判断是否是回放状态 MediaVideoLive live = this.read(MediaVideoLive.class, liveId); if(live.getStatus()==MediaVideoLive.STATUS_LIVE_DOWNLOAD){ try { this.doVideoLivePlayBack(live); //更新状态 live.setStatus(MediaVideoLive.STATUS_LIVE_REVIEW); this.save(live); } catch (Exception e) { //重新处理 cacheService.lstRightPush(LIVE_PLAYBACK_LST,liveId); } } } public void doVideoLivePlayBack(MediaVideoLive videoLive) throws Exception{ //读取原文件 File srcRootFile = new File(cfg.getSrcLivePath()); String[] fileNames= srcRootFile.list(new FilenameFilter(){ @Override public boolean accept(File dir, String name) { return name.startsWith(videoLive.getWyLiveNumber()); } }); log.info("doVideoLivePlayBack fileNames: "+fileNames==null?"xxx":Arrays.toString(fileNames)); //排序 Arrays.sort(fileNames, new Comparator(){ @Override public int compare(String o1, String o2) { if(StringUtils.isNotEmpty(o1) && StringUtils.isNotEmpty(o2)){ return getStartTime(o1).compareTo(getStartTime(o2)); } return 0; } }); //读取 if(fileNames!=null && fileNames.length>0){ short order = 1 ; for(String srcFileName:fileNames){ log.info("doCreateVideoLiveReplay srcFileName: "+srcFileName); doCreateVideoLiveReplay(videoLive,srcFileName,order++); //生成回放 } } //清理源文件 if(fileNames!=null && fileNames.length>0){ for(String srcFileName:fileNames){ log.info("删除原文件:filePath"+srcRootFile.getAbsolutePath().concat(File.separator).concat(srcFileName)); FileUtils.deleteQuietly(new File(srcRootFile.getAbsolutePath().concat(File.separator).concat(srcFileName))); } } } //生成VideoLiveReplay信息 private String doCreateVideoLiveReplay(MediaVideoLive videoLive,String srcFileName,short order) throws Exception{ //视频文件后缀 String mediaSuffix = srcFileName.substring(srcFileName.lastIndexOf(".")); //回放地址 String dstPath =this.generateDstPath(videoLive.getVideoLiveId(), mediaSuffix); File srcFile = new File(cfg.getSrcLivePath() + srcFileName); File dstFileDir = new File(cfg.getResRootPath().concat(dstPath)).getParentFile(); //创建目录 dstFileDir.mkdirs(); log.info("开始生成 doCreateVideoLiveReplay: srcFile"+srcFile.getAbsolutePath()); String dstFile = this.convertFlvToMp4(srcFile, dstFileDir); log.info("结束生成 doCreateVideoLiveReplay: dstFile"+ dstFile); //获取视频时长 long playTime = this.getMediaPlayTime(srcFile.getAbsolutePath()); MediaVideoLiveReplay liveReplay = new MediaVideoLiveReplay(); liveReplay.setName((videoLive.getName() + "-" + order + mediaSuffix).replace(".flv", ".mp4")); liveReplay.setVideoLiveId(videoLive.getVideoLiveId()); liveReplay.setOrderNo(order); liveReplay.setBeginTime(this.getStartTime(srcFileName)); liveReplay.setEndTime(this.getStartTime(srcFileName)+playTime); liveReplay.setDuration(playTime); liveReplay.setInitialSize(srcFile.length()); liveReplay.setUrl(dstFile.substring(dstFile.indexOf("live")).replace("\\", "/")); TraceUtils.setCreateTrace(liveReplay); this.save(liveReplay); return liveReplay.getLiveReplayId(); } /** * 获取文件目标路径 * * @param name * @return */ private String generateDstPath(String liveId,String suffix) { StringBuffer path = new StringBuffer(256); path.append("live"); Calendar now = new GregorianCalendar(); path.append('/'); path.append(now.get(Calendar.YEAR)); path.append(StringUtils.leftPad(String.valueOf(now.get(Calendar.MONTH)), 2, '0')); path.append('/'); path.append(now.get(Calendar.DAY_OF_MONTH)); path.append('/'); path.append(liveId); path.append('/'); path.append(UUIDUtils.UUID()); path.append(suffix); return path.toString(); } private long getMediaPlayTime(String filePath) throws Exception{ FfmpegFileVO fileVO = new FfmpegFileVO(); fileVO.setInputPath(filePath); FfmpegVideoInfo videoInfo = new FfmpegVideoInfo(); FfmpegMediaHelper.mediaInfo(fileVO, videoInfo); return videoInfo.getPlayTime(); } private String convertFlvToMp4(File srcFile,File dstFile) throws Exception{ // 获取直播视频片段信息 FfmpegFileVO videoFileVO = new FfmpegFileVO(); videoFileVO.setInputPath(srcFile.getPath()); // 转码视频 videoFileVO.setOutputPath(dstFile.getPath() + File.separator); return FfmpegMediaHelper.converUploadVideoToLiveMp4(videoFileVO, new FfmpegVideoInfo()); //copy备份原片段(暂不备份) // FileUtils.copyFile(srcFile, dstFile); } //格式: d72de336c7ed45beb14641a79074f9bb-1541497652-rec , 时间戳单位: 秒 private Long getStartTime(String filePath){ return Long.valueOf(filePath.split("-")[1]); } private boolean lock(){ boolean lockResult = redisTemplate.opsForValue().setIfAbsent(LIVE_PLAYBACK_LOCK, "lock"); //可能发生死锁,需要手动清除锁 if(lockResult==true){ redisTemplate.expire(LIVE_PLAYBACK_LOCK, 6, TimeUnit.HOURS); } return lockResult; } private void releaseLock(){ redisTemplate.delete(LIVE_PLAYBACK_LOCK); } }