import React, {Component} from 'react' import HeaderBar from '@/common/HeaderBar' import './video.scss' import { NavLink, Route, Redirect, Switch } from 'react-router-dom' import { http, getParam } from '@/utils' import Recommendation from './recommendation' import VideoCatalog from './video-catalog' import DatumCatalog from './datum-catalog' import {Toast} from 'antd-mobile' import videojs from 'video.js' import 'video.js/dist/video-js.min.css' import {Modal} from "antd-mobile" import {Loading} from '@/common' import {connect} from "react-redux" import jsCookie from 'js-cookie' import io from 'socket.io-client' import Single from "@/components/detail/single"; let alert = Modal.alert function ProgressShareModal(props) { return ( props.isShow && <div className='progress-share-modal-wrapper'> <div className="progress-share-modal"> <div className="title">每日打卡</div> <ul className="progress-container"> <li> <div className="title">累计学习</div> <div className="number"><span className='num'>{props.data.learn_day_count}</span>天</div> </li> <li> <div className="title">行动力超过</div> <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'> <div className="icon"><i className='iconfont iconweixinzhifu'></i></div> <div className='text'>微信好友</div> </li> <li className='share-icon'> <div className="icon"><i className='iconfont iconpengyouquaniconx'></i></div> <div className='text'>朋友圈</div> </li> </ul> </div> <i className="iconfont iconiconfront-2 close" onClick={props.closeShareModal}/> </div> </div> ) } class Video extends Component { video //video element player //video player instance courseID ws //websocket instance timer token count watchSec previousPlaybackRate = 1 currentPlaybackRate = 1 reconnect = true recordSocket recordTimer state = { title: '', courseId: null, videoList: [], datum: [], currentVideoSrc: '', activeIndex: 0, isAuth: true, course: null, salePrice: null, vCourseId: null, isLoading: true, isShowShareModal: false, shareData: {}, singleBox: false, singMess: '', } componentDidMount() { this.courseID = getParam('id') this.setState({ courseId: this.courseID }) this.token = jsCookie.get('token') this.getVideoList() this.getDatumCatalog() this.setupRecord() } // 直接购买 tobuy = () => { http.get(`${API['base-api']}/m/cart/addtopreorder/[${getParam('id')}]`).then((res) => { if (res.data.errno === 0) { this.props.history.push(`/order?id=${getParam('id')}`, {simple: 1}) } else { Toast.info(res.data.msg, 2); } }) } // 购买单集 toSingleset = (item) => { console.log(item) this.setState({ singleBox: true, singMess: item }) } // 自组件传给父组件的boxHide boxHide = (val) => { this.setState({singleBox: val}) } setupRecord = () => { this.recordSocket = io(API.record, { transports: ['websocket'] }) this.recordSocket.on('seek', time => { this.player.currentTime(time) }) this.recordTimer = setInterval(() => { this.sendRecord() }, 5000) } sendRecord = () => { if (this.recordSocket && this.player) { this.recordSocket.emit('addRecord', this.recordUserInfo()) } } recordUserInfo = () => { let {uid} = this.props.user.data return { uid, course_id: this.courseID, video_id: this.state.videoList[this.state.activeIndex]['id'], video_time: parseInt(this.player.currentTime()), plat: 5 } } setupWS = () => { this.ws = new WebSocket(API["process-api"]); this.ws.addEventListener('error', () => { this.ws = null /*setTimeout(() => { this.setupWS(); }, 1000)*/ }) this.ws.addEventListener('close', () => { if (this.reconnect) { this.ws = null setTimeout(() => { this.setupWS(); }, 1000) } clearInterval(this.timer) }) this.ws.addEventListener('message', e => { const data = JSON.parse(e.data); data.code == 4040 && (this.reconnect = false) }) } sendMessage = message => { this.ws.send(JSON.stringify(message)) } //视频结束请求接口 getShareProgressInfo = () => { http.get(`${API['base-api']}/m/aist/share_data/${this.courseID}/${this.state.videoList[this.state.activeIndex]['id']}`) .then(res => { const {data} = res if (data.errno == 200) { this.setState({shareData: data.data, isShowShareModal: true}) } }) } //告诉服务端切换视频 countSchedule = () => { this.sendMessage({ mtype: 'count_schedule', uid: this.props.user.data.uid, token: this.token, platform: 5 }) } sendWatchTime = (sec, rate) => { const {videoList, activeIndex, vCourseId} = this.state this.sendMessage({ mtype: 'watch_time', rate, time: sec, video_id: videoList[activeIndex]['id'], course_id: this.courseID, v_course_id: vCourseId, uid: this.props.user.data.uid, token: this.token, platform: 5 }) } setupTimer = () => { this.count = 0 this.watchSec = 0 clearInterval(this.timer) 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.count++ } } }, 1000) } initializePlayer = () => { window.HELP_IMPROVE_VIDEOJS = false; this.player = videojs(this.video, { controls: true, preload: 'auto', bigPlayButton: true, textTrackDisplay: false, posterImage: false, errorDisplay: false, playbackRates: ['0.75', '1', '1.5', '2'], controlBar: { pictureInPictureToggle: false } }) this.player.on('ready', () => { this.recordSocket.emit('load', this.recordUserInfo()) }) 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.getShareProgressInfo() clearInterval(this.timer) }) this.player.on('seeked', () => { this.sendRecord() }) } 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.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 } this.setState({ activeIndex: index }, () => { if(this.hasAuth()){ this.setPlayerSrc(this.state.videoList[index]['play_url']) this.sendLastRecord() this.playVideo() } } ) } getVideoList = () => { http.get(`${API.home}/m/course/play/${this.courseID}`) .then(res => { const data = res.data if (data.code === 200) { this.setState( state => ({ videoList: data.data['lessons'], currentVideoSrc: data.data['lessons'][state.activeIndex]['play_url'], course: data.data.course, courseId: data.data.course['course_id'], vCourseId: data.data.course['v_course_id'], title: data.data.course['course_title'], isLoading: false }), () => { if (this.state.course.is_aist) { this.setupWS() this.setupTimer() } if (this.lessonAvailable()) { if (this.hasAuth()) { Promise.resolve().then(() => { let {videoList, course} = this.state let videoIndex = videoList.findIndex(item => item.id == course.last_video_id) this.setState({ activeIndex: videoIndex < 0 ? 0 : videoIndex }) this.initializePlayer() this.playWithAuth() }) } else { this.getCoursePrice(); } } else { alert('暂无视频', '', [{ text: 'OK', onPress: () => { this.props.history.push('/') } }]) } } ) } else { Toast.info(data.msg) } }) } setPlayerSrc = src => { if(!this.player){ this.initializePlayer() } this.player.src({ src, type: 'application/x-mpegURL' }) } playVideo = () => { 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 { Toast.info(data.msg) } }) } lessonAvailable = () => { const {videoList, activeIndex} = this.state return videoList[activeIndex]['video_size'] !== 0 } getCoursePrice = () => { http.get(`${API.home}/sys/course/price/${getParam('id')}`) .then(res => { const {data} = res if (data.code === 200) { this.setState({ salePrice: data.data['sale_price'] }) } }) } playWithAuth = () => { const {videoList, activeIndex} = this.state if (this.hasAuth()) { this.setPlayerSrc(videoList[activeIndex]['play_url']) } } hasAuth = () => { const {course, videoList, activeIndex} = this.state let lesson = videoList[activeIndex] if (lesson['video_auth']) { this.setState({ isAuth: true }) return true } else { this.setState({ isAuth: false }) return false } } render() { let {match, location} = this.props const {videoList, activeIndex, isAuth, salePrice, course} = this.state return ( <div className='play'> <HeaderBar title={this.state.title} arrow={true}/> <Loading isLoading={this.state.isLoading}> <div className="video"> <video className={'video-js'} ref={el => this.video = el}> <source src={'/'} type='application/x-mpegURL'/> </video> { !isAuth && !!videoList[activeIndex]['is_class'] && ( <div className="purchase-box"> <div className='hint'>您尚未购买该课时,请购买后学习。</div> <div className='btns'> <button type='button' onClick={this.tobuy} className='purchase-class'>¥{salePrice} 购买课程 </button> <button type='button' onClick={this.toSingleset.bind(this, videoList[activeIndex])} className='purchase-episode'>¥{videoList.length && videoList[activeIndex]['class_price']} 购买单集 </button> </div> </div> ) } { !isAuth && !!course.is_aist && ( <div className="is-aist-box"> <i className={'iconfont iconiconfront-21'}></i> <p className={'time'}>{videoList[activeIndex]['aist_start_time']}</p> <p className={'time'}>请耐心等待...</p> </div> ) } </div> <div className='tab'> <div> <NavLink to={{pathname: `${match.url}/video`, search: `?id=${this.courseID}`}} replace activeClassName='active' >视频</NavLink> </div> <div> <NavLink to={{pathname: `${match.url}/datum`, search: `?id=${this.courseID}`}} replace activeClassName='active' >资料</NavLink> </div> </div> {/*单集购买*/} <Single singleBox={this.state.singleBox} boxHide={this.boxHide} data={this.state.singMess} videoId={this.state.singMess.id} title={this.state.singMess.course_tile}/> </Loading> <Switch> <Redirect exact from={'/play'} to={{ pathname: '/play/video', search: location.search }}/> <Route path={`${match.path}/video`} render={props => { return <VideoCatalog activeIndex={this.state.activeIndex} selectVideo={this.selectVideo} videoCatalog={this.state.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} /> </div> ); } } export default connect( state => ({user: state.user}), null )(Video);