新增视频转码、视频预览功能
This commit is contained in:
parent
4c6c5d00c9
commit
ee812fc4c5
@ -61,14 +61,9 @@ public class VideoController extends DefaultBaseController {
|
||||
@ApiImplicitParam(name = "ids", value = "ID列表,用下划线分隔", paramType = "path", example = "1_2_3")
|
||||
})
|
||||
@ApiResponses({@ApiResponse(code = 400, message = "请求失败", response = ErrorResult.class)})
|
||||
@DeleteMapping("remove/{isRemoveSource}/{ids}")
|
||||
public synchronized SuccessResult remove(@PathVariable() Integer isRemoveSource, @PathVariable("ids") String ids) {
|
||||
if (isRemoveSource == 0) {
|
||||
videoService.remove(Arrays.asList(ids.split("\\_")));
|
||||
}
|
||||
if (isRemoveSource == 1) {
|
||||
videoService.delete(Arrays.asList(ids.split("\\_")));
|
||||
}
|
||||
@DeleteMapping("delete/{ids}")
|
||||
public synchronized SuccessResult delete(@PathVariable("ids") String ids) {
|
||||
videoService.delete(Arrays.asList(ids.split("\\_")));
|
||||
return new SuccessResult();
|
||||
}
|
||||
|
||||
|
@ -1,14 +1,23 @@
|
||||
package ink.wgink.module.file.media.controller.route.video;
|
||||
|
||||
import ink.wgink.exceptions.ParamsException;
|
||||
import ink.wgink.interfaces.consts.ISystemConstant;
|
||||
import ink.wgink.module.file.media.service.video.IVideoService;
|
||||
import ink.wgink.pojo.result.ErrorResult;
|
||||
import ink.wgink.properties.media.video.VideoProperties;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.*;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import javax.servlet.AsyncContext;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* When you feel like quitting. Think about why you started
|
||||
* 当你想要放弃的时候,想想当初你为何开始
|
||||
@ -19,13 +28,15 @@ import org.springframework.web.servlet.ModelAndView;
|
||||
* @Date: 2021/6/12 11:16 下午
|
||||
* @Version: 1.0
|
||||
*/
|
||||
@Api(tags = ISystemConstant.API_TAGS_SYSTEM_PREFIX + "文件管理接口")
|
||||
@Api(tags = ISystemConstant.API_TAGS_SYSTEM_PREFIX + "视频管理接口")
|
||||
@Controller
|
||||
@RequestMapping(ISystemConstant.ROUTE_PREFIX + "/file/media/video")
|
||||
public class VideoRouteController {
|
||||
|
||||
@Autowired
|
||||
private VideoProperties videoProperties;
|
||||
@Autowired
|
||||
private IVideoService videoService;
|
||||
|
||||
@GetMapping("list")
|
||||
public ModelAndView list() {
|
||||
@ -40,4 +51,29 @@ public class VideoRouteController {
|
||||
return modelAndView;
|
||||
}
|
||||
|
||||
@GetMapping("preview")
|
||||
public ModelAndView preview() {
|
||||
return new ModelAndView("file/media/video/preview");
|
||||
}
|
||||
|
||||
@ApiOperation(value = "下载视频", notes = "下载视频接口")
|
||||
@ApiImplicitParams({
|
||||
@ApiImplicitParam(name = "isOpen", value = "是否打开,true和false", paramType = "path"),
|
||||
@ApiImplicitParam(name = "fileId", value = "文件ID", paramType = "path")
|
||||
})
|
||||
@ApiResponses({@ApiResponse(code = 400, message = "请求失败", response = ErrorResult.class)})
|
||||
@GetMapping(value = "download/{isOpen}/{fileId}", produces = "video/mp4")
|
||||
public void downLoad(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
@PathVariable("isOpen") Boolean isOpen,
|
||||
@PathVariable("fileId") String fileId) {
|
||||
if (isOpen == null) {
|
||||
throw new ParamsException("参数错误");
|
||||
}
|
||||
if (fileId.indexOf(".") > 0) {
|
||||
fileId = fileId.split("\\.")[0];
|
||||
}
|
||||
videoService.downLoad(request, response, isOpen, fileId);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -17,8 +17,20 @@ import java.io.InputStream;
|
||||
*/
|
||||
public interface IMediaStream {
|
||||
|
||||
/**
|
||||
* 输入
|
||||
*
|
||||
* @param inputStream
|
||||
* @throws Exception
|
||||
*/
|
||||
void input(InputStream inputStream) throws Exception;
|
||||
|
||||
/**
|
||||
* 错误
|
||||
*
|
||||
* @param errorStream
|
||||
* @throws Exception
|
||||
*/
|
||||
void error(InputStream errorStream) throws Exception;
|
||||
|
||||
}
|
||||
|
@ -59,7 +59,65 @@ public class MediaServiceImpl extends DefaultBaseService implements IMediaServic
|
||||
|
||||
@Override
|
||||
public String getContentType(String fileType) {
|
||||
return null;
|
||||
String contentType;
|
||||
switch (fileType) {
|
||||
case "png":
|
||||
contentType = "image/png";
|
||||
break;
|
||||
case "blob":
|
||||
case "jpg":
|
||||
case "jpeg":
|
||||
contentType = "image/jpeg";
|
||||
break;
|
||||
case "gif":
|
||||
contentType = "image/gif";
|
||||
break;
|
||||
case "mp4":
|
||||
contentType = "video/mp4";
|
||||
break;
|
||||
case "rmvb":
|
||||
contentType = "application/vnd.rn-realmedia-vbr";
|
||||
break;
|
||||
case "mp3":
|
||||
contentType = "audio/mp3";
|
||||
break;
|
||||
case "wmv":
|
||||
contentType = "video/x-ms-wmv";
|
||||
break;
|
||||
case "wav":
|
||||
contentType = "audio/wav";
|
||||
break;
|
||||
case "doc":
|
||||
contentType = "application/msword";
|
||||
break;
|
||||
case "docx":
|
||||
contentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
|
||||
break;
|
||||
case "xls":
|
||||
contentType = "application/vnd.ms-excel";
|
||||
break;
|
||||
case "xlsx":
|
||||
contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
||||
break;
|
||||
case "ppt":
|
||||
contentType = "application/vnd.ms-powerpoint";
|
||||
break;
|
||||
case "pptx":
|
||||
contentType = "application/vnd.openxmlformats-officedocument.presentationml.presentation";
|
||||
break;
|
||||
case "txt":
|
||||
contentType = "text/plain";
|
||||
break;
|
||||
case "apk":
|
||||
contentType = "application/vnd.android.package-archive";
|
||||
break;
|
||||
case "pdf":
|
||||
contentType = "application/pdf";
|
||||
break;
|
||||
default:
|
||||
contentType = "application/octet-stream";
|
||||
}
|
||||
return contentType;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -9,7 +9,10 @@ import ink.wgink.pojo.result.SuccessResultList;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@ -137,4 +140,13 @@ public interface IVideoService {
|
||||
*/
|
||||
SuccessResultList<List<VideoDTO>> listPage(ListPage page);
|
||||
|
||||
/**
|
||||
* 文件下载
|
||||
*
|
||||
* @param request
|
||||
* @param response
|
||||
* @param isOpen
|
||||
* @param fileId
|
||||
*/
|
||||
void downLoad(HttpServletRequest request, HttpServletResponse response, Boolean isOpen, String fileId);
|
||||
}
|
||||
|
@ -18,18 +18,32 @@ import ink.wgink.module.file.media.service.video.IVideoService;
|
||||
import ink.wgink.module.file.media.task.transcoding.VideoConvertManager;
|
||||
import ink.wgink.module.file.media.task.transcoding.runnable.VideoConvertRunnable;
|
||||
import ink.wgink.pojo.ListPage;
|
||||
import ink.wgink.pojo.pos.FilePO;
|
||||
import ink.wgink.pojo.result.SuccessResultList;
|
||||
import ink.wgink.properties.media.MediaProperties;
|
||||
import ink.wgink.util.UUIDUtil;
|
||||
import ink.wgink.util.date.DateUtil;
|
||||
import ink.wgink.util.map.HashMapUtil;
|
||||
import org.apache.catalina.connector.ClientAbortException;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@ -126,6 +140,7 @@ public class VideoServiceImpl extends DefaultBaseService implements IVideoServic
|
||||
params.put("fileFullPath", convertFile.getAbsolutePath());
|
||||
|
||||
VideoMetaInfo videoMetaInfo = MediaManager.getInstance().getVideoMetaInfo(convertFile);
|
||||
System.out.println(videoMetaInfo);
|
||||
params.put("videoDuration", videoMetaInfo.getDuration());
|
||||
params.put("videoWidth", videoMetaInfo.getWidth());
|
||||
params.put("videoHeight", videoMetaInfo.getHeight());
|
||||
@ -173,7 +188,7 @@ public class VideoServiceImpl extends DefaultBaseService implements IVideoServic
|
||||
|
||||
// 构建视频内容
|
||||
VideoVO videoVO = new VideoVO();
|
||||
videoVO.setFileName(fileName);
|
||||
videoVO.setFileName(fileName.split("\\.")[0]);
|
||||
videoVO.setFileFullPath(fileFullPath);
|
||||
videoVO.setFilePath(filePath);
|
||||
videoVO.setFileSize(fileSize);
|
||||
@ -204,18 +219,17 @@ public class VideoServiceImpl extends DefaultBaseService implements IVideoServic
|
||||
|
||||
@Override
|
||||
public SseEmitter getConvertProgress() {
|
||||
String threadId = String.valueOf(Thread.currentThread().getId());
|
||||
// 30分钟超时
|
||||
SseEmitter sseEmitter = new SseEmitter(1800000L);
|
||||
final String sseEmitterId = UUIDUtil.getUUID();
|
||||
SseEmitter sseEmitter = new SseEmitter(0L);
|
||||
sseEmitter.onTimeout(() -> {
|
||||
VideoConvertManager.getInstance().removeConvertProgressSseEmitter(threadId);
|
||||
VideoConvertManager.getInstance().removeConvertProgressSseEmitter(sseEmitterId);
|
||||
LOG.debug("连接超时,移出队列");
|
||||
});
|
||||
sseEmitter.onCompletion(() -> {
|
||||
VideoConvertManager.getInstance().removeConvertProgressSseEmitter(sseEmitterId);
|
||||
LOG.debug("连接结束,移出队列");
|
||||
});
|
||||
sseEmitter.onError(throwable -> {
|
||||
throwable.printStackTrace();
|
||||
});
|
||||
VideoConvertManager.getInstance().addConvertProgressSseEmitter(threadId, sseEmitter);
|
||||
VideoConvertManager.getInstance().addConvertProgressSseEmitter(sseEmitterId, sseEmitter);
|
||||
return sseEmitter;
|
||||
}
|
||||
|
||||
@ -251,6 +265,79 @@ public class VideoServiceImpl extends DefaultBaseService implements IVideoServic
|
||||
return new SuccessResultList<>(videoDTOs, pageInfo.getPageNum(), pageInfo.getTotal());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void downLoad(HttpServletRequest request, HttpServletResponse response, Boolean isOpen, String fileId) {
|
||||
VideoDTO videoDTO = get(fileId);
|
||||
if (videoDTO == null) {
|
||||
response.setStatus(HttpStatus.MOVED_PERMANENTLY.value());
|
||||
try {
|
||||
response.sendRedirect("404.html");
|
||||
} catch (IOException e) {
|
||||
throw new FileException("文件输出异常", e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
try (
|
||||
RandomAccessFile randomAccessFile = new RandomAccessFile(videoDTO.getFileFullPath(), "r");
|
||||
FileChannel fileChannel = randomAccessFile.getChannel();
|
||||
OutputStream outputStream = response.getOutputStream();
|
||||
WritableByteChannel writableByteChannel = Channels.newChannel(outputStream);
|
||||
) {
|
||||
response.setHeader("Content-Length", String.valueOf(videoDTO.getFileSize()));
|
||||
response.setContentType(mediaService.getContentType(videoDTO.getFileType()));
|
||||
if (!isOpen) {
|
||||
response.setHeader("Content-Disposition", "attachment;fileName=" + URLEncoder.encode(videoDTO.getFileName(), "UTF-8"));
|
||||
} else {
|
||||
response.setHeader("Content-Disposition", "inline;fileName=" + URLEncoder.encode(videoDTO.getFileName(), "UTF-8"));
|
||||
}
|
||||
String rangeString = request.getHeader("Range");
|
||||
LOG.debug("range: {}", rangeString);
|
||||
long contentLength = Long.valueOf(videoDTO.getFileSize());
|
||||
long startRange = 0;
|
||||
long endRange = contentLength - 1;
|
||||
if (!StringUtils.isBlank(rangeString)) {
|
||||
if (!isOpen) {
|
||||
response.setContentType("multipart/byteranges");
|
||||
}
|
||||
String[] rangeArray = rangeString.substring(rangeString.indexOf("=") + 1).split("-");
|
||||
startRange = Long.valueOf(rangeArray[0]);
|
||||
if (rangeArray.length > 1) {
|
||||
endRange = Long.valueOf(rangeArray[1]);
|
||||
}
|
||||
setRangeHeader(startRange, endRange, response, videoDTO.getFileId(), contentLength);
|
||||
randomAccessFile.seek(startRange);
|
||||
}
|
||||
LOG.debug("startRange: {}, endRange: {}", startRange, endRange);
|
||||
long totalOutputLength = endRange - startRange + 1;
|
||||
fileChannel.transferTo(startRange, totalOutputLength, writableByteChannel);
|
||||
outputStream.flush();
|
||||
} catch (Exception e) {
|
||||
if (e instanceof ClientAbortException) {
|
||||
LOG.debug("客户端断开连接");
|
||||
} else {
|
||||
throw new FileException("文件输出异常", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理视频流问题
|
||||
*
|
||||
* @param startRange
|
||||
* @param endRange
|
||||
* @param response
|
||||
* @param fileId
|
||||
* @param contentLength
|
||||
*/
|
||||
private void setRangeHeader(long startRange, long endRange, HttpServletResponse response, String fileId, long contentLength) {
|
||||
// 这里不设置,会出现第一次加载很慢的情况
|
||||
response.setHeader("Content-Length", String.valueOf(endRange - startRange + 1));
|
||||
response.setHeader("Content-Range", String.format("bytes %d-%d/%d", startRange, endRange, contentLength));
|
||||
response.setHeader("Accept-Ranges", "bytes");
|
||||
response.setHeader("Etag", fileId);
|
||||
response.setStatus(206);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查转换中状态
|
||||
*/
|
||||
|
@ -2,8 +2,11 @@ package ink.wgink.module.file.media.task.transcoding;
|
||||
|
||||
import ink.wgink.module.file.media.pojo.dtos.ConvertProgressDTO;
|
||||
import ink.wgink.module.file.media.task.transcoding.runnable.VideoConvertRunnable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
@ -18,7 +21,7 @@ import java.util.concurrent.*;
|
||||
* @Version: 1.0
|
||||
*/
|
||||
public class VideoConvertManager {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(VideoConvertManager.class);
|
||||
private static VideoConvertManager VIDEO_TRANS_CODING_MANAGER = VideoTransCodingManagerBuilder.videoConvertManager;
|
||||
private ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 10, 30L, TimeUnit.SECONDS, new LinkedBlockingDeque<>());
|
||||
private Map<String, SseEmitter> convertProgressSseEmitterMap = new ConcurrentHashMap<>();
|
||||
@ -34,24 +37,34 @@ public class VideoConvertManager {
|
||||
threadPoolExecutor.execute(videoConvertRunnable);
|
||||
}
|
||||
|
||||
public void addConvertProgressSseEmitter(String threadId, SseEmitter sseEmitter) {
|
||||
convertProgressSseEmitterMap.put(threadId, sseEmitter);
|
||||
public void addConvertProgressSseEmitter(String sseEmitterId, SseEmitter sseEmitter) {
|
||||
convertProgressSseEmitterMap.put(sseEmitterId, sseEmitter);
|
||||
// 发送空对象
|
||||
ConvertProgressDTO convertProgressDTO = new ConvertProgressDTO();
|
||||
convertProgressDTO.setFileId("");
|
||||
convertProgressDTO.setPercent(0D);
|
||||
try {
|
||||
sseEmitter.send(convertProgressDTO);
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void removeConvertProgressSseEmitter(String threadId) {
|
||||
convertProgressSseEmitterMap.remove(threadId);
|
||||
public void removeConvertProgressSseEmitter(String sseEmitterId) {
|
||||
convertProgressSseEmitterMap.remove(sseEmitterId);
|
||||
}
|
||||
|
||||
public void convertProgressSseEmitterNotice(String fileId, Double percent) {
|
||||
ConvertProgressDTO convertProgressDTO = new ConvertProgressDTO();
|
||||
convertProgressDTO.setFileId(fileId);
|
||||
convertProgressDTO.setPercent(percent);
|
||||
try {
|
||||
for (Map.Entry<String, SseEmitter> kv : convertProgressSseEmitterMap.entrySet()) {
|
||||
LOG.debug("视频ID {} 转码进度:{}%", fileId, percent);
|
||||
for (Map.Entry<String, SseEmitter> kv : convertProgressSseEmitterMap.entrySet()) {
|
||||
try {
|
||||
kv.getValue().send(convertProgressDTO);
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,30 +85,17 @@ public class VideoConvertRunnable implements Runnable {
|
||||
if (fullTime > 0L) {
|
||||
convertPercent = new BigDecimal((currentTime / (fullTime * 1D) * 100D)).setScale(2, RoundingMode.HALF_UP).doubleValue();
|
||||
}
|
||||
if (convertPercent > 0D && convertPercent < 100D && (int) convertPercent % 5 == 0) {
|
||||
if (convertPercent > 0D && convertPercent < 100D) {
|
||||
VideoConvertManager.getInstance().convertProgressSseEmitterNotice(fileId, convertPercent);
|
||||
}
|
||||
if (convertPercent >= 100D) {
|
||||
updateConvertStatus();
|
||||
}
|
||||
}
|
||||
VideoConvertManager.getInstance().convertProgressSseEmitterNotice(fileId, convertPercent);
|
||||
LOG.debug("转码结束,更新状态");
|
||||
videoService.updateConvert(fileId, outFile);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新转换状态
|
||||
*/
|
||||
private void updateConvertStatus() {
|
||||
if (isUpdateConvertStatus) {
|
||||
return;
|
||||
}
|
||||
VideoConvertManager.getInstance().convertProgressSseEmitterNotice(fileId, convertPercent);
|
||||
isUpdateConvertStatus = true;
|
||||
LOG.debug("转码结束,更新状态");
|
||||
videoService.updateConvert(fileId, outFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* 视频时长
|
||||
*
|
||||
|
@ -236,11 +236,14 @@
|
||||
FROM
|
||||
media_video
|
||||
<where>
|
||||
is_delete = 0
|
||||
<if test="fileId != null and fileId != ''">
|
||||
AND
|
||||
file_id = #{fileId}
|
||||
</if>
|
||||
<if test="fileMd5 != null and fileMd5 != ''">
|
||||
AND file_md5 = #{fileMd5}
|
||||
AND
|
||||
file_md5 = #{fileMd5}
|
||||
</if>
|
||||
</where>
|
||||
</select>
|
||||
|
@ -9,6 +9,10 @@
|
||||
<link rel="stylesheet" href="assets/fonts/font-awesome/css/font-awesome.css"/>
|
||||
<link rel="stylesheet" href="assets/layuiadmin/layui/css/layui.css" media="all">
|
||||
<link rel="stylesheet" href="assets/layuiadmin/style/admin.css" media="all">
|
||||
<style>
|
||||
.layui-table-progress {height: 28px !important; line-height: 28px !important; border-radius: 0px !important;}
|
||||
.layui-table-progress-bar {height: 28px !important; line-height: 28px !important; border-radius: 0px !important;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="layui-fluid layui-anim layui-anim-fadein">
|
||||
@ -41,45 +45,29 @@
|
||||
<i class="fa fa-lg fa-trash"></i> 删除
|
||||
</button>
|
||||
</div>
|
||||
<span style="margin-left: 10px;"><i class="fa fa-warning"></i> 视频在线播放只支持MP4格式,格式不符点击"转码状态"列的"转码"按钮进行转码</span>
|
||||
<span style="margin-left: 10px;"><i class="fa fa-warning"></i> 视频在线播放只支持MP4格式,格式不符合或不能播放,请点击"转码状态"列的"开始转码"按钮进行转码</span>
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="videoBoxBackGround" style="position: fixed;z-index: 1000;top: 0;left: 0;width: 100%;height: 100%;background-color: rgba(0,0,0,0.6); display: none;">
|
||||
<div role="button" class="viewer-button viewer-close video-close-btn"></div>
|
||||
<div id="videoBox" style="width: 600px; height: 400px; position: absolute; left: 50%; top: 50%; margin-left: -300px; margin-top: -200px;">
|
||||
<div id="video" style="width: 100%; height: 100%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="assets/js/vendor/ckplayer/ckplayer/ckplayer.js"></script>
|
||||
<script src="assets/layuiadmin/layui/layui.js"></script>
|
||||
<script type="text/javascript">
|
||||
layui.config({
|
||||
base: 'assets/layuiadmin/'
|
||||
}).extend({
|
||||
index: 'lib/index'
|
||||
}).use(['index', 'table', 'laydate', 'common'], function() {
|
||||
}).use(['index', 'table', 'laydate', 'element', 'common'], function() {
|
||||
var $ = layui.$;
|
||||
var $win = $(window);
|
||||
var table = layui.table;
|
||||
var admin = layui.admin;
|
||||
var laydate = layui.laydate;
|
||||
var element = layui.element;
|
||||
var common = layui.common;
|
||||
var resizeTimeout = null;
|
||||
var tableUrl = 'api/file/media/video/listpage';
|
||||
var ckPlayerFileId;
|
||||
var ckPlayer = new ckplayer({
|
||||
container: '#video',
|
||||
variable: 'player',
|
||||
flashplayer: false,
|
||||
video: {
|
||||
file: '',
|
||||
type: 'video/mp4'
|
||||
},
|
||||
});
|
||||
|
||||
// 初始化表格
|
||||
function initTable() {
|
||||
@ -183,7 +171,7 @@
|
||||
return rowData;
|
||||
}
|
||||
},
|
||||
{field: 'convertProgress', width: 100, title: '转换进度', align:'center', fixed: 'right',
|
||||
{field: 'convertProgress', width: 120, title: '转换进度', align:'center', fixed: 'right',
|
||||
templet: function(row) {
|
||||
if(row.convertStatus === 'UN_CONVERTED') {
|
||||
return '0%';
|
||||
@ -191,23 +179,27 @@
|
||||
if(row.convertStatus === 'CONVERTED') {
|
||||
return '100%';
|
||||
}
|
||||
return '<span id="progress_'+ row.fileId +'">0%</span>';
|
||||
return '<div class="layui-progress layui-progress-big layui-table-progress" lay-showPercent="true" lay-filter="progress_'+ row.fileId +'">\n' +
|
||||
' <div class="layui-progress-bar layui-table-progress-bar layui-bg-blue">' +
|
||||
' <span class="layui-progress-text">0%</span>' +
|
||||
' </div>\n' +
|
||||
'</div>';
|
||||
}
|
||||
},
|
||||
{field: 'convertStatus', width: 100, title: '转码状态', align:'center', fixed: 'right',
|
||||
{field: 'convertStatus', width: 120, title: '转码状态', align:'center', fixed: 'right',
|
||||
templet: function(row) {
|
||||
var rowData = row[this.field];
|
||||
if(typeof(rowData) === 'undefined' || rowData == null || rowData == '') {
|
||||
return '-';
|
||||
}
|
||||
if(rowData === 'UN_CONVERTED') {
|
||||
return '<button type="button" class="layui-btn layui-btn-xs" lay-event="convertEvent">转码</button>';
|
||||
return '<button type="button" class="layui-btn layui-btn-xs" lay-event="convertEvent">开始转码</button>';
|
||||
}
|
||||
if(rowData === 'CONVERTING') {
|
||||
return '转码中';
|
||||
return '排队转码中...';
|
||||
}
|
||||
if(rowData === 'CONVERTED') {
|
||||
return '已转码';
|
||||
return '转码完成';
|
||||
}
|
||||
return rowData;
|
||||
}
|
||||
@ -253,46 +245,33 @@
|
||||
});
|
||||
}
|
||||
// 删除
|
||||
function removeFile(isRemoveSource, ids) {
|
||||
var layIndex;
|
||||
top.restAjax.delete(top.restAjax.path('api/file/media/video/remove/{isRemoveSource}/{ids}', [isRemoveSource, ids]), {}, null, function (code, data) {
|
||||
top.dialog.msg(top.dataMessage.deleteSuccess, {time: 1000});
|
||||
reloadTable();
|
||||
}, function (code, data) {
|
||||
top.dialog.msg(data.msg);
|
||||
}, function () {
|
||||
layIndex = top.dialog.msg(top.dataMessage.deleting, {icon: 16, time: 0, shade: 0.3});
|
||||
}, function () {
|
||||
top.dialog.close(layIndex);
|
||||
});
|
||||
}
|
||||
function removeData(ids) {
|
||||
top.dialog.msg(top.dataMessage.delete, {
|
||||
time: 0,
|
||||
btn: [top.dataMessage.button.yes, '同时删除文件(不可逆)', top.dataMessage.button.no],
|
||||
shade: 0.3,
|
||||
btn1: function (index) {
|
||||
top.dialog.close(index);
|
||||
removeFile(0, ids);
|
||||
},
|
||||
btn2: function(index) {
|
||||
top.dialog.close(index);
|
||||
removeFile(1, ids);
|
||||
},
|
||||
btn3: function(index) {
|
||||
top.dialog.close(index);
|
||||
}
|
||||
top.dialog.confirm('确定删除(同时删除源文件)吗?', function(index) {
|
||||
top.dialog.close(index);
|
||||
var layIndex;
|
||||
top.restAjax.delete(top.restAjax.path('api/file/media/video/delete/{ids}', [ids]), {}, null, function (code, data) {
|
||||
top.dialog.msg(top.dataMessage.deleteSuccess, {time: 1000});
|
||||
reloadTable();
|
||||
}, function (code, data) {
|
||||
top.dialog.msg(data.msg);
|
||||
}, function () {
|
||||
layIndex = top.dialog.msg(top.dataMessage.deleting, {icon: 16, time: 0, shade: 0.3});
|
||||
}, function () {
|
||||
top.dialog.close(layIndex);
|
||||
});
|
||||
});
|
||||
}
|
||||
function sseConvertProgress() {
|
||||
var source = new EventSource(top.restAjax.path('api/file/media/video/get-convert-progress', []));
|
||||
source.addEventListener('message', function(e) {
|
||||
var data = JSON.parse(e.data);
|
||||
$('#progress_'+ data.fileId).text(parseInt(data.percent) + '%');
|
||||
if(data >= 100) {
|
||||
var fileId = data.fileId;
|
||||
var percent = data.percent;
|
||||
element.progress('progress_'+ fileId, percent +'%');
|
||||
if(percent >= 100) {
|
||||
setTimeout(function() {
|
||||
reloadTable();
|
||||
}, 2000);
|
||||
}, 5000);
|
||||
}
|
||||
});
|
||||
source.addEventListener('open', function(e) {}, false);
|
||||
@ -356,34 +335,60 @@
|
||||
var data = obj.data;
|
||||
var event = obj.event;
|
||||
if(event === 'searchFileEvent') {
|
||||
$('#videoBoxBackGround').show();
|
||||
if(ckPlayerFileId == data.fileId) {
|
||||
if(data.fileType != 'mp4') {
|
||||
top.dialog.msg('在线播放只支持MP4格式,请转码');
|
||||
return;
|
||||
}
|
||||
ckPlayer.newVideo({
|
||||
video: 'route/file/media/video/download/true/'+ data.fileId
|
||||
var maxWidth = parseInt($win.width() * 0.8);
|
||||
var maxHeight = parseInt($win.height() * 0.8);
|
||||
var videoWidth = data.videoWidth;
|
||||
var videoHeight = data.videoHeight;
|
||||
var widthHeightProportion = videoWidth / videoHeight;
|
||||
|
||||
var openWidth = videoWidth;
|
||||
var openHeight = videoHeight;
|
||||
if(videoWidth > videoHeight) {
|
||||
if(videoWidth > maxWidth) {
|
||||
openWidth = maxWidth;
|
||||
openHeight = maxWidth / widthHeightProportion;
|
||||
}
|
||||
if(videoHeight > maxHeight) {
|
||||
openHeight = maxHeight;
|
||||
openWidth = widthHeightProportion * openHeight;
|
||||
}
|
||||
} else {
|
||||
if(videoHeight > maxHeight) {
|
||||
openHeight = maxHeight;
|
||||
openWidth = widthHeightProportion * openHeight;
|
||||
}
|
||||
if(videoWidth > maxWidth) {
|
||||
openWidth = maxWidth;
|
||||
openHeight = maxWidth / widthHeightProportion;
|
||||
}
|
||||
}
|
||||
top.dialog.open({
|
||||
url: top.restAjax.path('route/file/media/video/preview?fileId={fileId}', [data.fileId]),
|
||||
title: false,
|
||||
width: parseInt(openWidth) +'px',
|
||||
height: parseInt(openHeight) +'px',
|
||||
onClose: function() {}
|
||||
});
|
||||
ckPlayerFileId = data.fileId;
|
||||
} else if(event === 'convertEvent') {
|
||||
top.dialog.confirm('转码成功后,源文件将删除,确定转码吗?', function(index) {
|
||||
top.dialog.close(index);
|
||||
top.restAjax.put(top.restAjax.path('api/file/media/video/convert/{fileId}', [data.fileId]), {}, null, function (code, data) {
|
||||
top.dialog.msg('正在转码...');
|
||||
top.dialog.msg('正在转码...', {time: 1000});
|
||||
reloadTable();
|
||||
}, function (code, data) {
|
||||
top.dialog.msg(data.msg);
|
||||
}, function () {
|
||||
layIndex = top.dialog.msg(top.dataMessage.deleting, {icon: 16, time: 0, shade: 0.3});
|
||||
layIndex = top.dialog.msg('正在提交...', {icon: 16, time: 0, shade: 0.3});
|
||||
}, function () {
|
||||
top.dialog.close(layIndex);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
$('.video-close-btn').on('click', function() {
|
||||
ckPlayer.videoPause();
|
||||
$('#videoBoxBackGround').hide();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
@ -0,0 +1,29 @@
|
||||
<!doctype html>
|
||||
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<base th:href="${#request.getContextPath() + '/'} ">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta charset="UTF-8"/>
|
||||
<style>
|
||||
html, body{margin: 0px; height: 0px; background-color: #000;}
|
||||
body { overflow: hidden;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="video"></div>
|
||||
<script src="assets/js/vendor/ckplayer/ckplayer/ckplayer.js"></script>
|
||||
<script type="text/javascript">
|
||||
var fileId = top.restAjax.params(window.location.href).fileId;
|
||||
var ckPlayer = new ckplayer({
|
||||
container: '#video',
|
||||
variable: 'ckPlayer',
|
||||
flashplayer: false,
|
||||
video: {
|
||||
file: top.restAjax.path('route/file/media/video/download/true/{fileId}', [fileId]),
|
||||
type: 'video/mp4'
|
||||
},
|
||||
autoplay: true
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -57,7 +57,7 @@
|
||||
var id = data.response;
|
||||
uploadFileArray.push(id);
|
||||
top.dialog.dialogData.uploadFileArray = uploadFileArray;
|
||||
if(uploadFileArray.length >= maxFileCount) {
|
||||
if(uploadFileArray.length >= $('#maxUploadCount').val()) {
|
||||
$('.btn-file').hide();
|
||||
}
|
||||
}).on('filesuccessremove', function(event, previewId, index) {
|
||||
|
@ -54,9 +54,9 @@ public class MediaTest {
|
||||
|
||||
@Test
|
||||
public void t3() {
|
||||
MediaManager.getInstance().setFFmpegPath("D:\\ffmpeg-4.4-full_build\\ffmpeg-4.4-full_build\\bin\\ffmpeg.exe");
|
||||
String sourcePath = "C:\\Users\\wenc0\\Desktop\\UploadFiles";
|
||||
String sourceName = "超体.rmvb";
|
||||
MediaManager.getInstance().setFFmpegPath("/Users/wanggeng/ffmpeg/ffmpeg");
|
||||
String sourcePath = "/Users/wanggeng/Desktop/UploadFiles";
|
||||
String sourceName = "720P_4000K_302139672.mp4";
|
||||
File sourceFile = new File(sourcePath + File.separator + sourceName);
|
||||
File outFile = new File(sourcePath + File.separator + sourceName + ".mp4");
|
||||
|
||||
@ -76,27 +76,29 @@ public class MediaTest {
|
||||
public void error(InputStream errorStream) throws Exception {
|
||||
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(errorStream));
|
||||
for (String line; (line = bufferedReader.readLine()) != null; ) {
|
||||
System.out.println(line);
|
||||
Matcher durationMatcher = durationPattern.matcher(line);
|
||||
Matcher timeMatcher = timePattern.matcher(line);
|
||||
if (durationMatcher.find()) {
|
||||
String duration = durationMatcher.group();
|
||||
System.out.println(duration);
|
||||
// System.out.println(duration);
|
||||
String durationTime = duration.replace("Duration: ", "");
|
||||
fullTime = durationToLongTime(durationTime);
|
||||
}
|
||||
if (timeMatcher.find()) {
|
||||
String time = timeMatcher.group();
|
||||
System.out.println(time);
|
||||
// System.out.println(time);
|
||||
String timeTime = time.replace("time=", "");
|
||||
currentTime = durationToLongTime(timeTime);
|
||||
}
|
||||
System.out.println(fullTime + "-" + currentTime);
|
||||
if (fullTime > 0L) {
|
||||
|
||||
System.out.println((currentTime / (fullTime * 1D) * 100D) + "%");
|
||||
}
|
||||
// System.out.println(fullTime + "-" + currentTime);
|
||||
// if (fullTime > 0L) {
|
||||
// System.out.println((currentTime / (fullTime * 1D) * 100D) + "%");
|
||||
// }
|
||||
}
|
||||
System.out.println("123123");
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user