文章目录
- 1. 开通阿里云视频点播服务
- 2.阿里云点播SDK实现
- 2.1上传视频到阿里云视频点播服务
- 2.2整合阿里云上传视频的接口
- 3. 课程信息的完善(为每个课程的小节添加视频)
- 3.1 添加上传视频
- 3.2删除视频信息
- 3.2.1后台
- 3.2.2前端
1. 开通阿里云视频点播服务
- 选择视频服务下面的视频点播
- 开通视频点播服务,选择按照流量计费
- 开通服务并进入控制台
查看文档&SDK有以下几个名称,下面进行解释
服务端:后端接口
客户端:浏览器、安卓、iOS
API:阿里云提供固定的地址,只需要调用这个固定地址,向这个地址传参数,实现功能
SDK:SDK对api进行封装,方便使用
2.阿里云点播SDK实现
将实现的功能:1. 获取视频播放地址(根据id获得) 2. 获得视频播放凭证(根据视频的id获取)3. 上传视频到阿里云视频点播服务
注意:因为上传视频时可以进行加密,而加密之后使用加密地址是不能进行播放的,在数据库中存储的不是地址而是视频的id
2.1上传视频到阿里云视频点播服务
- 在service模块下创建子模块service_vdo引入相关依赖
<dependencies><dependency><groupId>com.aliyun</groupId><artifactId>aliyun-java-sdk-core</artifactId></dependency><dependency><groupId>com.aliyun.oss</groupId><artifactId>aliyun-sdk-oss</artifactId></dependency><dependency><groupId>com.aliyun</groupId><artifactId>aliyun-java-sdk-vod</artifactId></dependency>
<!-- <dependency>-->
<!-- <groupId>com.aliyun</groupId>-->
<!-- <artifactId>aliyun-sdk-vod-upload</artifactId>-->
<!-- <version>1.4.12</version>-->
<!-- </dependency>--><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId></dependency><dependency><groupId>org.json</groupId><artifactId>json</artifactId></dependency><dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId></dependency><dependency><groupId>joda-time</groupId><artifactId>joda-time</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies>
- 初始化操作,创建InitVideo对象
public class InitVideo {public static DefaultAcsClient initVodClient(String accessKeyId, String accessKeySecret) throws ClientException {String regionId = "cn-shanghai"; // 点播服务接入区域DefaultProfile profile = DefaultProfile.getProfile(regionId, accessKeyId, accessKeySecret);DefaultAcsClient client = new DefaultAcsClient(profile);return client;}
}
- 测试通过视频id获取视频地址
public static void main(String[] args) throws Exception{//根据视频id获取视频播放地址//创建初始化对象/*** @param accessKeyId* @param accessKeySecret* */DefaultAcsClient client = InitVideo.initVodClient("LTAI5tSiC9UUHdWQ7CjjRBVK", "RxUwtLh8NH2U1rDzVh4WTtMXHA3dk5");//获取视频地址的request和responseGetPlayInfoRequest request = new GetPlayInfoRequest();GetPlayInfoResponse response = new GetPlayInfoResponse();//向request中设置视频idrequest.setVideoId("b13d3f216fa84c6390bee467c9896c69");//调用初始化对象里面的方法,调用request获取数据response= client.getAcsResponse(request);List<GetPlayInfoResponse.PlayInfo> playInfoList = response.getPlayInfoList();for(GetPlayInfoResponse.PlayInfo playInfo:playInfoList){System.out.println("url"+playInfo.getPlayURL());}//base信息System.out.println("base"+response.getVideoBase().getTitle());}
- 测试根据视频id获取视频凭证
//根据id获取视频凭证@Testvoid getAuth() throws Exception{//创建初始化对象/*** @param accessKeyId* @param accessKeySecret* */DefaultAcsClient client = InitVideo.initVodClient("LTAI5tSiC9UUHdWQ7CjjRBVK", "RxUwtLh8NH2U1rDzVh4WTtMXHA3dk5");//获取视频凭证的request和responseGetVideoPlayAuthRequest request = new GetVideoPlayAuthRequest();GetVideoPlayAuthResponse response = new GetVideoPlayAuthResponse();//向request设置视频idrequest.setVideoId("b13d3f216fa84c6390bee467c9896c69");//调用初始化的方法得到凭证response = client.getAcsResponse(request);System.out.println("playAuth"+ response.getPlayAuth());}
- 本地视频上传
/*** 本地文件上传* @param accessKeyId* @param accessKeySecret* @param title 上传文件后的命名* @param fileName 本地文件的路径和名称*/private static void testUploadVideo(String accessKeyId, String accessKeySecret, String title, String fileName) {UploadVideoRequest request = new UploadVideoRequest(accessKeyId, accessKeySecret, title, fileName);/* 可指定分片上传时每个分片的大小,默认为2M字节 */request.setPartSize(2 * 1024 * 1024L);/* 可指定分片上传时的并发线程数,默认为1,(注:该配置会占用服务器CPU资源,需根据服务器情况指定)*/request.setTaskNum(1);/* 是否开启断点续传, 默认断点续传功能关闭。当网络不稳定或者程序崩溃时,再次发起相同上传请求,可以继续未完成的上传任务,适用于超时3000秒仍不能上传完成的大文件。注意: 断点续传开启后,会在上传过程中将上传位置写入本地磁盘文件,影响文件上传速度,请您根据实际情况选择是否开启*///request.setEnableCheckpoint(false);/* OSS慢请求日志打印超时时间,是指每个分片上传时间超过该阈值时会打印debug日志,如果想屏蔽此日志,请调整该阈值。单位: 毫秒,默认为300000毫秒*///request.setSlowRequestsThreshold(300000L);/* 可指定每个分片慢请求时打印日志的时间阈值,默认为300s*///request.setSlowRequestsThreshold(300000L);/* 是否显示水印(可选),指定模板组ID时,根据模板组配置确定是否显示水印*///request.setIsShowWaterMark(true);/* 自定义消息回调设置(可选),参数说明参考文档 https://help.aliyun.com/document_detail/86952.html#UserData */// request.setUserData("{\"Extend\":{\"test\":\"www\",\"localId\":\"xxxx\"},\"MessageCallback\":{\"CallbackURL\":\"http://test.test.com\"}}");/* 视频分类ID(可选) *///request.setCateId(0);/* 视频标签,多个用逗号分隔(可选) *///request.setTags("标签1,标签2");/* 视频描述(可选) *///request.setDescription("视频描述");/* 封面图片(可选) *///request.setCoverURL("http://cover.sample.com/sample.jpg");/* 模板组ID(可选) *///request.setTemplateGroupId("8c4792cbc8694e7084fd5330e56a33d");/* 工作流ID(可选) *///request.setWorkflowId("d4430d07361f0*be1339577859b0177b");/* 存储区域(可选) *///request.setStorageLocation("in-201703232118266-5sejdln9o.oss-cn-shanghai.aliyuncs.com");/* 开启默认上传进度回调 *///request.setPrintProgress(false);/* 设置自定义上传进度回调 (必须继承 VoDProgressListener) *///request.setProgressListener(new PutObjectProgressListener());/* 设置您实现的生成STS信息的接口实现类*/// request.setVoDRefreshSTSTokenListener(new RefreshSTSTokenImpl());/* 设置应用ID*///request.setAppId("app-1000000");/* 点播服务接入点 *///request.setApiRegionId("cn-shanghai");/* ECS部署区域*/// request.setEcsRegionId("cn-shanghai");UploadVideoImpl uploader = new UploadVideoImpl();UploadVideoResponse response = uploader.uploadVideo(request);System.out.println(response);System.out.print("RequestId=" + response.getRequestId() + "\n"); //请求视频点播服务的请求IDif (response.isSuccess()) {System.out.print("VideoId=" + response.getVideoId() + "\n");} else {/* 如果设置回调URL无效,不影响视频上传,可以返回VideoId同时会返回错误码。其他情况上传失败时,VideoId为空,此时需要根据返回错误码分析具体错误原因 */System.out.print("VideoId=" + response.getVideoId() + "\n");System.out.print("ErrorCode=" + response.getCode() + "\n");System.out.print("ErrorMessage=" + response.getMessage() + "\n");}}
2.2整合阿里云上传视频的接口
- application.properties
# 服务端口
server.port=8003
# 服务名
spring.application.name=service-vod# 环境设置:dev、test、prod
spring.profiles.active=dev#阿里云 vod
#不同的服务器,地址不同
aliyun.vod.file.keyid=your accessKeyId
aliyun.vod.file.keysecret=your accessKeySecret
# 最大上传单个文件大小:默认1M
spring.servlet.multipart.max-file-size=1024MB
# 最大置总上传的数据大小 :默认10M
spring.servlet.multipart.max-request-size=1024MB
- 主启动
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class ServiceVdoApplication {public static void main(String[] args) {SpringApplication.run(ServiceVdoApplication.class, args);}}
- 创建常量类
@Component
public class VideoUtils implements InitializingBean {@Value("${aliyun.vod.file.keyid}")private String keyId;@Value("${aliyun.vod.file.keysecret}")private String keySecret;public static String ACCESS_KEY_ID;public static String ACCESS_KEY_SECRET;@Overridepublic void afterPropertiesSet() throws Exception {ACCESS_KEY_ID=keyId;ACCESS_KEY_SECRET=keySecret;}
}
- controller
@RestController
@RequestMapping("/eduvod/video")
@CrossOrigin
@Api(tags = "视频相关的接口")
public class VodController {@Autowiredprivate VodService vodService;//上传视频到阿里云@ApiOperation("上传视频到阿里云")@PostMapping("/uploadVideo")public Res uploadVideo(MultipartFile file){//返回上传视频idString id =vodService.uploadVideo(file);HashMap<String, Object> hashMap = new HashMap<>();hashMap.put("id",id);return Res.ok().data(hashMap);}}
- service
@Service
public class VodServiceImpl implements VodService {@Overridepublic String uploadVideo(MultipartFile file) {try {//title 上传后显示的名称String title = "";//filename 上传视频的原始名称String fileName = file.getOriginalFilename();title=fileName.substring(0,fileName.lastIndexOf("."));//inputStream 上传流InputStream inputStream = file.getInputStream();UploadStreamRequest request = new UploadStreamRequest(VideoUtils.ACCESS_KEY_ID, VideoUtils.ACCESS_KEY_SECRET, title, fileName, inputStream);UploadVideoImpl uploader = new UploadVideoImpl();UploadStreamResponse response = uploader.uploadStream(request);System.out.print("RequestId=" + response.getRequestId() + "\n"); //请求视频点播服务的请求IDString videoId = null;if (response.isSuccess()) {videoId = response.getVideoId();} else { //如果设置回调URL无效,不影响视频上传,可以返回VideoId同时会返回错误码。其他情况上传失败时,VideoId为空,此时需要根据返回错误码分析具体错误原因videoId = response.getVideoId();}return videoId;}catch (Exception e){e.printStackTrace();return null;}}
}
3. 课程信息的完善(为每个课程的小节添加视频)
3.1 添加上传视频
在edu\cousre\chapter.vue中添加下面的前端信息
<el-form-item label="上传视频"><el-upload:on-success="handleVodUploadSuccess" :on-remove="handleVodRemove":before-remove="beforeVodRemove":on-exceed="handleUploadExceed":file-list="fileList":action="BASE_API+'/eduvideo/video/uploadVideo'":limit="1"class="upload-demo"><el-button size="small" type="primary">上传视频</el-button><el-tooltip placement="right-end"><div slot="content">最大支持1G,<br>支持3GP、ASF、AVI、DAT、DV、FLV、F4V、<br>GIF、M2T、M4V、MJ2、MJPEG、MKV、MOV、MP4、<br>MPE、MPG、MPEG、MTS、OGG、QT、RM、RMVB、<br>SWF、TS、VOB、WMV、WEBM 等视频格式上传</div><i class="el-icon-question"/></el-tooltip></el-upload>
</el-form-item>
并在vue的data中添加上传文件列表和接口API地址
return {saveBtnDisabled: false, // 保存按钮是否禁用chapterVideoList:[],courseId:' ',dialogChapterFormVisible:false,//章节弹框dialogVideoFormVisible:false,//小节弹框fileList:[],//上传文件列表BASE_API:process.env.BASE_API,//接口API地址// BASE_API:'"localhost:8003"',chapter:{//章节信息title:'',sort:0,ju:0},video:{title: '',sort: 0,free: 0,videoSourceId: '',ju:0,id:''}}}
视频上传成功是调用的方法
//上传视频成功调用的方法handleVodUploadSuccess(response,file,fileList){this.video.videoSourceId=response.data.videoId},handleUploadExceed(){this.$message.warning('上传视频之前,删除已上传的视频')},
3.2删除视频信息
3.2.1后台
- 创建一个视频点播工具类
public class InitVideo {public static DefaultAcsClient initVodClient(String accessKeyId, String accessKeySecret) throws ClientException {String regionId = "cn-shanghai"; // 点播服务接入区域DefaultProfile profile = DefaultProfile.getProfile(regionId, accessKeyId, accessKeySecret);DefaultAcsClient client = new DefaultAcsClient(profile);return client;}
}
- 在VodController下创建删除视频的接口
//根据id删除阿里云视频public Res removeVideo(@PathVariable String id){try{//初始化对象DefaultAcsClient client = InitVideo.initVodClient(VideoUtils.ACCESS_KEY_ID, VideoUtils.ACCESS_KEY_SECRET);//创建删除视频request对象DeleteVideoRequest request = new DeleteVideoRequest();//向request设置视频idrequest.setVideoIds(id);//调用初始化对象的方法实现删除client.getAcsResponse(request);return Res.ok();}catch (Exception e){e.printStackTrace();throw new GuliException(20001,"视频删除失败");}}
3.2.2前端
- 定义api接口
在api/edu/video.js中加上路由信息
//通过id删除视频信息deleteAliyunVideo(id){return request({url:"/eduvideo/video/deleteVideo/"+id,method:"delete"})}
- 页面调用api
在edu/course/chapter.vue中加入调用方法
//确定调用的方法handleVodRemove(file,fileList){//调用删除视频的方法video1.deleteAliyunVideo(this.video.videoSourceId).then(response=>{this.$message({type:"success",message:"删除视频成功"});//把文件列表情况this.fileList=[]})},
chapter.vue的总体代码
<template><div class="app-container"><h2 style="text-align: center;">发布新课程</h2><el-steps :active="2" process-status="wait" align-center style="margin-bottom: 40px;"><el-step title="填写课程基本信息"/><el-step title="创建课程大纲"/><el-step title="提交审核"/></el-steps>
<el-button type="text" @click="openChapterDiglog()">添加章节</el-button><!-- 章节 -->
<!-- 章节 -->
<ul class="chanpterList"><liv-for="chapter in chapterVideoList":key="chapter.id"><p>{{ chapter.title }}<span class="acts"><el-button type="text" @click="openVideo(chapter.id)">添加课时</el-button><el-button style="" type="text" @click="openEditChapter(chapter.id)">编辑</el-button><el-button type="text" @click="removechapter(chapter.id)">删除</el-button></span></p><!-- 视频 --><ul class="chanpterList videoList"><liv-for="video in chapter.children":key="video.id"><p>{{ video.title }}<span class="acts"><el-button type="text" @click="openEditVideo(video.id)">编辑</el-button><el-button type="text" @click="removeVideo(video.id)">删除</el-button></span></p></li></ul></li>
</ul>
<div><el-button @click="previous">上一步</el-button><el-button :disabled="saveBtnDisabled" type="primary" @click="next">下一步</el-button>
</div><!-- 添加和修改章节表单 -->
<el-dialog :visible.sync="dialogChapterFormVisible" title="添加章节"><el-form :model="chapter" label-width="120px"><el-form-item label="章节标题"><el-input v-model="chapter.title"/></el-form-item><el-form-item label="章节排序"><el-input-number v-model="chapter.sort" :min="0" controls-position="right"/></el-form-item></el-form><div slot="footer" class="dialog-footer"><el-button @click="dialogChapterFormVisible = false">取 消</el-button><el-button type="primary" @click="saveOrUpdate">确 定</el-button></div>
</el-dialog>
<!-- 添加和修改课时表单 -->
<el-dialog :visible.sync="dialogVideoFormVisible" title="添加课时"><el-form :model="video" label-width="120px"><el-form-item label="课时标题"><el-input v-model="video.title"/></el-form-item><el-form-item label="课时排序"><el-input-number v-model="video.sort" :min="0" controls-position="right"/></el-form-item><el-form-item label="是否免费"><el-radio-group v-model="video.free"><el-radio :label="true">免费</el-radio><el-radio :label="false">默认</el-radio></el-radio-group></el-form-item><el-form-item label="上传视频"><el-upload:on-success="handleVodUploadSuccess" :on-remove="handleVodRemove":before-remove="beforeVodRemove":on-exceed="handleUploadExceed":file-list="fileList":action="BASE_API+'/eduvideo/video/uploadVideo'":limit="1"class="upload-demo"><el-button size="small" type="primary">上传视频</el-button><el-tooltip placement="right-end"><div slot="content">最大支持1G,<br>支持3GP、ASF、AVI、DAT、DV、FLV、F4V、<br>GIF、M2T、M4V、MJ2、MJPEG、MKV、MOV、MP4、<br>MPE、MPG、MPEG、MTS、OGG、QT、RM、RMVB、<br>SWF、TS、VOB、WMV、WEBM 等视频格式上传</div><i class="el-icon-question"/></el-tooltip></el-upload>
</el-form-item></el-form><div slot="footer" class="dialog-footer"><el-button @click="dialogVideoFormVisible = false">取 消</el-button><el-button :disabled="saveVideoBtnDisabled" type="primary" @click="saveOrUpdateVideo">确 定</el-button></div>
</el-dialog><!-- <el-form label-width="120px"><el-form-item><el-button @click="previous">上一步</el-button><el-button :disabled="saveBtnDisabled" type="primary" @click="next">下一步</el-button></el-form-item></el-form> --></div>
</template>
<script>
import chapter1 from '@/api/edu/chapter.js'
import video1 from '@/api/edu/video.js'
export default {data() {return {saveBtnDisabled: false, // 保存按钮是否禁用chapterVideoList:[],courseId:' ',dialogChapterFormVisible:false,//章节弹框dialogVideoFormVisible:false,//小节弹框fileList:[],//上传文件列表BASE_API:process.env.BASE_API,//接口API地址// BASE_API:'"localhost:8003"',chapter:{//章节信息title:'',sort:0,ju:0},video:{title: '',sort: 0,free: 0,videoSourceId: '',videoOriginalName:'',ju:0,id:''}}},created() {if(this.$route.params && this.$route.params.id){this.courseId = this.$route.params.id//根据id查询所有的章节和小节this.getChapterVideos()}},methods: {//点击X调用的方法beforeVodRemove(){return this.$confirm('确定移除${file.name}?')},//点击确定调用的方法handleVodRemove(file,fileList){//调用删除视频的方法video1.deleteAliyunVideo(this.video.videoSourceId).then(response=>{this.$message({type:"success",message:"删除视频成功"});//把文件列表情况this.fileList=[]//把vide中视频id和视频名称id情况this.video.videoSourceId=''this.video.videoOriginalName=''})},//上传视频成功调用的方法handleVodUploadSuccess(response,file,fileList){this.video.videoSourceId=response.data.videoIdthis.video.videoOriginalName=file.name},handleUploadExceed(){this.$message.warning('上传视频之前,删除已上传的视频')},//==========添加小节的操作=========================openEditVideo(id){//弹框this.dialogVideoFormVisible=true//调用接口video1.getVideoById(id).then(response =>{this.video=response.data.videothis.video.ju=1})// this.dialogChapterFormVisible=true},//添加小节的弹框openVideo(chapterId){this.video.title='',this.video.sort=0,this.video.free==0,this.video.videoSourceId=''//弹框this.dialogVideoFormVisible=true//设置章节idthis.video.chapterId=chapterId},//添加小节信息addVideo(){//设置课程idthis.video.courseId=this.courseIdthis.video.id=""video1.addVideo(this.video).then(response=>{//关闭弹框this.dialogVideoFormVisible=false//提示信息this.$message({type:"success",message:"添加小节成功"});this.getChapterVideos() })this.getChapterVideos()},updateVideo(){video1.updateVideo(this.video).then(response=>{//关闭弹框this.dialogVideoFormVisible=false//提示信息this.$message({type:"success",message:"修改小节成功"});this.getChapterVideos() })},saveOrUpdateVideo(){if(this.video.ju==1)this.updateVideo()elsethis.addVideo()this.video.ju=0},//删除小节removeVideo(id) {this.$confirm('此操作将永久删除该小节的记录, 是否继续?', '提示', {confirmButtonText: '确定',cancelButtonText: '取消',type: 'warning'}).then(() => { //点击确定执行then方法//调用删除方法video1.deleteVideo(id).then(response=>{ //删除成功this.$message({type: 'success',message: '删除成功!'});this.getChapterVideos()})//回到列表页面this.getChapterVideos()})}, //==========添加章节的操作====================//修改章节弹框数据回显openEditChapter(chapterId){//弹框this.dialogChapterFormVisible=true//调用接口chapter1.getChapterById(chapterId).then(response=>{this.chapter=response.data.chapterthis.chapter.ju=1})},//弹出添加章节openChapterDiglog(){//弹框this.dialogChapterFormVisible=true//表单数据清空this.chapter.title=' 'this.chapter.sort=0this.chapter.ju=2this.chapter.id=' '},saveOrUpdate(){if(this.chapter.ju==2){this.addChapter()}else{this.updateChapter()}},//修改章节信息updateChapter(){chapter1.updateChapter(this.chapter).then(response=>{//关闭弹框this.dialogChapterFormVisible=false//提示this.$message({type: "success",message:"修改章节成功"})//刷新页面this.getChapterVideos()})},//添加章节信息addChapter(){//设置课程idthis.chapter.courseId=this.courseIdchapter1.addChapter(this.chapter).then(response=>{//关闭弹框this.dialogChapterFormVisible=false//提示this.$message({type: "success",message:"添加章节成功"})//刷新页面this.getChapterVideos()})},//根据ID查询章节和小结getChapterVideos() {chapter1.getChapterVideo(this.courseId).then(response=>{this.chapterVideoList=response.data.chapterList})},//删除章节removechapter(chapterId){this.$confirm('此操作将永久删除该章节的记录, 是否继续?', '提示', {confirmButtonText: '确定',cancelButtonText: '取消',type: 'warning'}).then(() => { //点击确定执行then方法//调用删除方法chapter1.deleteChapter(chapterId).then(response=>{ //删除成功this.$message({type: 'success',message: '删除成功!'});this.getChapterVideos()})//回到列表页面this.getChapterVideos()})},previous() {console.log('previous')this.$router.push({ path: '/course/info/'+this.courseId })},next() {console.log('next')this.$router.push({ path: '/course/publish/'+this.courseId})}}
}
</script>
<style scoped>
.chanpterList{position: relative;list-style: none;margin: 0;padding: 0;
}
.chanpterList li{position: relative;
}
.chanpterList p{float: left;font-size: 20px;margin: 10px 0;padding: 10px;height: 70px;line-height: 50px;width: 100%;border: 1px solid #DDD;
}
.chanpterList .acts {float: right;font-size: 14px;
}
.videoList{padding-left: 50px;
}
.videoList p{float: left;font-size: 14px;margin: 10px 0;padding: 10px;height: 50px;line-height: 30px;width: 100%;border: 1px dotted #DDD;
}
</style>