某在线视频播放小程序项目总结

某在线视频播放小程序项目总结

1.项目简介

一款为家庭用户设计的实用技能学习平台,在tv端和小程序端都可以通过平台选择感兴趣的视频进行学习,小程序端主要功能包括3个tabBar:

  • 视频分类展示
  • 我的课程:最近学习课程、已有课程、收藏课程
  • 个人账号管理:同步电视学习、意见反馈等

2. 主要工作和疑难点汇总

2.1 主要工作

  1. 封装api请求,改造wx.requeset方法,封装http请求
  2. 抽取公共样式文件,在每个page文件夹的.wxss文件中,通过import 引入,
    如:@import "../../lib/base.wxss";
  3. 抽取公共组件,如底部分享组件、弹框组件,通过在各个页面配置usingComponents参数使用。
  4. 封装全局公共函数
  5. 业务逻辑

2.2 疑难点汇总

  1. 怎么保存学习记录?
  2. 怎么获取上次学习记录?
  3. 怎么创建订单?
  4. 怎么支付订单?
  5. 分享给第三人,点击如何跳转到分享的精确页面?
  6. 购买的课程列表、收藏的课程列表,从哪里拿到?
  7. 最近学习列表?这个是怎么拿到的?
  8. 如何区分是课程一级列表,还是二级列表?
  9. 跳转到课程详情页有哪些信息?
  10. sso_token 和 token 有什么不同?
  11. 为什么不支持ios下购买?
  12. 什么时候会同步电视端课程?
  13. 绑定电视账号后,如何跳转到之前的页面?
  14. 如何第一次只返回第一页,下拉分页,获取更多视频列表?
  15. swiper组件的使用
  16. top箭头何时出现?出现了如何返回顶部?
  17. 如何获取平台类型?
  18. 如何客服通话?
  19. 视频分类tab怎么做的?
  20. 如何使用canvas生成海报?
  21. 可操作区域高宽和屏幕高宽?
  22. 当前视频播放完毕如何自动播放下一个视频?
  23. onPullDownRefresh 和 onReachBottom

3. 业务逻辑梳理

1.项目哪几个page组成?有几个组件?


| 9个page  | 1个组件 |
|  ----  | ----  |
| login  | 获取token、管理跳转 |
| le_login  | 同步账号 |
| my | 个人账号管理,账号绑定、消息、关于、意见反馈 |
|  index | 首页 |
| list | 视频分类页 |
| detail | 视频详情页 |
| my-detailbox | 视频详情页,底部分享组件 |
| play | 视频播放页面、咨询页面 |
| topic | 专题列表页 |
| curriculum | 我的课程页面 |



重点:play(咨询、播放逻辑)、detail



2.login:微信授权登陆


login模块做了哪些工作? 
 
1. onLoad 的时候,如果是分享进入,获取分享所在的页面路径,课程分类和课程id。判断是否有权限,getUserInfo 。拿到平台信息,ios还是安卓,getSystemInfo。    

    ```
    //onLoad中:为什么需要场景值?用于分享,第三人登陆使用
    this.setData({
      isOnLoad:1,
      sharePage:e.page || '',
      shareId: e.id || '',
      scene:decodeURIComponent(e.scene) || '',   // 场景值
      course_item_id:e.course_item_id || '',   //课程类目id
      course_id:e.course_id || '',   // 课程id
      sso_token:e.sso_token || '',   // 电视端token
      isbind:e.isbind || 0,          // 是否绑定电视
    })
    ```
    
    
2.     发送登陆请求,拿到 token, is_bind_sso, openid 等值

        每个用户的token不一样
    
3. 处理页面跳转逻辑  
        
        
        通过解析 onLoad:(e) 场景值 e.scene,判断页面来源,从而做进一步跳转

4. 同步电视端账号,学习记录,

        第一种情况,有电视账号,但是和当前绑定的不一致,绑定新信息
        if(that.data.sso_token && app.globalData.is_bind_sso==1 && that.data.isbind==0){}
        
        第二种情况,没绑定过电视账号
        if(that.data.sso_token && that.data.isbind==0){}
    

3.global中几个参数, 干嘛的

globalData: {
    userInfo: null,
    openid:'',
    token:"", //微信token  2a35196268e335fbd3915d1ba14212b7
    sso_tk:"", //电视端账号 token
    is_bind_sso:"0", //是否绑定电视端账号,0未绑定,1绑定
    platform:"android" //平台
},

4.可以改进的地方:

    弹框可以抽成全局函数,弹框成功、弹框失败、loading弹框
    抽取全局公共样式
    
    index页面,可以做成8个icon可以总成组件
    list和ranking页面,有重复的部分,视频卡片信息,可以抽取出来,做成组件
    

5.首页数据怎么出来的?

head 轮播课程, 头部固定位
categorys 8个icon, 一级分类列表
modules 运营模块列表
ranking 每日精选(课程点播排行榜)10个

6.le-login 页面干嘛的?

    1. onLoad时候获取图片验证码,记录from值,上一个页面可能是同步页面,也可能是视频详情页面,记录course_id   
    2. 验证手机号   
    3. 点击切换图片验证码    
    4. 点击获取验证码,60秒后重新获取


        me.data.timer = 60;
        me.data.timerFun = setInterval(function () { 
        if (me.data.timer > 0) {
          me.setData({timer:me.data.timer-1})
        }else {
          me.setData({timer:'重新发送'})
          clearInterval(me.data.timerFun);
        }
        }, 1000);   
    5. 登陆  

            发送请求登陆

7.my 页面干嘛的

1. 跳转消息列表页,这里也是用到了onReachBottom方法,页面上拉触底事件的处理函数  
2. 跳转到意见反馈页面,open-type="contact"  
3. 绑定电视账号  

8.index 页面干嘛的

8.1 首页数据是怎么出来的?  
head 轮播课程, 头部固定位
categorys 8个icon, 一级分类列表
modules 运营模块列表
ranking 每日精选(课程点播排行榜)返回10个排名最高的视频

8.2 swiper组件的使用

    
    //假如只有一个视频,不能使用swiper,假如有多个视频,可以使用swiper
    
    
        
            
                
            
        
    

    
    

8.3 点击跳转至二层页面

    
     根据点击目标对象的action值进行跳转,  
     event.currentTarget.dataset.action==6

8.4 下拉显示更多视频

    
    onReachBottom 函数
    

8.5 一键返回顶部

9.list页面做了什么

9.1视频的二级tab,如生活信息、情感心理

可滚动视图区域。使用scroll-view 组件。

9.2视频卡片信息列表

和首页的一样,可以抽出来做组件。

10.detail页面做了什么?

10.1 展示视频相关信息

    
onLoad获取可使用窗口高宽,做什么?  
wx.getSystemInfo,返回参数,windowsHeight和windowWidth。 

为什么不用屏幕高宽?    
可操作区域的宽高就用windowsHeight和windowWidth。
    

10.2 课程介绍、课程评论、课程列表

        
        
课程介绍和课程列表是,onLoad的时候直接返回拿到的。
课程列表,通过onLoad发送请求,返回数组获取,videoList。

课程评论是,点击tab,发送请求获取的,会有个loading效果,返回评论列表
课程评论只能评论一条,类似购物评价,此次评论成功后,将不可再次评论。
    

10.3 点击可播放视频,跳转到视频播放页面


wx.navigateTo({
    url: '../play/play?type=list&course_item_id='+ ids 
    +'&course_id=' + event.currentTarget.dataset.id
})

10.4 咨询、收藏、分享、分享到朋友圈 ,是一个底部分享组件,my-detailbox , 在detail页面和play页面都用到了。

    
    //分享功能,跳转到login页面,
    
            
            
    onShareAppMessage:function(res){
        var me = this;
        return {
             path: 'pages/login/login?scene=detail-' + me.data.intro.course_id,
             success: function (res) {
             },
             fail: function (res) {
             }
       }
      }

10.5 使用canvas绘制分享图片


    //生成对象
    const query = wx.createSelectorQuery()
    app.globalData.myCanvas =  query.select('#myCanvas');


    //生成海报,逻辑
    pengyouquan:function(){
    var me = this;
    
    var imgH = 200*(me.data.windowWidth-100)/356;
    
    //视频名称,跳吧-莉莎广场舞
    var title = me.data.intro.title;
    var starPosition = [30,45,60,75,90];
    function drawRoundedRect(ctx, x, y, width, height, r, fill, stroke) {
      ctx.save(); ctx.beginPath(); // draw top and top right corner 
      ctx.moveTo(x + r, y);
      ctx.arcTo(x + width, y, x + width, y + r, r); // draw right side and bottom right corner 
      ctx.arcTo(x + width, y + height, x + width - r, y + height, r); // draw bottom and bottom left corner 
      ctx.arcTo(x, y + height, x, y + height - r, r); // draw left and top left corner 
      ctx.arcTo(x, y, x + r, y, r);
      if (fill) { ctx.fill(); }
      if (stroke) { ctx.stroke(); }
      ctx.restore(); 
    }
    if(title.length>=12){
      title= title.substring(0,12)+"...";
    }
    wx.showLoading({});
    me.setData({pengyouquan_layerHeight:imgH+320})
    me.setData({pengyouquanShow:1});
    wx.getImageInfo({
      src: me.data.intro.image_large,
      success: function (res1) {
        wx.request({
          method:"GET",
          url: api.qrcode, 
          data: {
            scene:'detail-'+me.data.intro.course_id,
            page:'pages/login/login'
          },
          success: function(res2){
            wx.hideLoading();
            if(res2.data.errno!=10000){
              wx.showToast({
                title: '加载图片失败,请重试',
                icon: 'none',
                duration: 2000
              })
              me.setData({pengyouquanShow:0});
              return;
            }
            var base64 =  "data:image/png;base64,"+res2.data.data.pic;
            const FILE_BASE_NAME = 'tmp_base64src';
            const fsm = wx.getFileSystemManager();
            const [, format, bodyData] = /data:image\/(\w+);base64,(.*)/.exec(base64) || [];
            const arrayBuffer = wx.base64ToArrayBuffer(bodyData);
            const filePath = `${wx.env.USER_DATA_PATH}/${FILE_BASE_NAME}.${format}`;
            fsm.writeFile({
              filePath,
              data: arrayBuffer,
              encoding: 'binary',
              success() {
                wx.getImageInfo({
                  src: app.globalData.userInfo.avatarUrl,
                  success: function (res3) {
                    var ctx = wx.createCanvasContext();
                    ctx.setFillStyle('#18B060');
                    ctx.fillRect(0, 0, me.data.windowWidth-60, imgH+670); //绿色底
                    ctx.setFillStyle('#fff');
                    drawRoundedRect(ctx, 10, 60, me.data.windowWidth-80, imgH+240, 8, true, false); //白底圆角
                    ctx.drawImage(res3.path, (me.data.windowWidth-60)/2-38, 23, 75, 75); //头像
                    ctx.drawImage('../../images/tx.png', (me.data.windowWidth-60)/2-42, 15, 84,84);//头像遮罩
                    ctx.setFontSize(14);
                    ctx.setFillStyle('#333333');
                    ctx.setTextAlign('center');
                    ctx.fillText(app.globalData.userInfo.nickName, (me.data.windowWidth-60)/2, 115); //微信名称
                    ctx.setFillStyle('#888888');
                    ctx.fillText('感觉这个课程挺适合你的,有兴趣可以看看', (me.data.windowWidth-60)/2, 142);
                    ctx.drawImage(res1.path, 20, 156, (me.data.windowWidth-100), imgH);    //视频图
                    ctx.drawImage('../../images/ic_playing_big.png', (me.data.windowWidth-90)/2-13, imgH/2+137, 57, 57);//b播放icon
                    ctx.setFontSize(16);
                    ctx.setFillStyle('#333333');
                    ctx.setTextAlign('left');
                    ctx.fillText(title, 20, imgH+180);
                    ctx.fillText(title, 20-0.2, imgH+180-0.2);
                    //点赞
                    ctx.drawImage('../../images/ic_praise_f.png', (me.data.windowWidth-140), imgH+167, 15, 15);//点赞图片
                    ctx.setFontSize(14);
                    ctx.setFillStyle('#FFB944');
                    ctx.fillText(me.data.intro.like, (me.data.windowWidth-122), imgH+181);
                    //分割线
                    ctx.setLineDash([3,3]); 
                    ctx.lineWidth = 1; 
                    ctx.strokeStyle = '#B8B7B7'; 
                    ctx.beginPath(); 
                    ctx.moveTo(20, imgH+200); 
                    ctx.lineTo(me.data.windowWidth-75, imgH+200); 
                    ctx.stroke();
                    ctx.setFillStyle('#BABABA');
                    ctx.setFontSize(12);
                    ctx.setTextAlign('left');
                    ctx.fillText('长按识别二维码去学习课程', 45, imgH+255);
                    ctx.drawImage(filePath, (me.data.windowWidth-175), imgH+208, 80, 80);
                    wx.drawCanvas({
                      canvasId:"myCanvas",
                      actions:ctx.getActions()
                    });
                  }
                })
              },
              fail() {
                wx.showToast({
                  title: '加载图片失败,请重试',
                  icon: 'none',
                  duration: 2000
                })
                me.setData({pengyouquanShow:0});
                return;
              },
            });
          }
        })
      },
      fail:function(){
        wx.hideLoading();
        wx.showToast({
          title: '加载图片失败,请重试',
          icon: 'none',
          duration: 2000
        })
        me.setData({pengyouquanShow:0});
      }
    })
      }

10.6 如何关闭生成的画报?

        
    整个背景做成蒙版效果,位置fixed, top:0, left:0, width: 100%
    点击,将视图可见设为false,点击不是画报的任何区域,关闭画报
        

10.7 如何保存生成的图片

    
    wx.canvasToTempFilePath
    
    
    canvasToImage:function(){
    var me = this
    wx.canvasToTempFilePath({
        x: 0,
        y: 10,
        //指定的画布区域的宽度
        width: me.data.windowWidth-60,
        height: (me.data.pengyouquan_layerHeight-20),
        
        //输出的图片的宽度,width*屏幕像素密度
        destWidth: (me.data.windowWidth-60) * 2,
        destHeight:(me.data.pengyouquan_layerHeight-20) * 2,
        canvasId: 'myCanvas',
        success: function (res) {
            wx.saveImageToPhotosAlbum({
              filePath: res.tempFilePath,
            })
            wx.showModal({
              title: '成功保存图片',
              showCancel: false,
              content: '图片已保存到手机相册,请自行前往朋友圈分享',
              confirmText: "知道了",
              success (res) {}
            })
        },
        fail: function (err) {
            console.log('失败')
            console.log(err)
        }
    })
      },
  

10.8 处理视频时长函数

    
    MillisecondToDate
    使用场景: 返回的课程视频列表,视频长是秒为单位的,展示是 时:分:秒
    

10.9 购买成功处理函数

        
    getDetailDate    

11.组件detailbox 详情

11.1 怎么创建订单?

    
安卓手机可以通过小程序点击购买

11.2 如何支付?

如果没有绑定电视账号,首先绑定电视账号

调用后端请求,在成功的回调函数中,调用
wx.requestPayment
支付成功后,detail 页面重新获取新的课程数据

11.3 如何咨询?

首先跳转到play页面,goChat函数

11.4 图标

返回列表,返回会话顶部,返回会话底部

12.play 页面

12.1 视频播放组件

// initial-time 指定视频初始播放位置
// bindtimeupdate 播放进度变化时触发,
//bindended 当播放到末尾时触发 ended 事件



12.2 咨询,建立一个全局 connectSocket

if(!me.data.isSocket){
  wx.getSystemInfo({
    success: function(res) {
      me.setData({model:res.model});
      me.socket(res.model); //设备型号
    },
  })
}        
//获取最近10条会话列表
me.getChatList(10, 0);

//建立链接
 wx.connectSocket({
  url: api.create_ws,
  fail:function(){
    console.log("failfail");
  }
})
//监听 WebSocket 连接打开事件
wx.onSocketOpen(function (res) {
  that.setData({isSocket:1})
  wx.hideLoading();
  socketOpen = true;
  wx.showToast({
    title: '连接成功',
    icon: 'none',
    duration: 1500
  })
  console.log('WebSocket成功!')
  clearInterval(setIntervalMsg)
  sendSocketMessage(socketMsgQueue)
})

//每隔6秒发送心跳, 发送信息
function sendSocketMessage(msg) {
  setIntervalMsg = setInterval(function(){
    wx.sendSocketMessage({
      data:"{\"type\":\"type_heartbeat\"}",
      fail: function(){
        that.getChatList(10000, that.data.msg_id_last,"",function(){
           that.socket(that.data.model);
        })
      }
    })
  },6000);
  if (socketOpen) {
    wx.sendSocketMessage({
      data: msg
    })
  }
}

//接受服务端返回信息回调函数,实时更新room中信息
wx.onSocketMessage

Q: 如何模拟会话效果?
如果是本人,头像、昵称放在右边
如果不是本人,头像、昵称放在左边

Q: msg_offset 参数为负数是什么意思?什么时候这个值会是负值?

下拉刷新的时候,是负值。看之前的评论的意思。

onPullDownRefresh: function(){
    var me = this;
    if(me.data.tab==2){
      me.getChatList(-10, me.data.msg_id_current,"pull");
    }else{
      wx.stopPullDownRefresh();
      return;
    }
    wx.stopPullDownRefresh()
 },




msg_id_last: 最后消息id
msg_id_current:0,//基准消息 id


if (msg_offset < 0) {
  this.data.messages = response.data.msg_items.concat(this.data.messages);
} else {
  this.data.messages = this.data.messages.concat(response.data.msg_items);
}
    
    
    
    

12.3 播放逻辑

    
播放完此视频后,播放下一个视频,如何操作?
让当前播放对象pause,获取播放列表数组,index+1,如果不是最后一个视频,返回下一个视频course_id,向后台发送请求,获取视频详情。

使用 wx.createVideoContext 创建 vedio 对象,播放,如果不满足条件(没有购买,试看结束),弹卡提示

如何记录播放位置?
bindtimeupdate:function(e){ //获取播放进度
    var me = this;
    me.setData({currentTime:e.detail.currentTime});
},

//返回页面时,发送请求让后端记下,参数为 currentTime
onUnload: function () {
    this.sendHistory();
    this.socketClose();
},

12.4 点击视频列表逻辑

如果点击视频没有购买,返回
如果点击视频id 和当前视频一致,返回
否则,发送新请求,获取点击视频详情         
当前播放视频回到初始位置,seek(0), 其他逻辑同播放下一个视频一致


12.5 如何跳到会话顶部?底部?

顶部逻辑,
wx.pageScrollTo({
    scrollTop: 0
}),

底部逻辑,创建查询对象,获取会话区域详情


wx.createSelectorQuery().select('#the-id').boundingClientRect(function(rect){
  rect.id      // 节点的ID
  rect.dataset // 节点的dataset
  rect.left    // 节点的左边界坐标
  rect.right   // 节点的右边界坐标
  rect.top     // 节点的上边界坐标
  rect.bottom  // 节点的下边界坐标
  rect.width   // 节点的宽度
  rect.height  // 节点的高度
}).exec()
},


wx.createSelectorQuery().select('#j_page')
.boundingClientRect(function(rect){
  wx.pageScrollTo({
    scrollTop: rect.bottom
  })
}).exec()    

13.curriculum 页面

13.1  最近学习   history_items  
13.2  购买的课程 order_items  
13.3 收藏的课程  collect_items

4. 问题汇总解答

  1. 怎么保存学习记录?

    bindtimeupdate, vedio组件的方法,播放进度变化时触发,
    event.detail = {currentTime, duration} 。
    触发频率 250ms 一次
    
    在页面看完的时候,提交上次学习记录
    onUnload: function () {
       this.sendHistory();
       this.socketClose();
    }
    
    第一个视频播放完毕后,标记 res.data.data.last_play_pos = 0;
    表示,再进入页面,第一个视频从头开始播放。
    
    
    
  2. 怎么获取上次学习记录?

    
    看完某个视频,会将该视频的观看信息返回后端,this.sendHistory
    第二次再load视频,后端返回开始时间数据
  3. 怎么创建订单?

    
    安卓手机可以通过点击,视频下方的立即购买按钮
  4. 怎么支付订单?
  5. 分享给第三人,点击如何跳转到分享的精确页面?

    通过login页面,scene参数判断,原分享页面来自哪里
  6. 购买的课程列表、收藏的课程列表

    通过点击 tab,向后端传递不同参数,后端返回
  7. 最近学习列表?

    
    默认向后端传currentNav=1, 默认学习列表page = 1, 后端返回
    当然也有onReachBottom处理函数,上拉获取更多视频数据
    
  8. 如何区分是课程一级列表,还是二级列表?

    点击8个icon, 到达的是课程分类页,其他的是课程播放页  
    跳到课程分类页:token + category_id + page_size  
    跳到课程播放页:token + course_id
  9. 跳转到课程详情页有哪些信息?

    
    课程标题、背景图、老师、购买状态、收藏状态、价格
    点赞数、是否已评论、是否能购买、iso用户???
    是否免费?播放次数、课程列表
    只有课程评论会重新发送请求。课程介绍和课程列表无。
  10. sso_token 和 token 有什么不同?

    
    电视账户sso_token ,用户 token
  11. 为什么不支持ios?

    
    暂停ios小程序虚拟支付
  12. 什么时候回同步视频信息?

    
    前提是有电视账号,值不为空
    
    第一种情况,有电视账号,
    if(that.data.sso_token && app.globalData.is_bind_sso==1 && that.data.isbind==0){}
    
    第二种情况,
    if(that.data.sso_token && that.data.isbind==0){}
  13. 绑定电视账号后,如何跳转到之前的页面?

    onLoad 的时候,记录e.from, 一般有两种情况,详情播放页,我的账号页
               
    
  14. 如何触底上拉,获取更多视频列表?

    onReachBottom 函数,页面上拉触底事件的处理函数
    触发这个函数,使得 this.data.pageIndex + 1
    重新向后端发送新的请求,返回page_size条数据
    然后把之前的数组 concat 后端返回的新数组,如[10] + [10]
    
  15. swiper组件的使用
  16. top箭头何时出现?出现了如何返回顶部?

    
        onPageScroll    页面滚动触发事件的处理函数
    
      // 监听滚动条坐标,超过500出现滚动到顶部箭头
      onPageScroll: function (e) {
        //console.log(e)
        var me = this
        var scrollTop = e.scrollTop
        var backTopValue = scrollTop > 500 ? true : false
        me.setData({
          isUp: backTopValue
        })
      },
      
     //滚动到顶部函数
     wx.pageScrollTo({
      scrollTop: 0
    })
    
    
  17. 如何获取平台类型?

    wx.getSystemInfo({
      success (res) {
        app.globalData.platform = res.platform;
      }
    })
    
  18. 如何客服通话?

    //回话消息卡片,客户发送消息,可以在,微信-服务通知-客服小助手 中收到实时消息
    
  19. 视频分类tab怎么做的?

    可滚动视图区域。scroll-view。
    哪几个参数?
    
    
        
        {{item.name}}
        
    
    
  20. 如何使用canvas生成海报?
  21. 可操作区域高宽和屏幕高宽?
  22. 当前视频播放完毕如何自动播放下一个视频?

    
    如果直接替换video的src是不行的,通过wx.createVideoContext创建不同的video组件,通过不同的id区分,给当前视频绑定,bindend 函数
    
    结束时触发,停止当前视频播放
    获取下一个视频src
    
    
  23. onPullDownRefresh 和 onReachBottom 区别

    页面相关事件处理函数——监听用户下拉动作
    页面上拉触发底事件的处理函数

你可能感兴趣的:(小程序,前端)