01-课程页面静态效果整合
一、列表页面
创建 pages/course/index.vue
二、详情页面
创建 pages/course/_id.vue
02-课程列表页面
一、课程后端接口
1、课程列表
(1)课程列表vo类(条件查询需要的对象)
@ApiModel(value = "课程查询对象", description = "课程查询对象封装")
@Data
public class CourseFrontVo {
@ApiModelProperty(value = "课程名称")
private String title;
@ApiModelProperty(value = "讲师id")
private String teacherId;
@ApiModelProperty(value = "一级类别id")
private String subjectParentId;
@ApiModelProperty(value = "二级类别id")
private String subjectId;
@ApiModelProperty(value = "销量排序")
private String buyCountSort;
@ApiModelProperty(value = "最新时间排序")
private String gmtCreateSort;
@ApiModelProperty(value = "价格排序")
private String priceSort;
}
(2)课程列表controller
@RestController
@CrossOrigin
@RequestMapping("/eduservice/coursefront")
public class CourseFrontController {
@Autowired
private EduCourseService courseService;
//1 条件查询带分页查询课程
@ApiOperation(value = "分页课程列表")
@PostMapping("pageCourseCondition/{current}/{limit}")
public R pageCourseCondition(@PathVariable long limit,
@PathVariable long current,
@RequestBody(required = false) CourseFrontVo courseFrontVo){
//@RequestBody(required = false)使用RequestBody必须是post提交,不添加required = false必须要有值,添加后表示可以没有值
Page pageParam = new Page(current,limit);
Map map = courseService.pageListWeb(pageParam, courseFrontVo);
return R.ok().data(map);
}
}
//@RequestBody(required = false)使用RequestBody必须是post提交,不添加required = false必须要有值,添加后可以没有值
(3)课程列表service
//1 条件查询带分页查询课程
@Override
public Map pageListWeb(Page pageParam, CourseFrontVo courseFrontVo) {
QueryWrapper queryWrapper = new QueryWrapper<>();
//判断条件值是否为空,不为空拼接
if (!StringUtils.isEmpty(courseFrontVo.getSubjectParentId())){//一级分类
queryWrapper.eq("subject_parent_id",courseFrontVo.getSubjectParentId());
}
if (!StringUtils.isEmpty(courseFrontVo.getSubjectId())){//二级分类
queryWrapper.eq("subject_id",courseFrontVo.getSubjectId());
}
if (!StringUtils.isEmpty(courseFrontVo.getBuyCountSort())){//销售数量
queryWrapper.orderByDesc("buy_count",courseFrontVo.getBuyCountSort());
}
if (!StringUtils.isEmpty(courseFrontVo.getGmtCreateSort())){//创建时间
queryWrapper.orderByDesc("gmt_create",courseFrontVo.getGmtCreateSort());
}
if (!StringUtils.isEmpty(courseFrontVo.getPriceSort())){//价格
queryWrapper.orderByDesc("price",courseFrontVo.getPriceSort());
}
baseMapper.selectPage(pageParam, queryWrapper);
List records = pageParam.getRecords();
long pages = pageParam.getPages();
long current = pageParam.getCurrent();
long total = pageParam.getTotal();
long size = pageParam.getSize();
boolean hasNext = pageParam.hasNext(); //下一页
boolean hasPrevious = pageParam.hasPrevious(); //上一页
Map map = new HashMap();
map.put("items",records);
map.put("current", current);
map.put("pages", pages);
map.put("size", size);
map.put("total", total);
map.put("hasNext", hasNext);
map.put("hasPrevious", hasPrevious);
return map;
}
二、课程列表前端
1、定义api
api/course.js
import request from '@/utils/request'
export default {
//1 条件查询带分页查询课程
getCourseList(current,limit,searchObj) {
return request({
url: `/eduservice/coursefront/pageCourseCondition/${current}/${limit}`,
method: 'post',
data:searchObj
})
},
//查询所有分类的方法
getAllSubject(){
return request({
url: `/eduservice/edu-subject/getAllSubject`,
method: 'get'
})
}
}
2、页面调用接口
pages/course/index.vue
import courseApi from '@/api/course'
export default {
data(){
return{
page:1,
data:{},
subjectNestedList: [], // 一级分类列表
subSubjectList: [], // 二级分类列表
searchObj: {}, // 查询表单对象
oneIndex:-1,
twoIndex:-1,
buyCountSort:"",
gmtCreateSort:"",
priceSort:""
}
},
created(){
//获取课程列表
this.initCourseFirst()
//获取分类
this.initSubject()
},
methods:{
//1 查询第一页数据
initCourseFirst(){
courseApi.getCourseList(1,8,this.searchObj).then(response=>{
this.data = response.data.data
})
},
//2 查询所有一级分类
initSubject(){
courseApi.getAllSubject().then(response=>{
this.subjectNestedList = response.data.data.list
})
},
//分页切换方法
gotoPage(page){
courseApi.getCourseList(page,8,this.searchObj).then(response=>{
this.data = response.data.data
})
},
//4 点击某个一级分类,查询对应的二级分类
searchOne(subjectParentId,index){
//把传递index值赋值给oneIndex,为了active样式生效
this.oneIndex = index
//查询一级分类,二级分类不需要值
this.twoIndex = -1
this.searchObj.subjectId = "";
this.subSubjectList = [];
//把一级分类点击id值,赋值给searchObj
this.searchObj.subjectParentId = subjectParentId
//点击某个一级分类进行条件查询
this.gotoPage(1) //或者gotoPage(this.page) this.page的默认值为1
//拿着点击一级分类id和所有一级分类id进行比较,
//如果id相同,从一级分类里面获取对应的二级分类
for(let i =0;i
//获取每个一级分类
var oneSubject = this.subjectNestedList[i]
//如果id值相同
if(subjectParentId == oneSubject.id){
this.subSubjectList = oneSubject.children
}
}
},
}
};
完善当点击一级分类时,页面中显示的是该一级分类下的所有课程:
完善点击添一级分类显示样式:
.active {
background: #bdbdbd;
}
.hide {
display: none;
}
.show {
display: block;
}
如果oneIndex==index则添加样式:
结果:
完善点击某个二级分类实现查询
结果:
完善排序方式显示
销量
↓
最新
↓
价格
↓
//6 根据销量进行排序
searchBuyCount(){
//设置对应变量值,为了样式生效
this.buyCountSort = "1"
this.gmtCreateSort = ""
this.priceSort = ""
//把值赋值到search0bj
this.searchObj.buyCountSort = this.buyCountSort
this.searchObj.gmtCreateSort = this.gmtCreateSort
this.searchObj.priceSort = this.priceSort
//调用方法查询
this.gotoPage(1)
},
//7 最新排序
searchGmtCreate(){
//设置对应变量值,为了样式生效
this.buyCountSort = ""
this.gmtCreateSort = "1"
this.priceSort = ""
//把值赋值到search0bj
this.searchObj.buyCountSort = this.buyCountSort
this.searchObj.gmtCreateSort = this.gmtCreateSort
this.searchObj.priceSort = this.priceSort
//调用方法查询
this.gotoPage(1)
},
//8 价格排序
searchPrice(){
//设置对应变量值,为了样式生效
this.buyCountSort = ""
this.gmtCreateSort = ""
this.priceSort = "1"
//把值赋值到search0bj
this.searchObj.buyCountSort = this.buyCountSort
this.searchObj.gmtCreateSort = this.gmtCreateSort
this.searchObj.priceSort = this.priceSort
//调用方法查询
this.gotoPage(1)
}
三、课程列表渲染
1、课程类别显示
课程类别
2、无数据提示
添加:v-if="data.total==0"
没有相关数据,小编正在努力整理中...
3、列表
0" class="comm-course-list">
4、分页页面渲染
03-课程详情页
一、vo对象的定义
在项目中很多时候需要把model转换成dto用于网站信息的展示,按前端的需要传递对象的数据,保证model对外是隐私的,例如密码之类的属性能很好地避免暴露在外,同时也会减小数据传输的体积。
CourseWebInfoVo.java(用于封装课程的详情信息)
@ApiModel(value="课程信息", description="网站课程详情页需要的相关字段")
@Data
public class CourseWebInfoVo {
private String id;
@ApiModelProperty(value = "课程标题")
private String title;
@ApiModelProperty(value = "课程销售价格,设置为0则可免费观看")
private BigDecimal price;
@ApiModelProperty(value = "总课时")
private Integer lessonNum;
@ApiModelProperty(value = "课程封面图片路径")
private String cover;
@ApiModelProperty(value = "销售数量")
private Long buyCount;
@ApiModelProperty(value = "浏览数量")
private Long viewCount;
@ApiModelProperty(value = "课程简介")
private String description;
@ApiModelProperty(value = "讲师ID")
private String teacherId;
@ApiModelProperty(value = "讲师姓名")
private String teacherName;
@ApiModelProperty(value = "讲师资历,一句话说明讲师")
private String intro;
@ApiModelProperty(value = "讲师头像")
private String avatar;
@ApiModelProperty(value = "课程一级类别ID")
private String subjectLevelOneId;
@ApiModelProperty(value = "类别一级名称")
private String subjectLevelOne;
@ApiModelProperty(value = "课程二级类别ID")
private String subjectLevelTwoId;
@ApiModelProperty(value = "类别二级名称")
private String subjectLevelTwo;
}
二、课程和讲师信息的获取
1、CourseFrontController
//根据课程id,编写sq1语句查询课程信息
@ApiOperation(value = "根据ID查询课程")
@GetMapping("getFrontCourseInfo/{courseId}")
public R getFrontCourseInfo(@PathVariable String courseId){
//根据课程id,编写sql语句查询课程信息
CourseWebInfoVo courseWebInfoVo = courseService.getBaseCourseInfo(courseId);
//根据课程id查询章节和小节
List chapterVideoByCourseId = chapterService.getChapterVideoByCourseId(courseId);
return R.ok().data("courseWebVo",courseWebInfoVo).data("chapterVideoList",chapterVideoByCourseId);
}
2、EduCourseService
//根据课程id,编写sq1语句查询课程信息
CourseWebInfoVo getBaseCourseInfo(String courseId);
3、EduCourseServiceImpl
//根据课程id,编写sq1语句查询课程信息
@Override
public CourseWebInfoVo getBaseCourseInfo(String courseId) {
return baseMapper.getBaseCourseInfo(courseId);
}
4、Mapper中关联查询课程和讲师信息
EduCourseMapper.java
//根据课程id,编写sq1语句查询课程信息
CourseWebInfoVo getBaseCourseInfo(String courseId);
EduCourseMapper.xml
SELECT ec.id,ec.title,ec.price,ec.lesson_num AS lessonNum,ec.cover,
ec.buy_count AS buyCount,ec.view_count AS viewCount,
ecd.description,
et.id AS teacherId,et.name AS teacherName,et.intro,et.avatar,
es1.id AS subjectLevelOneId,es1.title AS subjectLevelOne,
es2.id AS subjectLevelTwoId,es2.title AS subjectLevelTwo
FROM edu_course ec LEFT OUTER JOIN edu_course_description ecd ON ec.id=ecd.id
LEFT OUTER JOIN edu_teacher et ON ec.teacher_id=et.id
LEFT OUTER JOIN edu_subject es1 ON ec.subject_parent_id=es1.id
LEFT OUTER JOIN edu_subject es2 ON ec.subject_id=es2.id
WHERE ec.id=#{courseId}
因为现在方法中只有一个参数,当只有一个参数时,WHEREec.id=#{courseId}中courseId可以随便写。
默认xml文件不加载问题:
三、前端js
1、api/course.js
// 课程详情的方法
getCourseInfo(courseId){
return request({
url: `/eduservice/coursefront/getFrontCourseInfo/${courseId}`,
method: 'get'
})
}
2、pages/course/_id.vue
import courseApi from '@/api/course'
export default {
asyncData({ params, error }) {
return courseApi.getCourseInfo(params.id).then(response=>{
return {
courseWebVo:response.data.data.courseWebVo,
chapterVideoList:response.data.data.chapterVideoList
}
})
}
};
四、页面模板
pages/course/_id.vue
1、课程所属分类
首页
\
课程列表
\
{{courseWebVo.subjectLevelOne}}
\
{{courseWebVo.subjectLevelTwo}}
2、课程基本信息
{{courseWebVo.title}}
价格:
¥{{courseWebVo.price}}
主讲: {{courseWebVo.teacherName}}
收藏
立即观看
购买数
{{courseWebVo.buyCount}}
课时数
{{courseWebVo.lessonNum}}
浏览数
{{courseWebVo.viewCount}}
3、课程详情介绍
在后端添加课程简介时可以添加样式,但是 {{courseWebVo.description}}只是进行了原样输出。
{{courseWebVo.description}}
4、课程大纲
课程大纲
{{chapter.title}}
免费试听
{{video.title}}
5、主讲讲师
最终显示结果:
04-视频播放测试
一、获取播放地址播放
获取播放地址
参考文档:https://help.aliyun.com/document_detail/61064.html
前面的 03-使用服务端SDK 介绍了如何获取非加密视频的播放地址。直接使用03节的例子获取加密视频播放地址会返回如下错误信息
Currently only the AliyunVoDEncryption stream exists, you must use the Aliyun player to play or set the value of ResultType to Multiple.
目前只有AliyunVoDEncryption流存在,您必须使用Aliyun player来播放或将ResultType的值设置为Multiple。
因此在testGetPlayInfo测试方法中添加 ResultType 参数,并设置为true
privateParams.put("ResultType","Multiple");
此种方式获取的视频文件不能直接播放,必须使用阿里云播放器播放
二、视频播放器
参考文档:https://help.aliyun.com/document_detail/61109.html
1、视频播放器介绍
阿里云播放器SDK(ApsaraVideo Player SDK)是阿里视频服务的重要一环,除了支持点播和直播的基础播放功能外,深度融合视频云业务,如支持视频的加密播放、安全下载、清晰度切换、直播答题等业务场景,为用户提供简单、快速、安全、稳定的视频播放服务。
2、集成视频播放器
参考文档:https://help.aliyun.com/document_detail/51991.html
参考 【播放器简单使用说明】一节
引入脚本文件和css文件
初始化视频播放器
var player = new Aliplayer({
id: 'J_prismPlayer',
width: '100%',
autoplay: false,
cover: 'http://liveroom-img.oss-cn-qingdao.aliyuncs.com/logo.png',
//播放配置
},function(player){
console.log('播放器创建好了。')
});
3、播放地址播放
在Aliplayer的配置参数中添加如下属性
//播放方式一:支持播放地址播放,此播放优先级最高,此种方式不能播放加密视频
source : '你的视频播放地址',
测试:创建html文件(01播放地址播放.html)
Document
var player = new Aliplayer({
id: 'J_prismPlayer',
width: '100%',
autoplay: false,
cover: 'http://liveroom-img.oss-cn-qingdao.aliyuncs.com/logo.png',
//播放配置
//播放方式一:支持播放地址播放,此播放优先级最高,此种方式不能播放加密视频
source : 'https://outin-b2b9512bc4dc11ea89cf00163e1c60dc.oss-cn-shanghai.aliyuncs.com/sv/25884263-1734c67e05f/25884263-1734c67e05f.mp4?Expires=1595476184&OSSAccessKeyId=LTAIxSaOfEzCnBOj&Signature=wj2Q5wH1m2snNTmPyDSo6cD4YTU%3D',
},function(player){
console.log('播放器创建好了。')
});
启动浏览器运行,测试视频的播放:
4、播放凭证播放(推荐)
阿里云播放器支持通过播放凭证自动换取播放地址进行播放,接入方式更为简单,且安全性更高。播放凭证默认时效为100秒(最大为3000秒),只能用于获取指定视频的播放地址,不能混用或重复使用。如果凭证过期则无法获取播放地址,需要重新获取凭证。
encryptType:'1',//如果播放加密视频,则需设置encryptType=1,非加密视频无需设置此项
vid:'视频id',
playauth:'视频授权码',
注意:播放凭证有过期时间,默认值:100秒 。取值范围:100~3000。
设置播放凭证的有效期
在获取播放凭证的测试用例中添加如下代码
request.setAuthInfoTimeout(200L);
在线配置参考:https://player.alicdn.com/aliplayer/setting/setting.html
测试:创建html文件(02播放凭证播放.html)
playauth是根据视频id获取。
Document
var player = new Aliplayer({
id: 'J_prismPlayer',
width: '100%',
autoplay: false,
cover: 'http://liveroom-img.oss-cn-qingdao.aliyuncs.com/logo.png',
//播放配置
//播放凭证播放
encryptType:'1',//如果播放加密视频,则需设置encryptType=1,非加密视频无需设置此项
vid : '07d8aac8a3b045c69cd91b84bdbedc19',
playauth : 'eyJTZWN1cml0eVRva2VuIjoiQ0FJUzN3SjFxNkZ0NUIyeWZTaklyNWZrQ01uUnZMbEc0UFNETkdEZ3FITVVhc2RobjUvZ2pUejJJSGhKZVhOdkJPMGV0ZjQrbVdCWTdQY1lsck1xRXM0VUhST1lQSklwc01VSXJsUDRKcExGc3QySjZyOEpqc1V6aE5vMTFGaXBzdlhKYXNEVkVma3VFNVhFTWlJNS8wMGU2TC8rY2lyWVhEN0JHSmFWaUpsaFE4MEtWdzJqRjFSdkQ4dFhJUTBRazYxOUszemRaOW1nTGlidWkzdnhDa1J2MkhCaWptOHR4cW1qL015UTV4MzFpMXYweStCM3dZSHRPY3FjYThCOU1ZMVdUc3Uxdm9oemFyR1Q2Q3BaK2psTStxQVU2cWxZNG1YcnM5cUhFa0ZOd0JpWFNaMjJsT2RpTndoa2ZLTTNOcmRacGZ6bjc1MUN0L2ZVaXA3OHhtUW1YNGdYY1Z5R0dOLzZuNU9aUXJ6emI0WmhKZWVsQVJtWGpJRFRiS3VTbWhnL2ZIY1dPRGxOZjljY01YSnFBWFF1TUdxQ2QvTDlwdzJYT2x6NUd2WFZnUHRuaTRBSjVsSHA3TWVNR1YrRGVMeVF5aDBFSWFVN2EwNDQvNWVUWWFwazFNVWFnQUU1cXBMc2ZOQVB4S2lmQWRGbXZySjQyaWNaQVYyd3A4T05OODlVR1Q2NHEySkJnSE9NYlV3aHRYQUJvUmhBSXZLbVJtbVdIQnBwQ0hmRHB3cUxoNkRtUGw3bTQ0QUtrb0J0R0w0YmR2eUFMbGtBbWI0MWdWcXM2UERlVkUybTRHQ1BvRDNZNkFIUGJpYlpjWEJCQzNnaFpXOGhTa1dqMkJieFFKWHJEK09ZSFE9PSIsIkF1dGhJbmZvIjoie1wiQ0lcIjpcIkdCU2NldzJNQ0J4eHQ1d2MydXgyang0VTZzTFRESWtYMC9jMGpONnhiclFJUjR5MldxTHdLdzE4clJnRVZlTVJcXHJcXG5cIixcIkNhbGxlclwiOlwiYWovb2tGOC9CTFZOR0p2VEJlWHZWS3VYbE9oU1I3QldOVGRPRGVwVlB6az1cXHJcXG5cIixcIkV4cGlyZVRpbWVcIjpcIjIwMjAtMDctMjNUMDM6MTE6MThaXCIsXCJNZWRpYUlkXCI6XCIwN2Q4YWFjOGEzYjA0NWM2OWNkOTFiODRiZGJlZGMxOVwiLFwiU2lnbmF0dXJlXCI6XCJqcWRBRHp5OXliNkZJZmxLQlBDTHNyT3V4eVU9XCJ9IiwiVmlkZW9NZXRhIjp7IlN0YXR1cyI6Ik5vcm1hbCIsIlZpZGVvSWQiOiIwN2Q4YWFjOGEzYjA0NWM2OWNkOTFiODRiZGJlZGMxOSIsIlRpdGxlIjoiNiAtIFdoYXQgSWYgSSBXYW50IHRvIE1vdmUgRmFzdGVyIiwiQ292ZXJVUkwiOiJodHRwOi8vb3V0aW4tYjJiOTUxMmJjNGRjMTFlYTg5Y2YwMDE2M2UxYzYwZGMub3NzLWNuLXNoYW5naGFpLmFsaXl1bmNzLmNvbS8wN2Q4YWFjOGEzYjA0NWM2OWNkOTFiODRiZGJlZGMxOS9zbmFwc2hvdHMvNTkxZDRiYjM1MGQ1NDM4M2IzYWM4ZGVjYzI0ZGM0NGEtMDAwMDEuanBnP0V4cGlyZXM9MTU5NTQ3NzM3OCZPU1NBY2Nlc3NLZXlJZD1MVEFJeFNhT2ZFekNuQk9qJlNpZ25hdHVyZT13d0NieXh2R0gxeW1xM0g0d3RzMHlRTFMyelUlM0QiLCJEdXJhdGlvbiI6MTYuMjc2N30sIkFjY2Vzc0tleUlkIjoiU1RTLk5UUUNzZVFmZ1c2aDZGUUxzQWZITXBaS28iLCJBY2Nlc3NLZXlTZWNyZXQiOiJwNlNQdGZoOUhXS1FRMzZESERuOEg4QWlDTHRwM2tqODQ5V2hOeDRIUXZuIiwiUmVnaW9uIjoiY24tc2hhbmdoYWkiLCJDdXN0b21lcklkIjoxMTk5MDc0OTExNjk3NzgxfQ',
},function(player){
console.log('播放器创建好了。')
});
启动浏览器运行,测试视频的播放:
05-整合阿里云视频播放器
一、后端获取播放凭证
1、VodController
service-vod微服务中创建 VodController.java
controller中创建 getPlayAuth接口方法
//根据视频id获取视频凭证
@GetMapping("getPlayAuth/{id}")
public R getPlayAuth(@PathVariable String id){
try {
//创建初始化对象
DefaultAcsClient client = InitObjectV.initVodClient(ConstantPropertiesUtil.ACCESS_KEY_ID,ConstantPropertiesUtil.ACCESS_KEY_SECRET);
//创建获取凭证request和response对象
GetVideoPlayAuthRequest request = new GetVideoPlayAuthRequest();
//向request设置视频id
request.setVideoId(id);
//调用方法得到凭证
GetVideoPlayAuthResponse response = client.getAcsResponse(request);
String playAuth = response.getPlayAuth();
return R.ok().data("playAuth",playAuth);
} catch (ClientException e) {
throw new GuliException(20001,"获取凭证失败");
}
}
2、Swagger测试
二、前端播放器整合
1、点击播放超链接
course/_id.vue
修改课时目录超链接
2、layout
因为播放器的布局和其他页面的基本布局不一致,因此创建新的布局容器 layouts/video.vue
export default {}
html,body{
height:100%;
}
.head {
height: 50px;
position: absolute;
top: 0;
left: 0;
width: 100%;
}
.head .logo{
height: 50px;
margin-left: 10px;
}
.body {
position: absolute;
top: 50px;
left: 0;
right: 0;
bottom: 0;
overflow: hidden;
}
3、api
创建api模块 api/vod.js,从后端获取播放凭证
import request from '@/utils/request'
export default {
//根据视频id获取视频凭证
getPlayAuth(vid) {
return request({
url: `/eduvod/video/getPlayAuth/${vid}`,
method: 'get'
})
}
}
4、播放组件相关文档
集成文档:https://help.aliyun.com/document_detail/51991.html?spm=a2c4g.11186623.2.39.478e192b8VSdEn
在线配置:https://player.alicdn.com/aliplayer/setting/setting.html
功能展示:https://player.alicdn.com/aliplayer/presentation/index.html
5、创建播放页面
创建pages/player/_vid.vue
(1)引入播放器js库和css样式
(2)获取播放凭证
(3)创建播放器
/**
* 页面渲染完成时:此时js脚本已加载,Aliplayer已定义,可以使用
* 如果在created生命周期函数中使用,Aliplayer is not defined错误
*/
mounted() {
new Aliplayer({
id: 'J_prismPlayer',
vid: this.vid, // 视频id
playauth: this.playAuth, // 播放凭证
encryptType: '1', // 如果播放加密视频,则需设置encryptType=1,非加密视频无需设置此项
width: '100%',
height: '500px'
}, function(player) {
console.log('播放器创建成功')
})
}
(4)其他常见的可选配置
// 以下可选设置
cover: 'http://guli.shop/photo/banner/1525939573202.jpg', // 封面
qualitySort: 'asc', // 清晰度排序
mediaType: 'video', // 返回音频还是视频
autoplay: false, // 自动播放
isLive: false, // 直播
rePlay: false, // 循环播放
preload: true,
controlBarVisibility: 'hover', // 控制条的显示方式:鼠标悬停
useH5Prism: true, // 播放器类型:html5
6、加入播放组件
功能展示: https://player.alicdn.com/aliplayer/presentation/index.html