Commit 4ec71ac5 by zhanghaozhe

限时免费

parent 65adf51c
import React, { Component } from 'react' import React, { Component } from 'react'
import HeaderBar from '@/common/HeaderBar' import HeaderBar from '@/common/HeaderBar'
import './video.scss' import './video.scss'
import { NavLink, Route, Redirect, Switch, Link } from 'react-router-dom' import { NavLink, Route, Redirect, Switch} from 'react-router-dom'
import { http, getParam, browser } from '@/utils' import { http, getParam, browser } from '@/utils'
import Recommendation from './recommendation' import Recommendation from './recommendation'
import VideoCatalog from './video-catalog' import VideoCatalog from './video-catalog'
...@@ -9,8 +9,8 @@ import DatumCatalog from './datum-catalog' ...@@ -9,8 +9,8 @@ import DatumCatalog from './datum-catalog'
import { Toast } from 'antd-mobile' import { Toast } from 'antd-mobile'
import videojs from 'video.js' import videojs from 'video.js'
import 'video.js/dist/video-js.min.css' import 'video.js/dist/video-js.min.css'
import { Modal } from "antd-mobile" import { Modal} from "antd-mobile"
import { Loading, Popup } from '@/common' import { Loading} from '@/common'
import { connect } from "react-redux" import { connect } from "react-redux"
import jsCookie from 'js-cookie' import jsCookie from 'js-cookie'
import Single from "@/components/detail/single"; import Single from "@/components/detail/single";
...@@ -21,873 +21,896 @@ import './CustomPlayButton' ...@@ -21,873 +21,896 @@ import './CustomPlayButton'
let alert = Modal.alert let alert = Modal.alert
function ProgressShareModal(props) { function ProgressShareModal(props) {
return ( return (
props.isShow && props.isShow &&
<div className='progress-share-modal-wrapper'> <div className='progress-share-modal-wrapper'>
<div className="progress-share-modal"> <div className="progress-share-modal">
<div className="title">每日打卡</div> <div className="title">每日打卡</div>
<ul className="progress-container"> <ul className="progress-container">
<li> <li>
<div className="title">累计学习</div> <div className="title">累计学习</div>
<div className="number"><span className='num'>{props.data.learn_day_count}</span>天</div> <div className="number"><span className='num'>{props.data.learn_day_count}</span>天</div>
</li> </li>
<li> <li>
<div className="title">行动力超过</div> <div className="title">行动力超过</div>
<div className="number"><span className='num'>{parseFloat(props.data.action_power)}</span>% <div className="number"><span className='num'>{parseFloat(props.data.action_power)}</span>%
</div>
</li>
</ul>
<div className="share-container">
<div className="title">分享到</div>
<ul>
<li className='share-icon'>
<a style={{display: 'block'}} href={props.data.url}>
<div className="icon"><i className='iconfont iconweixinzhifu'/></div>
<div className='text'>微信好友</div>
</a>
</li>
<li className='share-icon'>
<a style={{display: 'block'}} href={props.data.url}>
<div className="icon"><i className='iconfont iconpengyouquaniconx'/></div>
<div className='text'>朋友圈</div>
</a>
</li>
</ul>
</div>
<i className="iconfont iconiconfront-2 close" onClick={props.closeShareModal}/>
</div> </div>
</li>
</ul>
<div className="share-container">
<div className="title">分享到</div>
<ul>
<li className='share-icon'>
<a style={{display: 'block'}} href={props.data.url}>
<div className="icon"><i className='iconfont iconweixinzhifu'/></div>
<div className='text'>微信好友</div>
</a>
</li>
<li className='share-icon'>
<a style={{display: 'block'}} href={props.data.url}>
<div className="icon"><i className='iconfont iconpengyouquaniconx'/></div>
<div className='text'>朋友圈</div>
</a>
</li>
</ul>
</div> </div>
) <i className="iconfont iconiconfront-2 close" onClick={props.closeShareModal}/>
</div>
</div>
)
} }
class Video extends Component { class Video extends Component {
video //video element video //video element
player //video player instance player //video player instance
courseID courseID
ws //websocket instance ws //websocket instance
timer timer
token token
count count
watchSec watchSec
previousPlaybackRate = 1 previousPlaybackRate = 1
currentPlaybackRate = 1 currentPlaybackRate = 1
reconnect = true reconnect = true
// timeEnough = false // timeEnough = false
recordSocket recordSocket
recordTimer recordTimer
isCurrentVideoFirstPlay = true isCurrentVideoFirstPlay = true
RECENTLEARN = "recent_learn" RECENTLEARN = "recent_learn"
state = { state = {
title: '', title: '',
courseId: null, courseId: null,
videoList: [], videoList: [],
datum: [], datum: [],
currentVideoSrc: '', currentVideoSrc: '',
activeIndex: 0, activeIndex: 0,
isAuth: true, isAuth: true,
course: {}, // course.course_id 为 0 或 '' 时 为免费课程 course: {}, // course.course_id 为 0 或 '' 时 为免费课程
salePrice: null, salePrice: null,
vCourseId: null, vCourseId: null,
isLoading: true, isLoading: true,
isShowShareModal: false, isShowShareModal: false,
shareData: {}, shareData: {},
singleBox: false, singleBox: false,
singMess: '', singMess: '',
singleType: 1,// 单集购买需要 singleType: 1,// 单集购买需要
nowPrice: 0,// 单集购买需要 nowPrice: 0,// 单集购买需要
laterPrice: 0,// 单集购买需要 laterPrice: 0,// 单集购买需要
limitFreeNoPromptChecked: false,
showLimitFreePopup: true //todo 联调
}
componentDidMount() {
if (window.location.protocol === 'https:') {
window.location.replace('http' + window.location.href.slice(5))
return
} }
this.courseID = getParam('id')
componentDidMount() { if (!this.courseID) {
if (window.location.protocol === 'https:') { this.props.history.replace('/')
window.location.replace('http' + window.location.href.slice(5)) return
return
}
this.courseID = getParam('id')
if (!this.courseID) {
this.props.history.replace('/')
return
}
this.setState({
courseId: this.courseID
})
const {location, location: {state = {}}} = this.props;
if (state.oid) {
this.check(state.oid);
}
if (getParam('is_class') === 1 || getParam('weixinpay')) {
this.payCallback()
}
if (browser.isWeixin) {
this.isweixinPay()
}
this.token = jsCookie.get('token')
this.getVideoList()
this.getDatumCatalog()
this.showLimitFreePopup({
title: '想领取【AI工程师必备干货礼包】? 想深入了解进阶课程? 职业前景不明朗? 资深规划师免费为你服务!',
})
} }
this.setState({
// 直接购买 courseId: this.courseID
tobuy = () => { })
// 详情页单集购买到该页面,url中的id不是课程id const {location, location: {state = {}}} = this.props;
const {course = {}} = this.state; if (state.oid) {
http.get(`${API['base-api']}/m/cart/addtopreorder/[${course.course_id}]`).then((res) => { this.check(state.oid);
if (res.data.errno === 0) {
this.props.history.push(`/order?id=${course.course_id}`, {simple: 1})
} else {
Toast.info(res.data.msg, 2);
}
})
} }
// 购买单集 if (getParam('is_class') === 1 || getParam('weixinpay')) {
toSingleset = (item) => { this.payCallback()
// console.log(item);
this.setState({
singleBox: true,
singleType: 1,
singMess: item
})
window.localStorage.setItem('singMess', JSON.stringify(item))
} }
if (browser.isWeixin) {
// 自组件传给父组件的boxHide this.isweixinPay()
boxHide = (val) => {
this.setState({singleBox: val, singleType: 1})
} }
this.token = jsCookie.get('token')
// 单集购买 H5支付成功后回调 this.getVideoList()
payCallback = () => { this.getDatumCatalog()
const _this = this; }
if (!getParam('oid')) {
return; // 直接购买
} else { tobuy = () => {
this.setState({ // 详情页单集购买到该页面,url中的id不是课程id
singMess: JSON.parse(window.localStorage.getItem('singMess')) const {course = {}} = this.state;
}) http.get(`${API['base-api']}/m/cart/addtopreorder/[${course.course_id}]`).then((res) => {
_this.intervalPayStatus = setInterval(function () { if (res.data.errno === 0) {
http.get(`${API['base-api']}/m/orderState/oid/${getParam('oid')}`).then(res => { this.props.history.push(`/order?id=${course.course_id}`, {simple: 1})
if (res.data.errno === 401) { } else {
clearInterval(_this.intervalPayStatus); Toast.info(res.data.msg, 2);
_this.intervalPayStatus = null; }
// 获取课程类型 })
http.get(`${API['base-api']}/class_order_status/${getParam('oid')}`).then((res) => { }
if (Number(res.data.data.errno) === 200) { // 购买单集
// 正常购买单集成功 toSingleset = (item) => {
_this.setState({ // console.log(item);
singleType: 6, this.setState({
}) singleBox: true,
} else if (Number(res.data.data.errno) === 201) { singleType: 1,
// 0元参团 singMess: item
_this.setState({ })
singleType: 4, window.localStorage.setItem('singMess', JSON.stringify(item))
}) }
} else if (Number(res.data.data.errno) === 202) {
// 0元购 // 自组件传给父组件的boxHide
_this.setState({ boxHide = (val) => {
singleType: 3, this.setState({singleBox: val, singleType: 1})
}) }
} else if (Number(res.data.data.errno) === 203) {
// 三天内特价 // 单集购买 H5支付成功后回调
_this.setState({ payCallback = () => {
nowPrice: res.data.data.data.now_price, const _this = this;
laterPrice: res.data.data.data.three_day_later_price, if (!getParam('oid')) {
singleType: 2, return;
}) } else {
} else { this.setState({
Toast.info(res.data.data.msg, 2) singMess: JSON.parse(window.localStorage.getItem('singMess'))
} })
}) _this.intervalPayStatus = setInterval(function () {
} http.get(`${API['base-api']}/m/orderState/oid/${getParam('oid')}`).then(res => {
}) if (res.data.errno === 401) {
}, 1000) clearInterval(_this.intervalPayStatus);
} _this.intervalPayStatus = null;
}; // 获取课程类型
// 单集购买 微信内支付成功后回调 http.get(`${API['base-api']}/class_order_status/${getParam('oid')}`).then((res) => {
isweixinPay = () => { if (Number(res.data.data.errno) === 200) {
let _this = this;
let weixin_code = getParam('code');
if (weixin_code) {
if (!getParam('oid')) {
return
} else {
this.setState({
singMess: JSON.parse(window.localStorage.getItem('singMess'))
})
// this.props.weixinPay(weixin_code)
http.get(`${API['base-api']}/pay/wxpay/pub_charge/oid/${getParam('oid')}/code/${weixin_code}`).then((res) => {
if (res.data.errno === 0) {
const data = res.data.data;
function onBridgeReady() {
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId": data.appId, //公众号名称,由商户传入
"timeStamp": data.timeStamp, //时间戳,自1970年以来的秒数
"nonceStr": data.nonceStr, //随机串
"package": data.package,
"signType": data.signType, //微信签名方式:
"paySign": data.paySign //微信签名
},
function (res) {
if (res.err_msg == "get_brand_wcpay_request:ok") {
Toast.info('支付成功', 2);
_this.intervalPayStatus = setInterval(function () {
http.get(`${API['base-api']}/m/orderState/oid/${getParam('oid')}`).then(res => {
if (res.data.errno === 401) {
clearInterval(_this.intervalPayStatus);
_this.intervalPayStatus = null;
// 获取课程类型
http.get(`${API['base-api']}/class_order_status/${getParam('oid')}`).then((res) => {
if (Number(res.data.data.errno) === 200) {
// 正常购买单集成功
_this.setState({
singleType: 6,
})
} else if (Number(res.data.data.errno) === 201) {
// 0元参团
_this.setState({
singleType: 4,
})
} else if (Number(res.data.data.errno) === 202) {
// 0元购
_this.setState({
singleType: 3,
})
} else if (Number(res.data.data.errno) === 203) {
// 三天内特价
_this.setState({
nowPrice: res.data.data.data.now_price,
laterPrice: res.data.data.data.three_day_later_price,
singleType: 2,
})
} else {
Toast.info(res.data.data.msg, 2)
}
})
}
})
}, 1000)
} else {
alert('支付失败')
}
}
)
}
if (typeof WeixinJSBridge == "undefined") {
if (document.addEventListener) {
document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false)
} else if (document.attachEvent) {
document.attachEvent('WeixinJSBridgeReady', onBridgeReady)
document.attachEvent('onWeixinJSBridgeReady', onBridgeReady)
}
} else {
onBridgeReady()
}
} else {
Toast.info(res.data.msg, 2)
}
})
}
}
};
// 判断支付是否成功
check = (oid) => {
this.setState({
singMess: JSON.parse(window.localStorage.getItem('singMess'))
})
http.get(`${API['base-api']}/class_order_status/${oid}`).then((res) => {
if (Number(res.data.data.errno) === 200) {
// 正常购买单集成功 // 正常购买单集成功
this.setState({ _this.setState({
singleType: 6, singleType: 6,
}) })
} else if (Number(res.data.data.errno) === 201) { } else if (Number(res.data.data.errno) === 201) {
// 0元参团 // 0元参团
this.setState({ _this.setState({
singleType: 4, singleType: 4,
}) })
} else if (Number(res.data.data.errno) === 202) { } else if (Number(res.data.data.errno) === 202) {
// 0元购 // 0元购
this.setState({ _this.setState({
singleType: 3, singleType: 3,
}) })
} else if (Number(res.data.data.errno) === 203) { } else if (Number(res.data.data.errno) === 203) {
// 三天内特价 // 三天内特价
this.setState({ _this.setState({
nowPrice: res.data.data.data.now_price, nowPrice: res.data.data.data.now_price,
laterPrice: res.data.data.data.three_day_later_price, laterPrice: res.data.data.data.three_day_later_price,
singleType: 2, singleType: 2,
}) })
} else { } else {
Toast.info(res.data.data.msg, 2) Toast.info(res.data.data.msg, 2)
} }
})
}
}) })
}, 1000)
} }
};
// 9502 初始化 监听事件 // 单集购买 微信内支付成功后回调
setupWS = () => { isweixinPay = () => {
this.ws = new WebSocket(API["process-api"]); let _this = this;
this.ws.addEventListener('error', () => { let weixin_code = getParam('code');
this.ws = null if (weixin_code) {
}) if (!getParam('oid')) {
this.ws.addEventListener('close', () => { return
if (this.reconnect) { } else {
this.ws = null this.setState({
setTimeout(() => { singMess: JSON.parse(window.localStorage.getItem('singMess'))
this.setupWS();
}, 1000)
}
clearInterval(this.timer)
this.timer = null;
}) })
this.ws.addEventListener('message', e => { // this.props.weixinPay(weixin_code)
const data = JSON.parse(e.data); http.get(`${API['base-api']}/pay/wxpay/pub_charge/oid/${getParam('oid')}/code/${weixin_code}`).then((res) => {
data.code == 4040 && (this.reconnect = false); if (res.data.errno === 0) {
if(data.code === 0) { const data = res.data.data;
console.log("上次的学习记录" + JSON.stringify(data));
if(data.data && data.data.position) { function onBridgeReady() {
this.player.currentTime(data.data.position); WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId": data.appId, //公众号名称,由商户传入
"timeStamp": data.timeStamp, //时间戳,自1970年以来的秒数
"nonceStr": data.nonceStr, //随机串
"package": data.package,
"signType": data.signType, //微信签名方式:
"paySign": data.paySign //微信签名
},
function (res) {
if (res.err_msg == "get_brand_wcpay_request:ok") {
Toast.info('支付成功', 2);
_this.intervalPayStatus = setInterval(function () {
http.get(`${API['base-api']}/m/orderState/oid/${getParam('oid')}`).then(res => {
if (res.data.errno === 401) {
clearInterval(_this.intervalPayStatus);
_this.intervalPayStatus = null;
// 获取课程类型
http.get(`${API['base-api']}/class_order_status/${getParam('oid')}`).then((res) => {
if (Number(res.data.data.errno) === 200) {
// 正常购买单集成功
_this.setState({
singleType: 6,
})
} else if (Number(res.data.data.errno) === 201) {
// 0元参团
_this.setState({
singleType: 4,
})
} else if (Number(res.data.data.errno) === 202) {
// 0元购
_this.setState({
singleType: 3,
})
} else if (Number(res.data.data.errno) === 203) {
// 三天内特价
_this.setState({
nowPrice: res.data.data.data.now_price,
laterPrice: res.data.data.data.three_day_later_price,
singleType: 2,
})
} else {
Toast.info(res.data.data.msg, 2)
}
})
}
})
}, 1000)
} else {
alert('支付失败')
}
} }
)
} }
})
}
sendMessage = message => { if (typeof WeixinJSBridge == "undefined") {
let readyState = this.ws.readyState, _this = this; if (document.addEventListener) {
if(readyState === 1) { document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false)
this.ws && this.ws.send(JSON.stringify(message)) } else if (document.attachEvent) {
}else if(readyState === 3) { document.attachEvent('WeixinJSBridgeReady', onBridgeReady)
this.ws.close(); document.attachEvent('onWeixinJSBridgeReady', onBridgeReady)
this.ws = null; }
let reconnect = setTimeout(function() { } else {
clearTimeout(reconnect); onBridgeReady()
reconnect = null; }
_this.ws = new WebSocket(PROCESS_URL); } else {
}, 500); Toast.info(res.data.msg, 2)
} }
})
}
} }
};
// 判断支付是否成功
check = (oid) => {
this.setState({
singMess: JSON.parse(window.localStorage.getItem('singMess'))
})
http.get(`${API['base-api']}/class_order_status/${oid}`).then((res) => {
if (Number(res.data.data.errno) === 200) {
// 正常购买单集成功
this.setState({
singleType: 6,
//视频结束请求接口 })
getShareProgressInfo = () => { } else if (Number(res.data.data.errno) === 201) {
http.get(`${API['base-api']}/m/aist/share_data/${this.courseID}/${this.state.videoList[this.state.activeIndex]['id']}`) // 0元参团
.then(res => { this.setState({
const {data} = res singleType: 4,
if (data.errno == 200) {
this.setState({shareData: data.data, isShowShareModal: true})
}
})
}
//告诉服务端计算进度
countSchedule = () => {
const {videoList, activeIndex, vCourseId, course = {}} = this.state
if (Number(course.course_id) === 0 || course.course_id === '') {
console.log('免费课程 拦截');
return;
}
let ctype = 0;
if (course.is_aist) {
ctype = 2;
}
// 计算进度 根据ctype判断 课程类型 0-视频 1-直播 2-AI特训营
this.sendMessage({
mtype: 'count_schedule',
uid: this.props.user.data.uid,
token: this.token,
platform: 5,
video_id: videoList[activeIndex]['id'],
course_id: this.state.courseId,
v_course_id: vCourseId,
ctype: ctype,
}) })
} else if (Number(res.data.data.errno) === 202) {
// 0元购
this.setState({
singleType: 3,
}
// 发送时间消息
sendWatchTime = (sec, rate) => {
const {videoList, activeIndex, vCourseId, course = {}} = this.state
// 免费课程不发送
// if (Number(course.course_id) === 0 || course.course_id === '') {
// console.log('免费课程 拦截');
// return;
// }
// 时间为0 不发送消息
if (Number(sec) === 0) {
return;
}
let ctype = 0;
if (course.is_aist) {
ctype = 2;
}
// 时间足够不发送
// if(this.timeEnough) {
// console.log('5001 时间足够');
// return;
// }
this.sendMessage({
mtype: 'watch_time',
rate,
time: sec,
video_id: videoList[activeIndex]['id'],
course_id: this.state.courseId,
v_course_id: vCourseId,
uid: this.props.user.data.uid,
token: this.token,
platform: 5,
position: parseInt(this.player.currentTime()),
ctype: ctype,
}) })
} } else if (Number(res.data.data.errno) === 203) {
// 三天内特价
this.setState({
nowPrice: res.data.data.data.now_price,
laterPrice: res.data.data.data.three_day_later_price,
singleType: 2,
setupTimer = () => { })
this.count = 0 } else {
this.watchSec = 0 Toast.info(res.data.data.msg, 2)
clearInterval(this.timer) }
this.timer = null; })
this.timer = setInterval(() => { }
if (this.player && this.player.player()) {
if (this.count === 5) { // 9502 初始化 监听事件
this.sendWatchTime(this.watchSec, this.currentPlaybackRate) setupWS = () => {
this.count = this.watchSec = 0 this.ws = new WebSocket(API["process-api"]);
} else { this.ws.addEventListener('error', () => {
!this.player.paused() && this.watchSec++ this.ws = null
!this.player.paused() && this.count++ })
} this.ws.addEventListener('close', () => {
} if (this.reconnect) {
this.ws = null
setTimeout(() => {
this.setupWS();
}, 1000) }, 1000)
}
clearInterval(this.timer)
this.timer = null;
})
this.ws.addEventListener('message', e => {
const data = JSON.parse(e.data);
data.code == 4040 && (this.reconnect = false);
if (data.code === 0) {
console.log("上次的学习记录" + JSON.stringify(data));
if (data.data && data.data.position) {
this.player.currentTime(data.data.position);
}
}
})
}
sendMessage = message => {
let readyState = this.ws.readyState, _this = this;
if (readyState === 1) {
this.ws && this.ws.send(JSON.stringify(message))
} else if (readyState === 3) {
this.ws.close();
this.ws = null;
let reconnect = setTimeout(function () {
clearTimeout(reconnect);
reconnect = null;
_this.ws = new WebSocket(PROCESS_URL);
}, 500);
} }
}
// 初始化视频播放器
initializePlayer = () => { //视频结束请求接口
window.HELP_IMPROVE_VIDEOJS = false; getShareProgressInfo = () => {
this.player = videojs(this.video, { http.get(`${API['base-api']}/m/aist/share_data/${this.courseID}/${this.state.videoList[this.state.activeIndex]['id']}`)
controls: true, .then(res => {
preload: 'auto', const {data} = res
bigPlayButton: false, if (data.errno == 200) {
textTrackDisplay: false, this.setState({shareData: data.data, isShowShareModal: true})
posterImage: false, }
errorDisplay: false, })
playbackRates: ['0.75', '1', '1.5', '2'], }
controlBar: {
pictureInPictureToggle: false //告诉服务端计算进度
} countSchedule = () => {
}) const {videoList, activeIndex, vCourseId, course = {}} = this.state
this.player.addChild('CustomPlayButtonCover') if (Number(course.course_id) === 0 || course.course_id === '') {
this.player.on('play', () => { console.log('免费课程 拦截');
const {videoList, activeIndex, vCourseId, course = {}} = this.state return;
// 当视频播放时 看是否是第一次播放(初次进入页面 刷新页面 切换视频 都是第一次播放 需要获取上次的播放时间)
if(this.isCurrentVideoFirstPlay) {
// 当某些原因导致视频暂停时(用户暂停 网络不好等) 再播放时不需要发送
this.isCurrentVideoFirstPlay = false;
// 发送消息 recent_learn
this.ws.send(JSON.stringify({
mtype: this.RECENTLEARN,
uid: this.props.user.data.uid,
token: this.token,
platform: 5,
video_id: videoList[activeIndex]['id'],
course_id: this.state.courseId,
v_course_id: vCourseId,
is_live: 0,
}))
}
if(!this.timer) {
this.setupTimer();
}
})
this.player.on('ratechange', () => {
this.currentPlaybackRate = this.player.playbackRate()
this.sendWatchTime(this.watchSec, this.previousPlaybackRate)
this.count = this.watchSec = 0
this.previousPlaybackRate = this.currentPlaybackRate
})
this.player.on('ended', () => {
this.sendWatchTime(this.watchSec, this.currentPlaybackRate);
this.count = this.watchSec = 0;
this.countSchedule(); // 计算进度 -- 播放完毕
// 返现课程才出现打卡记录
if(this.state.course.is_aist) {
this.getShareProgressInfo()
}
clearInterval(this.timer);
this.timer = null;
})
} }
let ctype = 0;
sendLastRecord = () => { if (course.is_aist) {
http.post(`${API.home}/m/course/record_last_video`, { ctype = 2;
v_course_id: this.state.course['v_course_id'],
video_id: this.state.videoList[this.state.activeIndex].id
})
} }
// 计算进度 根据ctype判断 课程类型 0-视频 1-直播 2-AI特训营
componentWillUnmount() { this.sendMessage({
this.player && this.player.dispose() mtype: 'count_schedule',
uid: this.props.user.data.uid,
clearInterval(this.timer) token: this.token,
this.timer = null; platform: 5,
this.ws && this.ws.close() video_id: videoList[activeIndex]['id'],
this.ws = null course_id: this.state.courseId,
v_course_id: vCourseId,
clearInterval(this.recordTimer) ctype: ctype,
this.recordSocket && this.recordSocket.close() })
this.recordSocket = null
}
// 发送时间消息
sendWatchTime = (sec, rate) => {
const {videoList, activeIndex, vCourseId, course = {}} = this.state
// 免费课程不发送
// if (Number(course.course_id) === 0 || course.course_id === '') {
// console.log('免费课程 拦截');
// return;
// }
// 时间为0 不发送消息
if (Number(sec) === 0) {
return;
} }
let ctype = 0;
// 选择新的视频 if (course.is_aist) {
selectVideo = index => { ctype = 2;
if (index === this.state.activeIndex) { }
return // 时间足够不发送
// if(this.timeEnough) {
// console.log('5001 时间足够');
// return;
// }
this.sendMessage({
mtype: 'watch_time',
rate,
time: sec,
video_id: videoList[activeIndex]['id'],
course_id: this.state.courseId,
v_course_id: vCourseId,
uid: this.props.user.data.uid,
token: this.token,
platform: 5,
position: parseInt(this.player.currentTime()),
ctype: ctype,
})
}
setupTimer = () => {
this.count = 0
this.watchSec = 0
clearInterval(this.timer)
this.timer = null;
this.timer = setInterval(() => {
if (this.player && this.player.player()) {
if (this.count === 5) {
this.sendWatchTime(this.watchSec, this.currentPlaybackRate)
this.count = this.watchSec = 0
} else {
!this.player.paused() && this.watchSec++
!this.player.paused() && this.count++
} }
console.log('selectVideo 先发送时间 再发送进度 在重置定时器'); }
this.isCurrentVideoFirstPlay = true; // 切换视频则重置这个变量 因为新视频肯定是首次播放 }, 1000)
this.sendWatchTime(this.watchSec, this.currentPlaybackRate)
this.countSchedule(); // 计算进度 -- 选择新视频(可能是M端特有的) }
// 初始化视频播放器
initializePlayer = () => {
window.HELP_IMPROVE_VIDEOJS = false;
this.player = videojs(this.video, {
controls: true,
preload: 'auto',
bigPlayButton: false,
textTrackDisplay: false,
posterImage: false,
errorDisplay: false,
playbackRates: ['0.75', '1', '1.5', '2'],
controlBar: {
pictureInPictureToggle: false
}
})
this.player.addChild('CustomPlayButtonCover')
this.player.on('play', () => {
const {videoList, activeIndex, vCourseId, course = {}} = this.state
// 当视频播放时 看是否是第一次播放(初次进入页面 刷新页面 切换视频 都是第一次播放 需要获取上次的播放时间)
if (this.isCurrentVideoFirstPlay) {
// 当某些原因导致视频暂停时(用户暂停 网络不好等) 再播放时不需要发送
this.isCurrentVideoFirstPlay = false;
// 发送消息 recent_learn
this.ws.send(JSON.stringify({
mtype: this.RECENTLEARN,
uid: this.props.user.data.uid,
token: this.token,
platform: 5,
video_id: videoList[activeIndex]['id'],
course_id: this.state.courseId,
v_course_id: vCourseId,
is_live: 0,
}))
}
if (!this.timer) {
this.setupTimer(); this.setupTimer();
}
this.setState( })
{ this.player.on('ratechange', () => {
activeIndex: index this.currentPlaybackRate = this.player.playbackRate()
}, this.sendWatchTime(this.watchSec, this.previousPlaybackRate)
() => { this.count = this.watchSec = 0
this.previousPlaybackRate = this.currentPlaybackRate
if (this.hasAuth(this.state.activeIndex)) { })
this.setPlayerSrc(this.state.videoList[index]['play_url']) this.player.on('ended', () => {
this.sendLastRecord() this.sendWatchTime(this.watchSec, this.currentPlaybackRate);
this.playVideo() this.count = this.watchSec = 0;
} else { this.countSchedule(); // 计算进度 -- 播放完毕
this.getCoursePrice(); // 返现课程才出现打卡记录
} if (this.state.course.is_aist) {
} this.getShareProgressInfo()
); }
clearInterval(this.timer);
this.timer = null;
})
}
sendLastRecord = () => {
http.post(`${API.home}/m/course/record_last_video`, {
v_course_id: this.state.course['v_course_id'],
video_id: this.state.videoList[this.state.activeIndex].id
})
}
componentWillUnmount() {
this.player && this.player.dispose()
clearInterval(this.timer)
this.timer = null;
this.ws && this.ws.close()
this.ws = null
clearInterval(this.recordTimer)
this.recordSocket && this.recordSocket.close()
this.recordSocket = null
}
// 选择新的视频
selectVideo = index => {
if (index === this.state.activeIndex) {
return
} }
console.log('selectVideo 先发送时间 再发送进度 在重置定时器');
getLastVideoIndex = lastIndex => { this.isCurrentVideoFirstPlay = true; // 切换视频则重置这个变量 因为新视频肯定是首次播放
return this.state.videoList.findIndex(item => item.id == lastIndex) this.sendWatchTime(this.watchSec, this.currentPlaybackRate)
} this.countSchedule(); // 计算进度 -- 选择新视频(可能是M端特有的)
this.setupTimer();
getVideoList = () => {
let url = ''; this.setState(
if (getParam('video_id')) { {
url = `${API.home}/m/course/play/${this.courseID + '?video_id=' + getParam('video_id')}` activeIndex: index
http.post(`${API['base-api']}/sys/get_class_audition`, { },
video_id: getParam('video_id') () => {
})
if (this.hasAuth(this.state.activeIndex)) {
this.setPlayerSrc(this.state.videoList[index]['play_url'])
this.sendLastRecord()
this.playVideo()
} else { } else {
url = `${API.home}/m/course/play/${this.courseID}` this.getCoursePrice();
} }
http.get(url).then(res => { }
const {data = {}, code} = res.data; );
if (code === 200) { }
this.setState(
state => ({ getLastVideoIndex = lastIndex => {
videoList: data['lessons'], return this.state.videoList.findIndex(item => item.id == lastIndex)
currentVideoSrc: data['lessons'][state.activeIndex]['play_url'], }
course: data.course,
courseId: data.course['course_id'], getVideoList = () => {
vCourseId: data.course['v_course_id'], let url = '';
title: data.course['course_title'], if (getParam('video_id')) {
isLoading: false url = `${API.home}/m/course/play/${this.courseID + '?video_id=' + getParam('video_id')}`
}), http.post(`${API['base-api']}/sys/get_class_audition`, {
this.playSetup video_id: getParam('video_id')
) })
} else { } else {
Toast.info(data.msg) url = `${API.home}/m/course/play/${this.courseID}`
}
}
)
} }
http.get(url).then(res => {
playSetup = () => { const {data = {}, code} = res.data;
// is_aist,是否AI特训营 if (code === 200) {
const {course = {}} = this.state; this.setState(
// if (Number(course.course_id) === 0 || course.course_id === '') { state => ({
// console.log('免费课程 拦截'); videoList: data['lessons'],
// }else{ currentVideoSrc: data['lessons'][state.activeIndex]['play_url'],
let _this = this; course: data.course,
this.setupWS(); courseId: data.course['course_id'],
this.setupTimer(); vCourseId: data.course['v_course_id'],
let scheduleTime = setTimeout(function () { title: data.course['course_title'],
clearTimeout(scheduleTime); isLoading: false
scheduleTime = null; }),
_this.countSchedule(); // 刚进入页面的时候 就计算进度 先获取视频列表getVideoList 获取列表后 播放选择的视频 然后计算进度 this.playSetup
}, 1000); )
// } } else {
let index = this.getLastVideoIndex(course.last_video_id); Toast.info(data.msg)
index = index >= 0 ? index : 0; }
this.setState( }
{ )
activeIndex: index }
},
() => { playSetup = () => {
if (this.lessonAvailable(index)) { // is_aist,是否AI特训营
if (this.hasAuth(index)) { const {course = {}} = this.state;
Promise.resolve().then(() => { // if (Number(course.course_id) === 0 || course.course_id === '') {
this.initializePlayer() // console.log('免费课程 拦截');
this.playWithAuth() // }else{
}) let _this = this;
} else { this.setupWS();
this.getCoursePrice(); this.setupTimer();
} let scheduleTime = setTimeout(function () {
} else { clearTimeout(scheduleTime);
alert('暂无视频', '', [{ scheduleTime = null;
text: 'OK', _this.countSchedule(); // 刚进入页面的时候 就计算进度 先获取视频列表getVideoList 获取列表后 播放选择的视频 然后计算进度
onPress: () => { }, 1000);
this.props.history.push('/') // }
} let index = this.getLastVideoIndex(course.last_video_id);
}]) index = index >= 0 ? index : 0;
} this.setState(
{
activeIndex: index
},
() => {
if (this.lessonAvailable(index)) {
if (this.hasAuth(index)) {
Promise.resolve().then(() => {
this.initializePlayer()
this.playWithAuth()
})
} else {
this.getCoursePrice();
}
} else {
alert('暂无视频', '', [{
text: 'OK',
onPress: () => {
this.props.history.push('/')
} }
); }])
}
setPlayerSrc = src => {
if (!this.player) {
this.initializePlayer()
} }
this.player.src({ }
src, );
type: 'application/x-mpegURL'
})
}
playVideo = () => { }
this.player.ready(() => {
this.player.play()
})
setPlayerSrc = src => {
if (!this.player) {
this.initializePlayer()
} }
this.player.src({
src,
type: 'application/x-mpegURL'
})
}
playVideo = () => {
this.player.ready(() => {
this.player.play()
})
getDatumCatalog() { }
http.get(`${API.home}/m/course/data/${this.courseID}`)
.then(res => {
const data = res.data
if (data.code === 200) {
this.setState({
datum: data.data
})
} else { getDatumCatalog() {
Toast.info(data.msg) http.get(`${API.home}/m/course/data/${this.courseID}`)
} .then(res => {
}) const data = res.data
} if (data.code === 200) {
lessonAvailable = index => { this.setState({
return this.state.videoList[index]['video_size'] !== 0 datum: data.data
} })
getCoursePrice = () => { } else {
const {course = {}} = this.state; Toast.info(data.msg)
http.get(`${API.home}/sys/course/price/${course.course_id}`) }
.then(res => { })
const {data} = res }
if (data.code === 200) {
this.setState({ lessonAvailable = index => {
salePrice: data.data['sale_price'] return this.state.videoList[index]['video_size'] !== 0
}) }
}
}) getCoursePrice = () => {
} const {course = {}} = this.state;
http.get(`${API.home}/sys/course/price/${course.course_id}`)
.then(res => {
const {data} = res
if (data.code === 200) {
this.setState({
salePrice: data.data['sale_price']
})
}
})
}
playWithAuth = () => { playWithAuth = () => {
const {videoList, activeIndex} = this.state const {videoList, activeIndex} = this.state
if (this.hasAuth(activeIndex)) { if (this.hasAuth(activeIndex)) {
this.setPlayerSrc(videoList[activeIndex]['play_url']) this.setPlayerSrc(videoList[activeIndex]['play_url'])
}
} }
}
hasAuth = index => { hasAuth = index => {
const {videoList} = this.state const {videoList} = this.state
let lesson = videoList[index] let lesson = videoList[index]
if (lesson['video_auth']) { if (lesson['video_auth']) {
this.setState({ this.setState({
isAuth: true isAuth: true
}) })
return true return true
} else { } else {
this.setState({ this.setState({
isAuth: false isAuth: false
}) })
return false return false
}
} }
}
showLimitFreePopup = (title, id) => {
Popup({ render() {
title: <span>想领取【AI工程师必备干货礼包】? 想深入了解进阶课程? 职业前景不明朗? 资深规划师免费为你服务!</span>, let {match, location, history} = this.props
className: 'free-popup', const {videoList, activeIndex, isAuth, salePrice, course, singleBox, singleType, showLimitFreePopup} = this.state;
content: <div className={'des'}> let toHref = '';
<img className="qrcode" src="https://julyedu-cdn.oss-cn-beijing.aliyuncs.com/tinypng-common/right_weixin.png"/> if (location.state && location.state.to && location.state.to === 'detail') {
</div> toHref = `/detail?id=${course.course_id}`
})
} }
return (
render() { <div className='play'>
let {match, location, history} = this.props <HeaderBar title={this.state.title} arrow={true} toHref={() => {
const {videoList, activeIndex, isAuth, salePrice, course, singleBox, singleType} = this.state; toHref ? history.push(
let toHref = ''; toHref,
if (location.state && location.state.to && location.state.to === 'detail') { {
toHref = `/detail?id=${course.course_id}` to: 'classify'
} }
return ( ) : history.go(-1)
<div className='play'> }}/>
<HeaderBar title={this.state.title} arrow={true} toHref={() => { <Loading isLoading={this.state.isLoading}>
toHref ? history.push( <div className="video">
toHref, <video className={'video-js'} ref={el => this.video = el}
{ webkit-playsinline="true"
to: 'classify' playsInline={true}
} x-webkit-airplay="allow"
) : history.go(-1) x5-video-player-type="h5">
}}/> <source src={'/'} type='application/x-mpegURL'/>
<Loading isLoading={this.state.isLoading}> </video>
<div className="video"> {
<video className={'video-js'} ref={el => this.video = el} !isAuth && !!videoList[activeIndex]['is_class'] && (
webkit-playsinline="true" <div className="purchase-box">
playsInline={true} <div className='hint'>您尚未购买该课时,请购买后学习。</div>
x-webkit-airplay="allow" <div className='btns'>
x5-video-player-type="h5"> <button
<source src={'/'} type='application/x-mpegURL'/> type='button'
</video> onClick={this.tobuy}
{ className='purchase-class'
!isAuth && !!videoList[activeIndex]['is_class'] && ( >
<div className="purchase-box"> ¥{salePrice} 购买课程
<div className='hint'>您尚未购买该课时,请购买后学习。</div> </button>
<div className='btns'> <button
<button type='button'
type='button' onClick={this.toSingleset.bind(this, videoList[activeIndex])}
onClick={this.tobuy} className='purchase-episode'
className='purchase-class' >
> ¥{videoList.length && videoList[activeIndex]['class_price']} 购买单集
¥{salePrice} 购买课程 </button>
</button> </div>
<button </div>
type='button' )
onClick={this.toSingleset.bind(this, videoList[activeIndex])} }
className='purchase-episode' {
> !isAuth && !!course.is_aist && (
¥{videoList.length && videoList[activeIndex]['class_price']} 购买单集 <div className="is-aist-box">
</button> <i className={'iconfont iconiconfront-21'}></i>
</div> <p className={'time'}>{videoList[activeIndex]['aist_start_time']}</p>
</div> <p className={'time'}>请耐心等待...</p>
) </div>
} )
{ }
!isAuth && !!course.is_aist && ( </div>
<div className="is-aist-box"> <div className='tab'>
<i className={'iconfont iconiconfront-21'}></i> <div>
<p className={'time'}>{videoList[activeIndex]['aist_start_time']}</p> <NavLink to={{pathname: `${match.url}/video`, search: `?id=${this.courseID}`}}
<p className={'time'}>请耐心等待...</p> replace
</div> activeClassName='active'
) >视频</NavLink>
} </div>
</div> <div>
<div className='tab'> <NavLink to={{pathname: `${match.url}/datum`, search: `?id=${this.courseID}`}}
<div> replace
<NavLink to={{pathname: `${match.url}/video`, search: `?id=${this.courseID}`}} activeClassName='active'
replace >资料</NavLink>
activeClassName='active' </div>
>视频</NavLink> </div>
</div>
<div> {/*单集购买*/}
<NavLink to={{pathname: `${match.url}/datum`, search: `?id=${this.courseID}`}} {
replace singleBox &&
activeClassName='active' <Single
>资料</NavLink> courseId={course.course_id}
</div> singleBox={this.state.singleBox}
</div> boxHide={this.boxHide}
data={this.state.singMess}
{/*单集购买*/} singleType={this.state.singleType}
{ vcourseId={course.v_course_id}
singleBox && videoId={this.state.singMess.video_id}
<Single check={this.check}
courseId={course.course_id} title={this.state.singMess.course_tile}/>
singleBox={this.state.singleBox} }
boxHide={this.boxHide} {/* 单集购买成功 */}
data={this.state.singMess} {
singleType={this.state.singleType} singleType !== 1 &&
vcourseId={course.v_course_id} <SingleSuccess
videoId={this.state.singMess.video_id} courseId={course.course_id}
check={this.check} boxHide={this.boxHide}
title={this.state.singMess.course_tile}/> data={this.state.singMess}
} singleType={singleType}
{/* 单集购买成功 */} vcourseId={course.v_course_id}
{ videoId={this.state.singMess.video_id}
singleType !== 1 && nowPrice={this.state.nowPrice}
<SingleSuccess laterPrice={this.state.laterPrice}
courseId={course.course_id} />
boxHide={this.boxHide} }
data={this.state.singMess}
singleType={singleType} </Loading>
vcourseId={course.v_course_id} <Switch>
videoId={this.state.singMess.video_id} <Redirect exact from={'/play'} to={{
nowPrice={this.state.nowPrice} pathname: '/play/video',
laterPrice={this.state.laterPrice} search: location.search
/> }}/>
} <Route
path={`${match.path}/video`}
</Loading> render={props => {
<Switch> return (
<Redirect exact from={'/play'} to={{ <VideoCatalog
pathname: '/play/video', activeIndex={this.state.activeIndex}
search: location.search selectVideo={this.selectVideo}
}}/> videoCatalog={videoList}
<Route {...props}
path={`${match.path}/video`}
render={props => {
return (
<VideoCatalog
activeIndex={this.state.activeIndex}
selectVideo={this.selectVideo}
videoCatalog={videoList}
{...props}
/>
);
}}
/>
<Route path={`${match.path}/datum`} render={props => {
return <DatumCatalog {...props} datum={this.state.datum}/>
}}/>
</Switch>
<Route render={props => {
return this.state.vCourseId ? <Recommendation {...props} vCourseId={this.state.vCourseId}/>
: null
}}/>
<ProgressShareModal isShow={this.state.isShowShareModal}
closeShareModal={() => this.setState({isShowShareModal: false})}
data={this.state.shareData}
/> />
);
}}
/>
<Route path={`${match.path}/datum`} render={props => {
return <DatumCatalog {...props} datum={this.state.datum}/>
}}/>
</Switch>
<Route render={props => {
return this.state.vCourseId ? <Recommendation {...props} vCourseId={this.state.vCourseId}/>
: null
}}/>
<ProgressShareModal isShow={this.state.isShowShareModal}
closeShareModal={() => this.setState({isShowShareModal: false})}
data={this.state.shareData}
/>
{
showLimitFreePopup &&
<div className={'limit-free-cover'}>
<div className="free-popup">
<div className="title">
<span>想领取【AI工程师必备干货礼包】? 想深入了解进阶课程? 职业前景不明朗? 资深规划师免费为你服务!</span>
</div>
<div className={'des'}>
<img className="qrcode"
src="https://julyedu-cdn.oss-cn-beijing.aliyuncs.com/tinypng-common/right_weixin.png" alt=''/>
<span>长按/扫码识别</span>
<span>添加时请备注<span>142</span>哦</span>
<div className="no-prompt">
<label htmlFor="no-prompt">
<span className={`checkbox-label ${this.state.limitFreeNoPromptChecked ? 'checked' : 'unchecked'}`}>
<i className={'iconfont iconiconfront-73'}/>
</span>
<input type="checkbox" id={'no-prompt'} onChange={(e) => {
this.setState({
limitFreeNoPromptChecked: e.target.checked
})
}}/>
<span>本课程不再提示</span>
</label>
</div>
</div>
<i className={'close-btn iconfont iconiconfront-2'} onClick={() => {
this.setState({
showLimitFreePopup: false
})
localStorage.setItem('neverShowLimitFreePopup', '1')
}}/>
</div> </div>
); </div>
} }
</div>
);
}
} }
export default connect( export default connect(
state => ({user: state.user}), state => ({user: state.user}),
null null
)(Video); )(Video);
...@@ -281,12 +281,26 @@ $tabHeight: 44px; ...@@ -281,12 +281,26 @@ $tabHeight: 44px;
} }
} }
.limit-free-cover {
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
background-color: rgba(0, 0, 0, .8);
z-index: 999;
}
.free-popup { .free-popup {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 290px; width: 290px;
height: 366px; height: 366px;
border-radius: 5px !important; border-radius: 5px !important;
padding: 0 !important; padding: 0 !important;
overflow: hidden;
background: url("https://julyedu-cdn.oss-cn-beijing.aliyuncs.com/time_limited_free/M/popup-bg.png") !important; background: url("https://julyedu-cdn.oss-cn-beijing.aliyuncs.com/time_limited_free/M/popup-bg.png") !important;
background-size: cover !important; background-size: cover !important;
...@@ -299,5 +313,90 @@ $tabHeight: 44px; ...@@ -299,5 +313,90 @@ $tabHeight: 44px;
font-size: 15px; font-size: 15px;
} }
.des {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding-top: 20px;
.qrcode {
margin-bottom: 10px;
width: 118px;
height: 118px;
}
& > span:nth-of-type(1) {
color: #666;
font-size: 13px;
margin-bottom: 10px;
}
& > span:nth-of-type(2) {
color: #333;
font-size: 15px;
margin-bottom: 14px;
}
span span {
color: #FF2121;
}
.no-prompt {
label {
position: relative;
padding-left: 21px;
height: 14px;
}
input, .checkbox-label {
position: absolute;
top: 50%;
left: 0;
transform: translateY(-50%);
width: 13px;
height: 13px;
-webkit-appearance: none;
outline: 0;
background: #fff;
}
input {
opacity: 0;
}
.checkbox-label {
border: 1px solid rgba(84, 92, 100, .6);
border-radius: 1px;
left: -1px;
box-sizing: border-box;
.iconfont {
color: #fff;
font-size: 12px;
}
}
span {
color: #545C64;
font-size: 13px;
margin-bottom: 0;
line-height: 14px;
}
.checked {
background: #09f;
}
}
}
.close-btn {
position: absolute;
bottom: -44px;
left: 50%;
transform: translateX(-50%);
font-size: 26px;
color: #fff;
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment