提交 02c0859c authored 作者: 王鹏飞's avatar 王鹏飞

merge...

...@@ -3,4 +3,5 @@ node_modules ...@@ -3,4 +3,5 @@ node_modules
npm-debug.log npm-debug.log
.idea/ .idea/
.DS_Store .DS_Store
*/.DS_Store */.DS_Store
\ No newline at end of file miniprogram_npm
\ No newline at end of file
import httpRequest from '../utils/request.js'
/**
* 获取课程详情
* @param {string} semesterId 学期ID
* @param {string} courseId 课程ID
*/
export function getCourse(semesterId, courseId) {
return httpRequest.get(`/api/lms-ep/v2/education/courses/${courseId}`).then(response => {
response.chapters = response.chapters.filter(item => {
item.children = item.children.filter(child => child.type === 2 && child.resource_id)
return item.children.length
})
return response
})
}
/**
* 获取章节资源详情
* @param {string} vid 章节的资源ID
*/
export function getChapterVideo(vid) {
return httpRequest.post('/api/lms-ep/v2/education/aliyun-video-streaming', { vid })
}
/**
* 获取章节视频播放进度
* @param {string} semesterId 学期ID
* @param {string} resourseId 章节的资源ID
* @param {Object} params
*/
export function getChapterVideoProgress(semesterId, resourseId, params) {
return httpRequest.get(`/api/lms-ep/v2/education/video/${resourseId}/device`, params)
}
/**
* 更新章节视频播放进度
* @param {Object} params
*/
export function updateChapterVideoProgress(params) {
return httpRequest.get('/api/lms-ep/v2/analytics/upload-video', params)
}
...@@ -83,24 +83,24 @@ const getChapterList = (cur_course_id, cur_semester_id, cur_video_id, callback) ...@@ -83,24 +83,24 @@ const getChapterList = (cur_course_id, cur_semester_id, cur_video_id, callback)
}) })
} }
/* 获取对应某个章节的详细信息 */ /* 获取对应某个章节的详细信息 */
const getCurrentChapterDetail = (vid, videoType,callback) => { const getCurrentChapterDetail = (sid, vid, cid,callback) => {
// CC视频请求数据接口 // CC视频请求数据接口
let _url = null; const uid = wx.getStorageSync('uid')
if(videoType == 3){
_url = util.config.URL_PATH1 + '/v2/education/aliyun-video-streaming'
}else{
_url = util.config.URL_PATH1 + '/v2/education/video-streaming'
}
util.requestApi({ util.requestApi({
url: _url, url: util.config.URL_PATH1 + '/v2/education/aliyun-video-streaming',
method: 'POST', method: 'POST',
data: { data: {
vid: vid semester_id: sid,
vid: vid,
uid: wx.getStorageSync('uid'),
sid: wx.getStorageSync('sid'),
c: cid,
s: sid,
v: vid
}, },
callback: function (res) { callback: function (res) {
// 进入视频,开始读取视频时,存储 vid、log_key // 进入视频,开始读取视频时,存储 vid、log_key
wx.setStorageSync('video_001', { vid: vid, log_key: res.data.log_key || ''}) wx.setStorageSync('video_001', { semester_id: sid, vid: vid, log_key: res.data.log_key || ''})
let json = { let json = {
video: { video: {
src: res.data.video.SD, src: res.data.video.SD,
...@@ -124,9 +124,16 @@ const getCurrentChapterDetail = (vid, videoType,callback) => { ...@@ -124,9 +124,16 @@ const getCurrentChapterDetail = (vid, videoType,callback) => {
} }
/* 结束时,调用接口 */ /* 结束时,调用接口 */
const endVideo = () => { const endVideo = (obj) => {
let json = wx.getStorageSync('video_001') || { vid: '', log_key: '' } let json = wx.getStorageSync('video_001') || { semester_id: '', vid: '', log_key: '' }
if (json.vid !== '') { if (json.vid !== '') {
json.uid = wx.getStorageSync('uid')
json.sid = wx.getStorageSync('sid')
json.d = obj.did
json.i = obj.did
json.c = obj.cid
json.s = json.semester_id
json.v = json.vid
util.requestApi({ util.requestApi({
url: util.config.URL_PATH1 + '/v2/education/end-aliyun-video-streaming', url: util.config.URL_PATH1 + '/v2/education/end-aliyun-video-streaming',
method: 'POST', method: 'POST',
......
...@@ -103,6 +103,7 @@ const getCourseDetail = (id, sid, callback) => { ...@@ -103,6 +103,7 @@ const getCourseDetail = (id, sid, callback) => {
_homework.semester_id = res.data.semester_id _homework.semester_id = res.data.semester_id
} }
return { return {
id: __.id,
cid: cur.course_id, cid: cur.course_id,
sid: cur.semester_id, sid: cur.semester_id,
vid: __.resource_id, vid: __.resource_id,
......
...@@ -16,7 +16,8 @@ ...@@ -16,7 +16,8 @@
"pages/learnSystem/my/my", "pages/learnSystem/my/my",
"pages/learnSystem/myScore/myScore", "pages/learnSystem/myScore/myScore",
"pages/learnSystem/myDiscuss/myDiscuss", "pages/learnSystem/myDiscuss/myDiscuss",
"pages/learnSystem/discussDetail/discussDetail" "pages/learnSystem/discussDetail/discussDetail",
"pages/course/player"
], ],
"window": { "window": {
"navigationBarBackgroundColor": "#fff", "navigationBarBackgroundColor": "#fff",
...@@ -52,6 +53,10 @@ ...@@ -52,6 +53,10 @@
"sitemapLocation": "sitemap.json", "sitemapLocation": "sitemap.json",
"usingComponents": { "usingComponents": {
"dialog": "./components/dialog", "dialog": "./components/dialog",
"main-page": "./components/main" "main-page": "./components/main",
"van-tab": "@vant/weapp/tab/index",
"van-tabs": "@vant/weapp/tabs/index",
"van-collapse": "@vant/weapp/collapse/index",
"van-collapse-item": "@vant/weapp/collapse-item/index"
} }
} }
const audio = require('./behaviors/audio.js')
Component({
behaviors: [audio],
/**
* 组件的属性列表
*/
properties: {
hasBackgroundAudio: { type: Boolean, defaut: false }
},
/**
* 组件的初始数据
*/
data: {},
/**
* 组件的方法列表
*/
methods: {
// 切换音频
switchAudio() {
this.triggerEvent('switchAudio')
}
}
})
{
"component": true,
"usingComponents": {}
}
\ No newline at end of file
<view class="player">
<view class="controls">
<view class="controls-inner">
<!-- 播放 -->
<image
src="/assets/images/player_audio_play.png"
class="controls-button controls-button__play"
bindtap="play"
hidden="{{ status === 'playing' }}"
></image>
<!-- 暂停 -->
<image
src="/assets/images/player_audio_pause.png"
class="controls-button controls-button__pause"
bindtap="pause"
hidden="{{ status !== 'playing' }}"
></image>
<view class="controls-time"> {{ currentTimeText }} </view>
<view class="controls-progress">
<slider
class="slider"
backgroundColor="#cccccc"
activeColor="#ff6767"
block-size="13"
block-color="#ff6767"
value="{{ currentTime }}"
max="{{ duration }}"
bindchanging="onSlideChanging"
bindchange="onSliderChange"
></slider>
<!-- <view class="controls-progress-bar">
<view
class="controls-progress-bar__buffer"
style="{{ bufferBarStyle }}"
></view>
<view
class="controls-progress-bar__inner"
style="{{ progressBarStyle }}"
>
<view
class="controls-progress-bar__dot"
bindtouchstart="handleTouchstart"
bindtouchmove="handleTouchmove"
bindtouchend="handleTouchend"
><view class="controls-progress-bar__dot__inner"></view
></view>
</view>
</view> -->
</view>
<view class="controls-time">{{ durationText }}</view>
<!-- 全屏 -->
<image
src="/assets/images/player_audio_listen.png"
class="controls-button controls-button__audio"
hidden="{{ !hasBackgroundAudio }}"
bindtap="switchAudio"
></image>
</view>
</view>
</view>
.controls-inner {
display: flex;
align-items: center;
height: 100rpx;
padding: 0 20rpx;
color: #222;
}
.controls-button {
width: 36rpx;
height: 36rpx;
padding: 20rpx;
}
.controls-button__play {
width: 22rpx;
height: 30rpx;
}
.controls-button__pause {
width: 22rpx;
height: 30rpx;
}
.controls-button__ppt {
width: 33rpx;
height: 33rpx;
}
.controls-button__fullscreen {
width: 46rpx;
height: 34rpx;
}
.controls-progress {
flex: 1;
margin: 0 24rpx;
}
.controls-progress .slider {
margin: 0;
padding: 0;
}
.controls-progress-bar {
position: relative;
width: 100%;
height: 6rpx;
background: rgba(204, 204, 204, 1);
}
.controls-progress-bar__buffer {
position: absolute;
left: 0;
top: 0;
bottom: 0;
background-color: #fff;
}
.controls-progress-bar__inner {
position: absolute;
left: 0;
top: 0;
bottom: 0;
background-color: #c62245;
}
.controls-progress-bar__dot {
position: absolute;
right: -13rpx;
top: -10rpx;
width: 16rpx;
height: 16rpx;
padding: 5rpx;
background-color: rgba(255, 103, 103, 0.6);
border-radius: 50%;
box-sizing: content-box;
}
.controls-progress-bar__dot__inner {
width: 16rpx;
height: 16rpx;
background-color: #c62245;
border-radius: 50%;
}
const computedBehavior = require('miniprogram-computed')
import { secondToTime } from '../../../utils/util.js'
module.exports = Behavior({
behaviors: [computedBehavior],
properties: {
source: String,
autoplay: { type: Boolean, value: false }, // 自动播放
loop: { type: Boolean, value: false }, // 循环播放
muted: { type: Boolean, value: false }, // 静音
startTime: { type: Number, value: 0 } // 初始播放时间
},
data: {
src: 'http://mp3.9ku.com/mp3/3/2943.mp3',
status: '',
duration: 0, // 总时长
currentTime: 0, // 当前播放时间
buffered: 0 // 当前播放时间点到此时间点内容已缓冲
},
computed: {
// 缓冲进度
bufferBarStyle(data) {
let percentage = Math.floor((data.buffered / data.duration) * 100)
return `width:${percentage}%;`
},
// 播放进度
progressBarStyle(data) {
let percentage = Math.floor((data.currentTime / data.duration) * 100)
return `width:${percentage}%;`
},
// 当前播放时间
currentTimeText(data) {
const currentTime = Math.floor(data.currentTime)
return secondToTime(currentTime)
},
// 总时长
durationText(data) {
const duration = Math.floor(data.duration)
return secondToTime(duration)
}
},
lifetimes: {
attached() {
this.createPlayer()
},
detached() {
this.destroyPlayer()
}
},
pageLifetimes: {
hide() {
this.pause()
}
},
player: null,
methods: {
createPlayer() {
this.player = wx.createInnerAudioContext()
const { src, startTime, autoplay, loop } = this.data
this.player.src = src
this.player.startTime = startTime
this.player.autoplay = autoplay
this.player.loop = loop
wx.setInnerAudioOption({ obeyMuteSwitch: false })
// 事件监听
this.bindEvent()
},
// 事件
bindEvent() {
this.player.onCanplay(() => this.onCanplay())
this.player.onPlay(() => this.onPlay())
this.player.onPause(() => this.onPause())
this.player.onWaiting(() => this.onWaiting())
this.player.onEnded(() => this.onEnded())
this.player.onError(e => this.onError(e))
this.player.onTimeUpdate(() => this.onTimeUpdate())
this.player.onStop(() => this.onStop())
this.player.onSeeked(() => this.onSeeked())
},
// 播放
play() {
this.player.play()
this.updateStatus('playing')
console.log('audio', 'play')
},
// 暂停
pause() {
this.player.pause()
this.updateStatus('paused')
console.log('audio', 'pause')
},
// 跳转到指定位置,单位s
seek(position) {
this.player.seek(position)
console.log('audio', 'seek')
},
// 设置倍速播放
playbackRate(rate) {
// 范围 0.5-2.0
this.player.playbackRate = rate
},
// 更新状态
updateStatus(status) {
this.setData({ status })
},
// 销毁实例
destroyPlayer() {
this.player && this.player.destroy()
},
onCanplay() {
this.data.autoplay && this.play()
console.log('audio', 'oncanplay')
},
// 监听播放
onPlay() {
const { duration, buffered } = this.player
this.setData({ duration, buffered })
this.updateStatus('playing')
this.triggerEvent('play')
console.log('audio', 'onplay')
},
// 监听暂停
onPause() {
this.updateStatus('paused')
this.triggerEvent('pause')
console.log('audio', 'onpause')
},
// 监听播放结束
onEnded() {
this.updateStatus('ended')
this.triggerEvent('ended')
console.log('audio', 'onended')
},
onStop() {
this.updateStatus('ended')
console.log('audio', 'onstop')
},
// 播放进度变化
onTimeUpdate() {
const { currentTime, duration, buffered } = this.player
if (!Math.floor(currentTime)) {
return
}
this.setData({ currentTime, duration, buffered })
this.data.status === 'waiting' && this.updateStatus('playing')
this.triggerEvent('timeupdate', { currentTime, duration })
// console.log('audio', 'ontimeupdate', currentTime)
},
// 视频出现缓冲
onWaiting() {
const { buffered } = this.player
this.setData({ buffered })
this.updateStatus('waiting')
console.log('audio', 'onwaiting')
},
// 进度跳转完成
onSeeked() {
this.play()
console.log('audio', 'onseeked')
},
onError(e) {
this.updateStatus('ended')
console.log('audio', 'onerror', e)
},
// 进度拖动中
onSlideChanging(e) {
if (this.data.status === 'playing') {
this.player.pause()
}
const { value } = e.detail
this.setData({ currentTime: value })
},
// 进度拖动完成
onSliderChange(e) {
const { value } = e.detail
this.seek(value)
}
}
})
const computedBehavior = require('miniprogram-computed')
import { secondToTime } from '../../../utils/util.js'
const app = getApp()
module.exports = Behavior({
behaviors: [computedBehavior],
/**
* 组件的属性列表
*/
properties: {
src: { type: String },
controls: { type: Boolean, value: true }, // 控制栏
autoplay: { type: Boolean, value: false }, // 自动播放
loop: { type: Boolean, value: false }, // 循环播放
muted: { type: Boolean, value: false }, // 静音
startTime: { type: Number, value: 0 }, // 初始播放时间
playbackRate: { type: Number, value: 1 }, // 播放倍数
poster: { type: String },
showCenterPlayBtn: { type: Boolean, value: true } // 显示中间播放按钮
},
/**
* 组件的初始数据
*/
data: {
status: '',
duration: 0, // 总时长
currentTime: 0, // 当前播放时间
buffered: 0, // 0-100,加载进度百分比
isFullscreen: false, // 是否全屏
showControls: true,
showCenterPlay: true,
playbackRateList: [0.5, 0.8, 1.0, 1.25, 1.5, 2.0],
playbackRateVisible: false
},
observers: {
src() {
this.setData({ status: '', showCenterPlay: true })
},
showCenterPlayBtn(value) {
this.setData({ showCenterPlay: value })
}
},
computed: {
// 缓冲进度
bufferBarStyle(data) {
return `width:${data.buffered}%;`
},
// 播放进度
progressBarStyle(data) {
let percentage = Math.floor((data.currentTime / data.duration) * 100)
return `width:${percentage}%;`
},
// 当前播放时间
currentTimeText(data) {
const currentTime = Math.floor(data.currentTime)
return secondToTime(currentTime)
},
// 总时长
durationText(data) {
const duration = Math.floor(data.duration)
return secondToTime(duration)
}
},
/**
* 组件的生命周期
* */
lifetimes: {
attached() {
this.createPlayer()
wx.onNetworkStatusChange(res => {
if (res.networkType !== 'wifi' && this.data.status === 'playing') {
this.pause()
wx.showModal({
content: '你正在使用非WIFI网络,播放将产生流量费用',
confirmText: '继续播放',
success: res => {
if (res.confirm) {
this.play()
} else if (res.cancel) {
this.pause()
}
}
})
}
})
},
detached() {
wx.offNetworkStatusChange()
}
},
player: null,
/**
* 组件的方法列表
*/
methods: {
createPlayer() {
this.player = wx.createVideoContext('palyerVideo', this)
// 设置倍数
this.playbackRate(this.data.playbackRate)
},
// 播放
play() {
this.player.play()
this.updateStatus('playing')
// 停止背景音频播放,Android手机不停止播放的问题
wx.getBackgroundAudioManager().stop()
console.log('video', 'play')
},
// 暂停
pause() {
this.player.pause()
this.updateStatus('paused')
console.log('video', 'pause')
},
toggleFullscreen() {
this.data.isFullscreen ? this.exitFullScreen() : this.fullscreen()
},
// 全屏
fullscreen() {
this.player.requestFullScreen()
this.setData({ isFullscreen: true })
},
// 退出全屏
exitFullScreen() {
this.player.exitFullScreen()
this.setData({ isFullscreen: false })
},
// 跳转到指定位置,单位s
seek(position) {
this.player.seek(position)
console.log('video', 'seek')
},
// 设置倍速播放
playbackRate(rate) {
// 0.5/0.8/1.0/1.25/1.5/2.0
this.player.playbackRate(rate)
console.log('video', 'playbackrate', rate)
},
// 更新状态
updateStatus(status) {
this.setData({ status })
},
// 监听播放
onPlay(e) {
this.updateStatus('playing')
this.triggerEvent('play')
this.data.showCenterPlayBtn && this.setData({ showCenterPlay: false })
console.log('video', 'onplay')
},
// 监听暂停
onPause() {
this.updateStatus('paused')
this.triggerEvent('pause')
console.log('video', 'onpause')
},
// 监听播放结束
onEnded(e) {
this.updateStatus('ended')
this.triggerEvent('ended')
this.data.showCenterPlayBtn && this.setData({ showCenterPlay: true })
// 全屏中退出全屏
this.data.isFullscreen && this.exitFullScreen()
console.log('video', 'onended')
},
// 播放进度变化
onTimeUpdate(e) {
const { currentTime, duration } = e.detail
if (!Math.floor(currentTime)) {
return
}
this.setData({ currentTime })
this.data.status === 'waiting' && this.updateStatus('playing')
this.triggerEvent('timeupdate', { currentTime, duration })
// console.log('video', 'ontimeupdate', currentTime)
},
// 加载进度变化
onProgress(e) {
const { buffered } = e.detail
this.setData({ buffered })
console.log('video', 'onprogress')
},
// 视频出现缓冲
onWaiting(e) {
this.updateStatus('waiting')
console.log('video', 'onwaiting')
},
// 视频元数据加载完成
onLoadedMetaData(e) {
const { duration } = e.detail
this.setData({ duration })
console.log('video', 'onloadedmetadata')
},
onFullScreenChange() {
this.triggerEvent('fullscreenchange')
console.log('video', 'onloadedmetadata')
},
onError(e) {
this.updateStatus('ended')
console.log('video', 'onerror', e)
},
// 返回
back() {
if (this.data.isFullscreen) {
this.exitFullScreen()
}
},
// 切换显示控制栏
toggleControls() {
this.setData({ showControls: !this.data.showControls })
},
// 进度拖动中
onSlideChanging(e) {
if (this.data.status === 'playing') {
this.player.pause()
}
const { value } = e.detail
this.setData({ currentTime: value })
},
// 进度拖动完成
onSliderChange(e) {
const { value } = e.detail
this.seek(value)
this.play()
},
// 显示倍数
showPlaybackRate() {
this.setData({ showControls: false, playbackRateVisible: true })
},
togglePlaybackRate() {
let playbackRateVisible = !this.data.playbackRateVisible
this.setData({ playbackRateVisible })
},
// 倍数切换
onChangePlaybackRate(e) {
const { rate } = e.currentTarget.dataset
this.setData({ playbackRate: rate, playbackRateVisible: false })
this.playbackRate(rate)
}
}
})
const video = require('./behaviors/video.js')
Component({
behaviors: [video],
/**
* 组件的属性列表
*/
properties: {
ppts: { type: Array, value: [] },
hasBackgroundAudio: { type: Boolean, defaut: false }
},
/**
* 组件的初始数据
*/
data: {},
/**
* 组件的方法列表
*/
methods: {
// 切换音频
switchAudio() {
// 全屏中退出全屏
this.pause()
this.data.isFullscreen && this.exitFullScreen()
this.triggerEvent('switchAudio')
}
}
})
{
"component": true,
"usingComponents": {}
}
<view class="player {{ isFullscreen ? 'is-fullscreen' : '' }}" wx:if="{{ src }}">
<video
id="palyerVideo"
class="video"
src="{{ src }}"
controls="{{ false }}"
autoplay="{{ autoplay }}"
loop="{{ loop }}"
muted="{{ muted }}"
initial-time="{{ time }}"
show-center-play-btn="{{ false }}"
auto-pause-if-open-native="{{ false }}"
bindplay="onPlay"
bindpause="onPause"
bindended="onEnded"
bindtimeupdate="onTimeUpdate"
bindprogress="onProgress"
bindwaiting="onWaiting"
bindloadedmetadata="onLoadedMetaData"
bindfullscreenchange="onFullScreenChange"
binderror="onError"
>
<!-- 中间播放按钮 -->
<block wx:if="{{ showCenterPlay }}">
<!-- 封面 -->
<image src="{{ poster }}" class="poster" wx:if="{{ poster }}"></image>
<view class="player-center" hidden="{{ status === 'playing' }}">
<image src="/assets/images/player_play.png" bindtap="play" class="center-play-btn"></image>
</view>
</block>
<block wx:else>
<view class="cover" bindtap="toggleControls"></view>
<view class="tips" wx:if="{{ status === 'waiting' }}">
<!-- 缓冲中 -->
<view class="loading"></view>
</view>
<block wx:if="{{ showControls }}">
<view class="toolbar">
<view class="toolbar-inner">
<!-- 返回 -->
<image
src="/assets/images/player_arrow.png"
class="toolbar-button toolbar-button__back"
bindtap="back"
hidden="{{ !isFullscreen }}"
></image>
<!-- 切换音频 -->
<image
src="/assets/images/player_audio.png"
class="toolbar-button toolbar-button__audio"
bindtap="switchAudio"
hidden="{{ !hasBackgroundAudio }}"
></image>
</view>
</view>
<view class="controls" wx:if="{{ controls }}">
<view class="controls-inner">
<!-- 播放 -->
<image
src="/assets/images/player_play.png"
class="controls-button controls-button__play"
bindtap="play"
hidden="{{ status === 'playing' }}"
></image>
<!-- 暂停 -->
<image
src="/assets/images/player_pause.png"
class="controls-button controls-button__pause"
bindtap="pause"
hidden="{{ status !== 'playing' }}"
></image>
<view class="controls-time">
{{ currentTimeText }}
</view>
<view class="controls-progress">
<slider
class="slider"
backgroundColor="#cccccc"
activeColor="#ff6767"
block-size="13"
block-color="#ff6767"
value="{{ currentTime }}"
max="{{ duration }}"
bindchanging="onSlideChanging"
bindchange="onSliderChange"
></slider>
<!-- <view class="controls-progress-bar">
<view
class="controls-progress-bar__buffer"
style="{{ bufferBarStyle }}"
></view>
<view
class="controls-progress-bar__inner"
style="{{ progressBarStyle }}"
>
<view
class="controls-progress-bar__dot"
bindtouchstart="handleTouchstart"
bindtouchmove="handleTouchmove"
bindtouchend="handleTouchend"
><view class="controls-progress-bar__dot__inner"></view
></view>
</view>
</view> -->
</view>
<view class="controls-time">{{ durationText }}</view>
<view class="controls-playbackRate" bindtap="showPlaybackRate">
<block wx:if="{{ playbackRate === 1 }}">倍速</block><block wx:else>{{ playbackRate }}X</block>
</view>
<!-- ppt -->
<image
src="/assets/images/player_ppt.png"
class="controls-button controls-button__ppt"
hidden="{{ !ptts.length }}"
></image>
<!-- 全屏 -->
<image
src="/assets/images/player_fullscreen.png"
class="controls-button controls-button__fullscreen"
bindtap="toggleFullscreen"
></image>
</view>
</view>
</block>
<view class="rate" hidden="{{ !playbackRateVisible }}" bindtap="togglePlaybackRate">
<view class="rate-list" catchtap="false">
<block wx:for="{{ playbackRateList }}" wx:key="*this">
<view
class="rate-item {{ item === playbackRate ? 'is-active' : '' }}"
data-rate="{{ item }}"
catchtap="onChangePlaybackRate"
>{{ item }}X</view
>
</block>
</view>
</view>
</block>
<slot></slot>
</video>
</view>
.player {
position: relative;
}
.video {
position: relative;
display: block;
width: 100%;
height: 422rpx;
}
.cover {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
z-index: 1;
}
.toolbar {
position: absolute;
left: 0;
right: 0;
top: 0;
color: #fff;
background: linear-gradient(360deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.6) 100%);
z-index: 10;
}
.is-fullscreen .toolbar {
padding-top: env(safe-area-inset-top);
padding-left: env(safe-area-inset-left);
padding-right: env(safe-area-inset-right);
}
.toolbar-inner {
display: flex;
align-items: center;
justify-content: flex-end;
height: 100rpx;
color: #fff;
}
.is-fullscreen .toolbar-inner {
justify-content: space-between;
padding: 0 60rpx;
}
.toolbar-button {
width: 36rpx;
height: 36rpx;
padding: 40rpx;
}
.toolbar-button__back {
width: 19rpx;
height: 36rpx;
}
.toolbar-button__audio {
width: 36rpx;
height: 30rpx;
}
.controls {
position: absolute;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.6) 100%);
z-index: 10;
}
.is-fullscreen .controls {
padding-bottom: env(safe-area-inset-bottom);
padding-left: env(safe-area-inset-left);
padding-right: env(safe-area-inset-right);
}
.is-fullscreen .controls-inner {
padding: 0 60rpx;
}
.controls-inner {
display: flex;
align-items: center;
height: 100rpx;
color: #fff;
}
.controls-button {
width: 36rpx;
height: 36rpx;
padding: 30rpx 40rpx;
}
.controls-button__play {
width: 22rpx;
height: 30rpx;
}
.controls-button__pause {
width: 22rpx;
height: 30rpx;
}
.controls-button__ppt {
width: 33rpx;
height: 33rpx;
}
.controls-button__fullscreen {
width: 46rpx;
height: 34rpx;
}
.controls-time {
min-width: 64rpx;
font-size: 24rpx;
line-height: 1;
color: #fff;
}
.controls-playbackRate {
padding-left: 18rpx;
font-size: 30rpx;
color: #fff;
}
.controls-progress {
flex: 1;
margin: 0 24rpx;
}
.controls-progress .slider {
margin: 0;
padding: 0;
}
.controls-progress-bar {
position: relative;
width: 100%;
height: 6rpx;
background: rgba(204, 204, 204, 1);
}
.controls-progress-bar__buffer {
position: absolute;
left: 0;
top: 0;
bottom: 0;
background-color: #fff;
}
.controls-progress-bar__inner {
position: absolute;
left: 0;
top: 0;
bottom: 0;
background-color: #c62245;
}
.controls-progress-bar__dot {
position: absolute;
right: -13rpx;
top: -10rpx;
width: 16rpx;
height: 16rpx;
padding: 5rpx;
background-color: rgba(255, 103, 103, 0.6);
border-radius: 50%;
box-sizing: content-box;
}
.controls-progress-bar__dot__inner {
width: 16rpx;
height: 16rpx;
background-color: #c62245;
border-radius: 50%;
}
.tips {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
padding: 5px;
color: #fff;
background: #4c4c4c;
border-radius: 5px;
}
.loading {
width: 30px;
height: 30px;
display: inline-block;
vertical-align: middle;
animation: loading 1s steps(12, end) infinite;
background: transparent
url("data:image/svg+xml;charset=utf8, %3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120' viewBox='0 0 100 100'%3E%3Cpath fill='none' d='M0 0h100v100H0z'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='%23E9E9E9' rx='5' ry='5' transform='translate(0 -30)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='%23989697' rx='5' ry='5' transform='rotate(30 105.98 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='%239B999A' rx='5' ry='5' transform='rotate(60 75.98 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='%23A3A1A2' rx='5' ry='5' transform='rotate(90 65 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='%23ABA9AA' rx='5' ry='5' transform='rotate(120 58.66 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='%23B2B2B2' rx='5' ry='5' transform='rotate(150 54.02 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='%23BAB8B9' rx='5' ry='5' transform='rotate(180 50 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='%23C2C0C1' rx='5' ry='5' transform='rotate(-150 45.98 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='%23CBCBCB' rx='5' ry='5' transform='rotate(-120 41.34 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='%23D2D2D2' rx='5' ry='5' transform='rotate(-90 35 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='%23DADADA' rx='5' ry='5' transform='rotate(-60 24.02 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='%23E2E2E2' rx='5' ry='5' transform='rotate(-30 -5.98 65)'/%3E%3C/svg%3E")
no-repeat;
background-size: 100%;
}
@keyframes loading {
0% {
transform: rotate3d(0, 0, 1, 0deg);
}
100% {
transform: rotate3d(0, 0, 1, 360deg);
}
}
.player-center {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.2);
z-index: 10;
}
.center-play-btn {
width: 60rpx;
height: 80rpx;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.poster {
width: 100%;
height: 100%;
}
.rate {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
z-index: 100;
}
.rate-list {
position: absolute;
top: 0;
right: 0;
bottom: 0;
display: flex;
flex-direction: column;
justify-content: center;
background: linear-gradient(90deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.6) 100%);
width: 300rpx;
}
.is-fullscreen .rate-list {
padding-right: env(safe-area-inset-bottom);
background-color: #000;
}
.rate-item {
font-size: 30rpx;
line-height: 54rpx;
color: #fff;
text-align: center;
}
.is-fullscreen .rate-item {
padding: 10rpx 0;
}
.rate-item.is-active {
color: #f47885;
}
...@@ -3,13 +3,13 @@ const env = 'production' ...@@ -3,13 +3,13 @@ const env = 'production'
if (env === 'production') { if (env === 'production') {
module.exports = { module.exports = {
appVersion: 'enterprise_v3.2.2', appVersion: 'enterprise_v3.2.5',
apiBaseURL: 'https://wechat-api.ezijing.com', // 接口请求地址 apiBaseURL: 'https://wechat-api.ezijing.com', // 接口请求地址
tenant: 'classes' tenant: 'classes'
} }
} else { } else {
module.exports = { module.exports = {
appVersion: 'enterprise_v3.2.2', appVersion: 'enterprise_v3.2.5',
apiBaseURL: 'https://wechat-api2.ezijing.com', // 接口请求地址 apiBaseURL: 'https://wechat-api2.ezijing.com', // 接口请求地址
tenant: 'classes' tenant: 'classes'
} }
......
{
"name": "learn-weapp",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@vant/weapp": {
"version": "1.6.5",
"resolved": "https://registry.npmjs.org/@vant/weapp/-/weapp-1.6.5.tgz",
"integrity": "sha512-7+oNnlLIcHSqcwtzAKSM2LM+feXyfNIEOedHn5yDumPQPyGPDmsY6x5vwZ88i+sZspfj4HmzfeljJQ/rtDT0cg=="
},
"fast-deep-equal": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
"integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk="
},
"miniprogram-computed": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/miniprogram-computed/-/miniprogram-computed-2.2.0.tgz",
"integrity": "sha512-UlPfPh5cvBnwb2jNXToUMxNkWAmi8a7hAjtJlwdA4XF5EbFI5Q5jI9mn1U8AAxbsUfnSf1Vmsc0LhkxGdKlRkg==",
"requires": {
"fast-deep-equal": "^2.0.1",
"rfdc": "^1.1.4"
}
},
"rfdc": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.2.0.tgz",
"integrity": "sha512-ijLyszTMmUrXvjSooucVQwimGUk84eRcmCuLV8Xghe3UO85mjUtRAHRyoMM6XtyqbECaXuBWx18La3523sXINA=="
}
}
}
{
"name": "learn-weapp",
"version": "1.0.0",
"description": "",
"main": "app.js",
"dependencies": {
"@vant/weapp": "^1.6.5",
"miniprogram-computed": "^2.2.0"
},
"devDependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "https://gitlab.ezijing.com/zhangyanxin/learn-online.git"
},
"author": "",
"license": "ISC"
}
const computedBehavior = require('miniprogram-computed')
import throttle from '../../utils/throttle.js'
import * as api from '../../api/course.js'
const app = getApp()
Page({
behaviors: [computedBehavior],
/**
* 页面的初始数据
*/
data: {
loaded: false,
tabActive: 0,
collapseActiveNames: [],
course: {}, // 课程详情
activeChapter: {}, // 当前选中的章节
semesterId: '', // 学期ID
courseId: '', // 课程ID
chapterId: '', // 章节ID
video: {}, // 视频信息
ppts: [], // ppt
deviceId: 'jjhz92fn0.le2a6c06c9g0.thhg7ekb1f8',
progress: {
loaded: false,
cpt: 0, // 当前播放时间
mpt: 0, // 当前播放最大时间
progress: 0, // 进度
pt: 0 // 累计观看时间
},
throttled: null,
throttleWait: 5,
watchedTime: 0,
watchedTimePoint: [], // 视频观看的时间点
scrollViewHeight: 0,
autoplay: true
},
computed: {
hasVideo(data) {
return !!(data.video.SD || data.video.LD || data.video.FD) && data.activeChapter.type === 2
},
hasAudio(data) {
return !!data.video.SQ && data.activeChapter.type === 2
},
scrollViewStyle(data) {
return `height:${data.scrollViewHeight}px;`
},
videoPlayUrl(data) {
return data.video.SD || data.video.LD || data.video.FD
}
},
onLoad: function (options) {
let { semester_id: semesterId, id: courseId, chapter_id: chapterId } = options
this.setData({ semesterId, courseId, chapterId })
this.getCourse()
},
// 获取课程详情数据
getCourse() {
api.getCourse(this.data.semesterId, this.data.courseId).then(response => {
response.chapters = response.chapters.filter(item => {
item.children = item.children.filter(child => child.type === 2)
return item.children.length
})
this.setData({ loaded: true, course: response })
const activeChapter = this.findChapter(this.data.chapterId, response.chapters)
if (activeChapter) {
// 更新选中的章节
this.updateActiveChapter(activeChapter)
// 设置默认展开
this.setData({ 'collapseActiveNames[0]': activeChapter.parent_id })
}
})
},
// 获取章节视频
getChapterVideo(vid) {
wx.showLoading({ title: '加载中...' })
api
.getChapterVideo(vid)
.then(response => {
const { video, ppts } = response
this.setData({ video, ppts }, this.updateScrollViewHeight)
this.getChapterVideoProgress(vid)
wx.hideLoading()
})
.catch(() => {
wx.hideLoading()
})
},
// 获取章节视频进度
getChapterVideoProgress(vid) {
this.setData({ 'progress.loaded': false })
api.getChapterVideoProgress(this.data.semesterId, vid, { device_id: this.data.deviceId }).then(response => {
this.data.progress = response
// 获取本地进度
const cpt = wx.getStorageSync(this.data.chapterId)
if (typeof cpt === 'number') {
this.data.progress.cpt = cpt
}
// 跳转播放进度
const player = this.selectComponent('#player')
if (player && this.data.progress.cpt) {
player.seek(this.data.progress.cpt)
}
this.setData({ 'progress.loaded': true })
})
},
// 选项卡切换
onTabChange(e) {
this.setData({ tabActive: e.detail.index })
},
// 手风琴伸缩
onCollapseChange(e) {
this.setData({ collapseActiveNames: e.detail })
},
// 章节改变
onChangeChapter(e) {
const { id, chapter } = e.currentTarget.dataset
if (id === this.data.chapterId) {
return
}
// 结束后上传最后一次
if (this.data.throttled) {
this.data.throttled.cancel()
this.updateChapterVideoProgress()
}
// 清除上传进度
this.clearProgressStatus()
// 更新chapter
this.updateActiveChapter(chapter)
},
// 当前播放时间更新
onTimeupdate(e) {
let { currentTime } = e.detail
currentTime = Math.floor(currentTime)
if (!this.data.progress.loaded || !currentTime) {
return
}
this.updateProgress(currentTime)
},
// 清除上传进度数据状态
clearProgressStatus() {
this.data.throttled = null
this.data.watchedTime = 0
this.data.watchedTimePoint = []
this.setData({ 'progress.loaded': false })
},
// 更新选中的章节
updateActiveChapter(chapter) {
if (!chapter) {
return
}
wx.setNavigationBarTitle({ title: chapter.name })
this.setData({ activeChapter: chapter, chapterId: chapter.id })
if (chapter.type === 2) {
this.getChapterVideo(chapter.resource_id)
}
},
// 更新列表进度
updateProgress(currentTime) {
// 更新当前播放时间
this.data.progress.cpt = currentTime
// 本地保存进度数据
wx.setStorageSync(this.data.chapterId, this.data.progress.cpt)
// 更新视频观看总时长
this.updateWatchTime(currentTime)
// 观看的最大点
this.data.progress.mpt = Math.max(currentTime, this.data.progress.mpt)
const hasTimePoint = this.data.watchedTimePoint.includes(this.data.progress.cpt)
if (!hasTimePoint) {
this.data.watchedTimePoint.push(this.data.progress.cpt)
}
// 更新视频进度,5秒更新一次
if (this.data.throttled) {
this.data.throttled()
} else {
this.data.throttled = throttle(this.updateChapterVideoProgress, this.data.throttleWait * 1000, { leading: false })
}
},
// 更新观看总时长
updateWatchTime(time) {
if (time === this.data.watchedTime) {
return
}
this.data.watchedTime = time
// 默认增加20秒
this.data.progress.pt = Math.max(this.data.progress.pt, 20)
this.data.progress.pt++
},
// 更新章节视频进度
updateChapterVideoProgress() {
// 登录用户信息
const params = {
sid: wx.getStorageSync('sid'),
uid: wx.getStorageSync('uid'),
d: this.data.deviceId,
i: this.data.deviceId,
s: this.data.semesterId,
c: this.data.courseId, // 课程ID
v: this.data.activeChapter.resource_id, // 视频资源ID
_p: this.data.progress.pt, // 累计时间
_m: this.data.progress.mpt, // 当前播放最大时间
_c: this.data.progress.cpt, // 当前播放位置
ps: this.data.watchedTimePoint.join(',') // 播放时,统计帧
}
api.updateChapterVideoProgress(params).then(() => {
console.log('video', '记录成功')
})
// 清空已经上传过的观看时间点
this.data.watchedTimePoint = []
},
// 预览ppt
previewImage(e) {
const { src } = e.currentTarget.dataset
const urls = this.data.ppts.map(item => item.ppt_url)
wx.previewImage({ current: src, urls })
},
// 更新滚动区域高度
updateScrollViewHeight() {
const query = wx.createSelectorQuery()
query
.select('.course-tabs')
.boundingClientRect(rect => {
this.setData({ scrollViewHeight: rect.height - 44 })
})
.exec()
},
// 查找当前章节
findChapter(id, list) {
for (const item of list) {
if (item.id === id) {
return item
}
if (item.children && item.children.length) {
const found = this.findChapter(id, item.children)
if (found) {
found.parent_id = item.id
return found
}
}
}
return null
}
})
{
"disableScroll": true,
"navigationBarTitleText": "",
"usingComponents": {
"player-video": "/components/player/video",
"player-audio": "/components/player/audio"
}
}
<view class="container" wx:if="{{ loaded }}">
<view class="course-player" wx:if="{{ hasVideo || hasAudio }}">
<player-video
id="player"
src="{{ videoPlayUrl }}"
autoplay="{{ autoplay }}"
bindtimeupdate="onTimeupdate"
bindswitchAudio="onSwitchVideo"
wx:if="{{ hasVideo }}"
></player-video>
<player-audio
id="player"
src="{{ video.SQ }}"
bindtimeupdate="onTimeupdate"
bindswitchAudio="onSwitchVideo"
wx:elif="{{ hasAudio }}"
></player-audio>
</view>
<view class="course-tabs">
<van-tabs
color="#ba053f"
custom-class="tabs"
swipeable="{{ true }}"
line-width="{{ 70 }}"
active="{{ tabActive }}"
bindchange="onTabChange"
>
<van-tab title="章节课程">
<scroll-view scroll-y="{{ true }}" class="tab-content" style="{{ scrollViewStyle }}">
<view style="padding: 10px 0">
<van-collapse
border="{{ false }}"
custom-class="collapse"
value="{{ collapseActiveNames }}"
bindchange="onCollapseChange"
>
<block wx:for="{{ course.chapters }}" wx:key="id">
<van-collapse-item border="{{ false }}" title="{{ item.name }}" name="{{ item.id }}">
<view
class="play-item {{ chapterId === item.id ? 'is-active' : '' }}"
wx:for="{{ item.children }}"
wx:key="id"
bindtap="onChangeChapter"
data-id="{{ item.id }}"
data-chapter="{{ item }}"
>
<text class="name">{{ item.name }}</text>
</view>
</van-collapse-item>
</block>
</van-collapse>
</view>
</scroll-view>
</van-tab>
<van-tab title="学习资料">
<scroll-view scroll-y="{{ true }}" class="tab-content" style="{{ scrollViewStyle }}">
<view style="padding: 20px">
<block wx:for="{{ ppts }}" wx:key="id">
<image
src="{{ item.ppt_url }}"
mode="aspectFit"
lazy-load="{{ true }}"
class="ppt-image"
data-src="{{ item.ppt_url }}"
bindtap="previewImage"
></image>
</block>
</view>
</scroll-view>
</van-tab>
</van-tabs>
</view>
</view>
.container {
display: flex;
flex-direction: column;
height: 100vh;
}
.course-tabs {
flex: 1;
overflow: hidden;
}
.play-item {
display: flex;
padding: 10px 0;
}
.play-item .name {
flex: 1;
font-size: 13px;
overflow: hidden;
}
.play-item:last-child {
padding-bottom: 20px;
}
.play-item.is-active {
color: #ba053f;
}
.ppt-image {
width: 100%;
height: 377rpx;
margin-top: 10px;
border-radius: 10px;
}
.ppt-image:first-child {
margin-top: 0;
}
...@@ -7,6 +7,7 @@ Page({ ...@@ -7,6 +7,7 @@ Page({
* 页面的初始数据 * 页面的初始数据
*/ */
data: { data: {
latestVideo: {},
headerInfo: { headerInfo: {
id: '', id: '',
sid: '', sid: '',
...@@ -120,6 +121,21 @@ Page({ ...@@ -120,6 +121,21 @@ Page({
this.setData({ 'headerInfo': json.headerInfo }) this.setData({ 'headerInfo': json.headerInfo })
this.setData({ 'tabs[0].content': json.tabs0Content }) this.setData({ 'tabs[0].content': json.tabs0Content })
this.setData({ 'tabs[1].chapterList': json.tabs1ChapterList }) this.setData({ 'tabs[1].chapterList': json.tabs1ChapterList })
// 设置开始学习的视频
const courseList = json.tabs1ChapterList.course
for (let i = 0; i < courseList.length; i++) {
const children = courseList[i].chapters || []
if (this.data.latestVideo && this.data.latestVideo.vid) {
break
}
for (let k = 0; k < children.length; k++) {
const item = children[k]
if (!json.tabs1ChapterList.currentChapterId || item.vid === json.tabs1ChapterList.currentChapterId) {
this.setData({ latestVideo: item })
break
}
}
}
json.tabs3richTest && this.setData({ 'tabs[3].richText': json.tabs3richTest.replace(/<img.*?(src=["|'].*?["|']).*?>/gi, '<img width="100%" $1>') }) json.tabs3richTest && this.setData({ 'tabs[3].richText': json.tabs3richTest.replace(/<img.*?(src=["|'].*?["|']).*?>/gi, '<img width="100%" $1>') })
CourseApi.getCourseAssess(this.cid, this.sid, (json1) => { CourseApi.getCourseAssess(this.cid, this.sid, (json1) => {
this.setData({ 'tabs[3].assess': json1 }) this.setData({ 'tabs[3].assess': json1 })
...@@ -211,10 +227,10 @@ Page({ ...@@ -211,10 +227,10 @@ Page({
* 开始学习或继续学习 - 跳转到对应音视频播放页 * 开始学习或继续学习 - 跳转到对应音视频播放页
*/ */
startLearn: function (e) { startLearn: function (e) {
let _data = e.target.dataset let _data = this.data.latestVideo
let cid = _data.cid, sid = _data.sid, vid = _data.vid,video_type = _data.type; let cid = _data.cid, sid = _data.sid;
wx.navigateTo({ wx.navigateTo({
url: '/pages/videoPlayer/show' + '?cid=' + cid + '&sid=' + sid + '&vid=' + vid + '&type=' + video_type url: `/pages/course/player?semester_id=${sid}&id=${cid}&chapter_id=${_data.id}`
}) })
}, },
/** /**
...@@ -239,9 +255,8 @@ Page({ ...@@ -239,9 +255,8 @@ Page({
} }
wx.showToast({ title: '请在PC上使用该功能', icon: 'none' }); return ; wx.showToast({ title: '请在PC上使用该功能', icon: 'none' }); return ;
} }
wx.navigateTo({ wx.navigateTo({
url: '/pages/videoPlayer/show' + '?cid=' + cid + '&sid=' + sid + '&vid=' + vid + '&type=' + video_type url: `/pages/course/player?semester_id=${sid}&id=${cid}&chapter_id=${_data.id}`
}) })
}, },
/** /**
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
</button> </button>
<block wx:for='{{_item.chapters}}' wx:key='{{index1}}' wx:for-item='item1' wx:for-index='index1'> <block wx:for='{{_item.chapters}}' wx:key='{{index1}}' wx:for-item='item1' wx:for-index='index1'>
<view class='body {{item1.id === item.chapterList.currentChapterId && "on"}}'> <view class='body {{item1.id === item.chapterList.currentChapterId && "on"}}'>
<view class='name' data-vid='{{item1.vid}}' data-cid='{{item1.cid}}' data-sid='{{item1.sid}}' data-hasVA='{{item1.time}}' data-name='{{item1.name}}' bindtap='jumpToOtherVA' data-index='{{index}}' data-index1='{{index1}}' data-type='{{item1.videoType}}'> <view class='name' data-id='{{item1.id}}' data-vid='{{item1.vid}}' data-cid='{{item1.cid}}' data-sid='{{item1.sid}}' data-hasVA='{{item1.time}}' data-name='{{item1.name}}' bindtap='jumpToOtherVA' data-index='{{index}}' data-index1='{{index1}}' data-type='{{item1.videoType}}'>
{{item1.name}} {{item1.name}}
<view class='time'>{{item1.time}}</view> <view class='time'>{{item1.time}}</view>
</view> </view>
......
<template name='contentAudio'> <template name='contentAudio'>
<view class='content-audio' wx:if="{{status.isAudio}}"> <view class='content-audio'
style='{{status.isSet && status.isAudio ? "display: none;" : "" }} {{status.isImages && status.isAudio ? "display: none;" : "" }} {{status.isVideo ? "display: none;" : "" }}'
>
<image src='./contentAudio/icons/loading_small.gif' class='loading-image {{status.imagesLoaded && "none"}}' mode='aspectFit'/> <image src='./contentAudio/icons/loading_small.gif' class='loading-image {{status.imagesLoaded && "none"}}' mode='aspectFit'/>
<image wx:if='{{image.imgUrls[image.current]}}' src='{{image.imgUrls[image.current]}}' class='slide-image' mode='aspectFill' bindload='imagesLoaded'/> <image wx:if='{{image.imgUrls[image.current]}}' src='{{image.imgUrls[image.current]}}' class='slide-image' mode='aspectFill' bindload='imagesLoaded'/>
<view wx:if='{{!image.imgUrls[image.current]}}' class='slide-image'>本课程暂无PPT</view> <view wx:if='{{!image.imgUrls[image.current]}}' class='slide-image'>本课程暂无PPT</view>
......
<template name='contentVideo'> <template name='contentVideo'>
<!-- 这里加 这么多判断原因,video组件在真机上测试,一定在最上层,其他任何元素都挡不住,只能先隐藏,需要时再显示 --> <!-- 这里加 这么多判断原因,video组件在真机上测试,一定在最上层,其他任何元素都挡不住,只能先隐藏,需要时再显示 -->
<view class='content-video' wx:if="{{status.isVideo}}"> <view class='content-video'
<video id='my-video' class='my-video' src='{{video.src}}' show-center-play-btn='{{false}}' controls='{{false}}' bindtimeupdate='timeUpdate' bindfullscreenchange='beginAndOutFullScreen' enable-progress-gesture='{{!status.disable}}' bindpause='beginPauseVA' bindplay='beginPlayVA' binderror='playError' bindended='playEnded' autoplay='{{true}}'></video> style='{{status.isSet && status.isVideo ? "display: none;" : "" }} {{status.isImages && status.isVideo ? "display: none;" : "" }} {{status.isAudio ? "display: none;" : "" }} {{chapterList.isShow && status.isVideo ? "display: none;" : "" }} {{ status.isLoading ? "display: none": "" }}'
</view> >
</template> <video id='my-video' class='my-video' src='{{video.src}}' show-center-play-btn='{{false}}' controls='{{false}}' bindtimeupdate='timeUpdate' bindfullscreenchange='beginAndOutFullScreen' bindpause='beginPauseVA' bindplay='beginPlayVA' binderror='playError' bindended='playEnded' autoplay='{{true}}'></video>
\ No newline at end of file </view>
</template>
...@@ -132,6 +132,7 @@ Page({ ...@@ -132,6 +132,7 @@ Page({
} }
/* 一定会 存在值 */ /* 一定会 存在值 */
if (res) { if (res) {
console.log('set res', res)
let tempVid = wx.getStorageSync('videoCacheCtrlBar').vid let tempVid = wx.getStorageSync('videoCacheCtrlBar').vid
if (vid == tempVid) { if (vid == tempVid) {
this._cache = wx.getStorageSync('videoCacheCtrlBar') || {} this._cache = wx.getStorageSync('videoCacheCtrlBar') || {}
...@@ -174,12 +175,11 @@ Page({ ...@@ -174,12 +175,11 @@ Page({
let cid = this.data.options.cid, let cid = this.data.options.cid,
sid = this.data.options.sid, sid = this.data.options.sid,
did = 'jjhz92fn0.le2a6c06c9g0.thhg7ekb1f8' did = 'jjhz92fn0.le2a6c06c9g0.thhg7ekb1f8'
ChapterApi.getChapterList(cid, sid, vid, (json) => { ChapterApi.getChapterList(cid, sid, vid, json => {
this.setData({ chapterList: json }) this.setData({ chapterList: json })
}) })
// CC视频走这个接口 // CC视频走这个接口
ChapterApi.getCurrentChapterDetail(vid, videoType, (json) => { ChapterApi.getCurrentChapterDetail(sid, vid, videoType, json => {
// ChapterApi.endVideo()
if (!json.is_locked && this.data.status.disable) { if (!json.is_locked && this.data.status.disable) {
this.setData({ 'status.disable': false }) this.setData({ 'status.disable': false })
} }
...@@ -187,12 +187,17 @@ Page({ ...@@ -187,12 +187,17 @@ Page({
this.setData({ video: json.video }) this.setData({ video: json.video })
this.setData({ audio: json.audio }) this.setData({ audio: json.audio })
this.setData({ image: json.image }) this.setData({ image: json.image })
ChapterApi.getProgress(vid, did, sid, (res) => { ChapterApi.getProgress(vid, did, sid, res => {
console.log('get', res)
if (!res) {
wx.showToast({ title: '获取视频进度失败', icon: 'none' })
}
res.did = did res.did = did
res.vid = vid res.vid = vid
res.cid = cid res.cid = cid
res.sid = sid res.sid = sid
this.initPage(res, vid) this.initPage(res, vid)
ChapterApi.endVideo(res)
}) })
}) })
}, },
...@@ -217,11 +222,11 @@ Page({ ...@@ -217,11 +222,11 @@ Page({
this.isBackend = true this.isBackend = true
this.pauseVA() this.pauseVA()
clearInterval(this.heartbeat) clearInterval(this.heartbeat)
// ChapterApi.endVideo() ChapterApi.endVideo(this.realTimeProgress)
}, },
onUnload: function () { onUnload: function () {
this.pauseVA() this.pauseVA()
// ChapterApi.endVideo() ChapterApi.endVideo(this.realTimeProgress)
}, },
onShow: function () { onShow: function () {
/* 兼容 android 这里发现 android 打开预览大图,然后关闭 自动播放视频,而且关不掉, 兼容android强制关闭视频 */ /* 兼容 android 这里发现 android 打开预览大图,然后关闭 自动播放视频,而且关不掉, 兼容android强制关闭视频 */
...@@ -274,7 +279,7 @@ Page({ ...@@ -274,7 +279,7 @@ Page({
wx.showModal({ wx.showModal({
title: '提示', title: '提示',
content: obj.content, content: obj.content,
success: (res) => { success: res => {
if (res.confirm) { if (res.confirm) {
// wx.setStorageSync(obj.setStorage, obj.getStorage) // wx.setStorageSync(obj.setStorage, obj.getStorage)
let cache = wx.getStorageSync(obj.getStorage) let cache = wx.getStorageSync(obj.getStorage)
...@@ -499,8 +504,7 @@ Page({ ...@@ -499,8 +504,7 @@ Page({
} }
/* iphone6 8.4.1 首次初始化时,不能直接 设置进度并播放 */ /* iphone6 8.4.1 首次初始化时,不能直接 设置进度并播放 */
if (this.isFirstInitAndSwitchVideo) { if (this.isFirstInitAndSwitchVideo) {
if (!this._cache) if (!this._cache) this._cache = wx.getStorageSync('videoCacheCtrlBar') || {}
this._cache = wx.getStorageSync('videoCacheCtrlBar') || {}
this.seekVA(this._cache.initial_time) this.seekVA(this._cache.initial_time)
this.isFirstInitAndSwitchVideo = false this.isFirstInitAndSwitchVideo = false
} }
...@@ -516,6 +520,7 @@ Page({ ...@@ -516,6 +520,7 @@ Page({
playError: function (e) {}, playError: function (e) {},
/* VideoOrAudio标签 - 视频播放发生变化时 - bindtimeupdate事件 */ /* VideoOrAudio标签 - 视频播放发生变化时 - bindtimeupdate事件 */
timeUpdate: function (e) { timeUpdate: function (e) {
console.log('timeupdate', e)
let _data = this.data, let _data = this.data,
_status = _data.status, _status = _data.status,
_detail = e.detail, _detail = e.detail,
...@@ -537,13 +542,11 @@ Page({ ...@@ -537,13 +542,11 @@ Page({
this.setData({ this.setData({
'ctrlBar.totalTime': util.durationToTimeString(_obj.totalTime) 'ctrlBar.totalTime': util.durationToTimeString(_obj.totalTime)
}) })
console.log(_obj.currentTime, _obj.totalTime)
/* 实时改变 当前的PPT当前页 */ /* 实时改变 当前的PPT当前页 */
let arr = this.data.image.timeArr let arr = this.data.image.timeArr
for (let i = 0; i < arr.length; i++) { for (let i = 0; i < arr.length; i++) {
if ( if (_obj.currentTime >= arr[i] && (i + 1 < arr.length ? _obj.currentTime < arr[i + 1] : 1)) {
_obj.currentTime >= arr[i] &&
(i + 1 < arr.length ? _obj.currentTime < arr[i + 1] : 1)
) {
this.setData({ 'image.current': i }) this.setData({ 'image.current': i })
break break
} }
...@@ -566,6 +569,7 @@ Page({ ...@@ -566,6 +569,7 @@ Page({
clearInterval(this.heartbeat) clearInterval(this.heartbeat)
this.heartbeat = setInterval(() => { this.heartbeat = setInterval(() => {
console.log(this.data.initVAFlag)
/* 如果是初始加载状态,就不计算时间状态 和 提交进度了 */ /* 如果是初始加载状态,就不计算时间状态 和 提交进度了 */
if (this.data.initVAFlag) return if (this.data.initVAFlag) return
let arr = _data.ctrlBar.currentTime.split(':'), let arr = _data.ctrlBar.currentTime.split(':'),
......
...@@ -9,11 +9,11 @@ ...@@ -9,11 +9,11 @@
"minified": true, "minified": true,
"newFeature": true, "newFeature": true,
"coverView": true, "coverView": true,
"nodeModules": false, "nodeModules": true,
"autoAudits": false, "autoAudits": false,
"showShadowRootInWxmlPanel": true, "showShadowRootInWxmlPanel": true,
"scopeDataCheck": false, "scopeDataCheck": false,
"uglifyFileName": true, "uglifyFileName": false,
"checkInvalidKey": true, "checkInvalidKey": true,
"checkSiteMap": true, "checkSiteMap": true,
"uploadWithSourceMap": true, "uploadWithSourceMap": true,
......
!function(n){"use strict";function t(n,t){var r=(65535&n)+(65535&t);return(n>>16)+(t>>16)+(r>>16)<<16|65535&r}function r(n,t){return n<<t|n>>>32-t}function e(n,e,o,u,c,f){return t(r(t(t(e,n),t(u,f)),c),o)}function o(n,t,r,o,u,c,f){return e(t&r|~t&o,n,t,u,c,f)}function u(n,t,r,o,u,c,f){return e(t&o|r&~o,n,t,u,c,f)}function c(n,t,r,o,u,c,f){return e(t^r^o,n,t,u,c,f)}function f(n,t,r,o,u,c,f){return e(r^(t|~o),n,t,u,c,f)}function i(n,r){n[r>>5]|=128<<r%32,n[14+(r+64>>>9<<4)]=r;var e,i,a,d,h,l=1732584193,g=-271733879,v=-1732584194,m=271733878;for(e=0;e<n.length;e+=16)i=l,a=g,d=v,h=m,g=f(g=f(g=f(g=f(g=c(g=c(g=c(g=c(g=u(g=u(g=u(g=u(g=o(g=o(g=o(g=o(g,v=o(v,m=o(m,l=o(l,g,v,m,n[e],7,-680876936),g,v,n[e+1],12,-389564586),l,g,n[e+2],17,606105819),m,l,n[e+3],22,-1044525330),v=o(v,m=o(m,l=o(l,g,v,m,n[e+4],7,-176418897),g,v,n[e+5],12,1200080426),l,g,n[e+6],17,-1473231341),m,l,n[e+7],22,-45705983),v=o(v,m=o(m,l=o(l,g,v,m,n[e+8],7,1770035416),g,v,n[e+9],12,-1958414417),l,g,n[e+10],17,-42063),m,l,n[e+11],22,-1990404162),v=o(v,m=o(m,l=o(l,g,v,m,n[e+12],7,1804603682),g,v,n[e+13],12,-40341101),l,g,n[e+14],17,-1502002290),m,l,n[e+15],22,1236535329),v=u(v,m=u(m,l=u(l,g,v,m,n[e+1],5,-165796510),g,v,n[e+6],9,-1069501632),l,g,n[e+11],14,643717713),m,l,n[e],20,-373897302),v=u(v,m=u(m,l=u(l,g,v,m,n[e+5],5,-701558691),g,v,n[e+10],9,38016083),l,g,n[e+15],14,-660478335),m,l,n[e+4],20,-405537848),v=u(v,m=u(m,l=u(l,g,v,m,n[e+9],5,568446438),g,v,n[e+14],9,-1019803690),l,g,n[e+3],14,-187363961),m,l,n[e+8],20,1163531501),v=u(v,m=u(m,l=u(l,g,v,m,n[e+13],5,-1444681467),g,v,n[e+2],9,-51403784),l,g,n[e+7],14,1735328473),m,l,n[e+12],20,-1926607734),v=c(v,m=c(m,l=c(l,g,v,m,n[e+5],4,-378558),g,v,n[e+8],11,-2022574463),l,g,n[e+11],16,1839030562),m,l,n[e+14],23,-35309556),v=c(v,m=c(m,l=c(l,g,v,m,n[e+1],4,-1530992060),g,v,n[e+4],11,1272893353),l,g,n[e+7],16,-155497632),m,l,n[e+10],23,-1094730640),v=c(v,m=c(m,l=c(l,g,v,m,n[e+13],4,681279174),g,v,n[e],11,-358537222),l,g,n[e+3],16,-722521979),m,l,n[e+6],23,76029189),v=c(v,m=c(m,l=c(l,g,v,m,n[e+9],4,-640364487),g,v,n[e+12],11,-421815835),l,g,n[e+15],16,530742520),m,l,n[e+2],23,-995338651),v=f(v,m=f(m,l=f(l,g,v,m,n[e],6,-198630844),g,v,n[e+7],10,1126891415),l,g,n[e+14],15,-1416354905),m,l,n[e+5],21,-57434055),v=f(v,m=f(m,l=f(l,g,v,m,n[e+12],6,1700485571),g,v,n[e+3],10,-1894986606),l,g,n[e+10],15,-1051523),m,l,n[e+1],21,-2054922799),v=f(v,m=f(m,l=f(l,g,v,m,n[e+8],6,1873313359),g,v,n[e+15],10,-30611744),l,g,n[e+6],15,-1560198380),m,l,n[e+13],21,1309151649),v=f(v,m=f(m,l=f(l,g,v,m,n[e+4],6,-145523070),g,v,n[e+11],10,-1120210379),l,g,n[e+2],15,718787259),m,l,n[e+9],21,-343485551),l=t(l,i),g=t(g,a),v=t(v,d),m=t(m,h);return[l,g,v,m]}function a(n){var t,r="",e=32*n.length;for(t=0;t<e;t+=8)r+=String.fromCharCode(n[t>>5]>>>t%32&255);return r}function d(n){var t,r=[];for(r[(n.length>>2)-1]=void 0,t=0;t<r.length;t+=1)r[t]=0;var e=8*n.length;for(t=0;t<e;t+=8)r[t>>5]|=(255&n.charCodeAt(t/8))<<t%32;return r}function h(n){return a(i(d(n),8*n.length))}function l(n,t){var r,e,o=d(n),u=[],c=[];for(u[15]=c[15]=void 0,o.length>16&&(o=i(o,8*n.length)),r=0;r<16;r+=1)u[r]=909522486^o[r],c[r]=1549556828^o[r];return e=i(u.concat(d(t)),512+8*t.length),a(i(c.concat(e),640))}function g(n){var t,r,e="";for(r=0;r<n.length;r+=1)t=n.charCodeAt(r),e+="0123456789abcdef".charAt(t>>>4&15)+"0123456789abcdef".charAt(15&t);return e}function v(n){return unescape(encodeURIComponent(n))}function m(n){return h(v(n))}function p(n){return g(m(n))}function s(n,t){return l(v(n),v(t))}function C(n,t){return g(s(n,t))}function A(n,t,r){return t?r?s(t,n):C(t,n):r?m(n):p(n)}"object"==typeof module&&module.exports?module.exports=A:n.md5=A}(this);
// https://github.com/jashkenas/underscore/blob/master/modules/throttle.js
function now() {
return Date.now() || new Date().getTime()
}
// Returns a function, that, when invoked, will only be triggered at most once
// during a given window of time. Normally, the throttled function will run
// as much as it can, without ever going more than once per `wait` duration;
// but if you'd like to disable the execution on the leading edge, pass
// `{leading: false}`. To disable execution on the trailing edge, ditto.
export default function throttle(func, wait, options) {
var timeout, context, args, result
var previous = 0
if (!options) options = {}
var later = function () {
previous = options.leading === false ? 0 : now()
timeout = null
result = func.apply(context, args)
if (!timeout) context = args = null
}
var throttled = function () {
var _now = now()
if (!previous && options.leading === false) previous = _now
var remaining = wait - (_now - previous)
context = this
args = arguments
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout)
timeout = null
}
previous = _now
result = func.apply(context, args)
if (!timeout) context = args = null
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining)
}
return result
}
throttled.cancel = function () {
clearTimeout(timeout)
previous = 0
timeout = context = args = null
}
return throttled
}
...@@ -79,8 +79,27 @@ const durationToTimeString = duration => { ...@@ -79,8 +79,27 @@ const durationToTimeString = duration => {
* audioCacheCtrlBar * audioCacheCtrlBar
*/ */
// 补零
const formatNumber = n => {
n = n.toString()
return n[1] ? n : '0' + n
}
// 秒转时间
const secondToTime = second => {
const h = Math.floor(second / 3600)
const m = Math.floor((second / 60) % 60)
const s = Math.floor(second % 60)
if (h) {
return `${formatNumber(h)}:${formatNumber(m)}:${formatNumber(s)}`
} else {
return `${formatNumber(m)}:${formatNumber(s)}`
}
}
module.exports = { module.exports = {
config: config, config: config,
requestApi: requestApi, requestApi: requestApi,
durationToTimeString: durationToTimeString durationToTimeString: durationToTimeString,
formatNumber,
secondToTime
} }
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论