文章目录
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和response
GetPlayInfoRequest request = new GetPlayInfoRequest();
GetPlayInfoResponse response = new GetPlayInfoResponse();
//向request中设置视频id
request.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获取视频凭证
@Test
void getAuth() throws Exception
{
//创建初始化对象
/**
* @param accessKeyId
* @param accessKeySecret
* */
DefaultAcsClient client = InitVideo.initVodClient("LTAI5tSiC9UUHdWQ7CjjRBVK", "RxUwtLh8NH2U1rDzVh4WTtMXHA3dk5");
//获取视频凭证的request和response
GetVideoPlayAuthRequest request = new GetVideoPlayAuthRequest();
GetVideoPlayAuthResponse response = new GetVideoPlayAuthResponse();
//向request设置视频id
request.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"); //请求视频点播服务的请求ID
if (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;
@Override
public void afterPropertiesSet() throws Exception {
ACCESS_KEY_ID=keyId;
ACCESS_KEY_SECRET=keySecret;
}
}
- controller
@RestController
@RequestMapping("/eduvod/video")
@CrossOrigin
@Api(tags = "视频相关的接口")
public class VodController {
@Autowired
private VodService vodService;
//上传视频到阿里云
@ApiOperation("上传视频到阿里云")
@PostMapping("/uploadVideo")
public Res uploadVideo(MultipartFile file)
{
//返回上传视频id
String 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 {
@Override
public 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"); //请求视频点播服务的请求ID
String 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设置视频id
request.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">
<li
v-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">
<li
v-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.videoId
this.video.videoOriginalName=file.name
},
handleUploadExceed(){
this.$message.warning('上传视频之前,删除已上传的视频')
},
//==========添加小节的操作=========================
openEditVideo(id)
{
//弹框
this.dialogVideoFormVisible=true
//调用接口
video1.getVideoById(id)
.then(response =>{
this.video=response.data.video
this.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
//设置章节id
this.video.chapterId=chapterId
},
//添加小节信息
addVideo()
{
//设置课程id
this.video.courseId=this.courseId
this.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()
else
this.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.chapter
this.chapter.ju=1
})
},
//弹出添加章节
openChapterDiglog(){
//弹框
this.dialogChapterFormVisible=true
//表单数据清空
this.chapter.title=' '
this.chapter.sort=0
this.chapter.ju=2
this.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(){
//设置课程id
this.chapter.courseId=this.courseId
chapter1.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>