From 03b13a045cba19659b242c19ecb0258dc99a23cf Mon Sep 17 00:00:00 2001 From: littly <544028951@qq.com> Date: Tue, 9 Apr 2019 22:22:11 +0800 Subject: [PATCH 1/4] =?UTF-8?q?feat(components):=20=E5=A2=9E=E5=8A=A0video?= =?UTF-8?q?=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/canvas/index.js | 34 +- .../src/components/navigator/index.js | 58 +- .../src/components/tabbar/index.js | 6 +- .../src/components/video/controls.js | 171 ++++++ .../src/components/video/danmu.js | 107 ++++ .../src/components/video/images/full.png | Bin 0 -> 190 bytes .../src/components/video/images/mute.png | Bin 0 -> 2184 bytes .../src/components/video/images/pause.png | Bin 0 -> 159 bytes .../src/components/video/images/play.png | Bin 0 -> 430 bytes .../src/components/video/images/shrink.png | Bin 0 -> 374 bytes .../src/components/video/images/unmute.png | Bin 0 -> 2050 bytes .../src/components/video/images/volume.png | Bin 0 -> 1471 bytes .../src/components/video/index.js | 528 ++++++++++++++++-- .../src/components/video/style/index.scss | 349 +++++++++++- .../src/components/video/utils.js | 14 + .../taro-components/src/utils/touchable.js | 38 +- packages/taro-h5/src/api/index.js | 15 +- packages/taro-h5/src/api/videoUtils/index.js | 63 +++ packages/taro-router/src/router/route.tsx | 5 +- packages/taro-router/src/router/router.tsx | 4 +- 20 files changed, 1262 insertions(+), 130 deletions(-) create mode 100644 packages/taro-components/src/components/video/controls.js create mode 100644 packages/taro-components/src/components/video/danmu.js create mode 100644 packages/taro-components/src/components/video/images/full.png create mode 100644 packages/taro-components/src/components/video/images/mute.png create mode 100644 packages/taro-components/src/components/video/images/pause.png create mode 100644 packages/taro-components/src/components/video/images/play.png create mode 100644 packages/taro-components/src/components/video/images/shrink.png create mode 100644 packages/taro-components/src/components/video/images/unmute.png create mode 100644 packages/taro-components/src/components/video/images/volume.png create mode 100644 packages/taro-components/src/components/video/utils.js create mode 100644 packages/taro-h5/src/api/videoUtils/index.js diff --git a/packages/taro-components/src/components/canvas/index.js b/packages/taro-components/src/components/canvas/index.js index 858497719548..ff1574466215 100644 --- a/packages/taro-components/src/components/canvas/index.js +++ b/packages/taro-components/src/components/canvas/index.js @@ -5,22 +5,30 @@ import classnames from 'classnames' import './style/index.css' -// canvas-id String canvas 组件的唯一标识符 -// disable-scroll Boolean false 当在 canvas 中移动时且有绑定手势事件时,禁止屏幕滚动以及下拉刷新 -// bindtouchstart EventHandle 手指触摸动作开始 -// bindtouchmove EventHandle 手指触摸后移动 -// bindtouchend EventHandle 手指触摸动作结束 -// bindtouchcancel EventHandle 手指触摸动作被打断,如来电提醒,弹窗 -// bindlongtap EventHandle 手指长按 500ms 之后触发,触发了长按事件后进行移动不会触发屏幕的滚动 -// binderror EventHandle 当发生错误时触发 error 事件,detail = {errMsg: 'something wrong'} +/** + * Canvas组件参数 + * @typedef CanvasProps + * @param {String} [canvasId=canvas] 组件的唯一标识符 + * @param {Boolean} [disableScroll=false] 当在 canvas 中移动时且有绑定手势事件时,禁止屏幕滚动以及下拉刷新 + * @param {EventHandle} onTouchstart 手指触摸动作开始 + * @param {EventHandle} onTouchmove 手指触摸后移动 + * @param {EventHandle} onTouchend 手指触摸动作结束 + * @param {EventHandle} onTouchcancel 手指触摸动作被打断,如来电提醒,弹窗 + * @param {EventHandle} onLongtap 手指长按 500ms 之后触发,触发了长按事件后进行移动不会触发屏幕的滚动 + * @param {EventHandle} onError 当发生错误时触发 error 事件,detail = {errMsg: 'something wrong'} + */ @touchable() -export default class Canvas extends Taro.PureComponent { +class Canvas extends Taro.PureComponent { + /** @type {CanvasProps} */ static defaultProps = { canvasId: '', disableScroll: false, - bindError: null + onError: null } + + /** @type {CanvasProps} */ + props width = 300 height = 150 getWrapRef = ref => { @@ -40,8 +48,8 @@ export default class Canvas extends Taro.PureComponent { this.height = height } componentDidCatch (e) { - const bindError = this.props.bindError - bindError && bindError({ + const onError = this.props.onError + onError && onError({ errMsg: e.message }) } @@ -68,3 +76,5 @@ export default class Canvas extends Taro.PureComponent { ) } } + +export default Canvas diff --git a/packages/taro-components/src/components/navigator/index.js b/packages/taro-components/src/components/navigator/index.js index 47735241be33..8a00d10d721d 100644 --- a/packages/taro-components/src/components/navigator/index.js +++ b/packages/taro-components/src/components/navigator/index.js @@ -8,19 +8,21 @@ import './navigator.css' /* eslint-disable prefer-promise-reject-errors */ -/* - *target String self 在哪个目标上发生跳转,默认当前小程序,可选值self/miniProgram - *url String 当前小程序内的跳转链接 - *open-type String navigate 跳转方式 - *delta Number 当 open-type 为 'navigateBack' 时有效,表示回退的层数 - *app-id String 当target="miniProgram"时有效,要打开的小程序 appId - *path String 当target="miniProgram"时有效,打开的页面路径,如果为空则打开首页 - *extra-data Object 当target="miniProgram"时有效,需要传递给目标小程序的数据,目标小程序可在 App.onLaunch(),App.onShow() 中获取到这份数据。详情 - version version release 当target="miniProgram"时有效,要打开的小程序版本,有效值 develop(开发版),trial(体验版),release(正式版),仅在当前小程序为开发版或体验版时此参数有效;如果当前小程序是 *式版,则打开的小程序必定是正式版。 - *bindsuccess String 当target="miniProgram"时有效,跳转小程序成功 - *bindfail String 当target="miniProgram"时有效,跳转小程序失败 - *bindcomplete String 当target="miniProgram"时有效,跳转小程序完成 - *aria-label String 无障碍访问,(属性)元素的额外描述 +/** + * Navigator组件参数 + * @typedef NavigatorProps + * @property {String} appId 当target="miniProgram"时有效,要打开的小程序 appId + * @property {String} ariaLabel 无障碍访问,(属性)元素的额外描述 + * @property {Number} delta 当 openType 为 'navigateBack' 时有效,表示回退的层数 + * @property {Object} extraData 当target="miniProgram"时有效,需要传递给目标小程序的数据,目标小程序可在 App.onLaunch(),App.onShow() 中获取到这份数据。详情 + * @property {String} [openType=navigate] 跳转方式 + * @property {String} path 当target="miniProgram"时有效,打开的页面路径,如果为空则打开首页 + * @property {String} [target=self] 在哪个目标上发生跳转,默认当前小程序,可选值self/miniProgram + * @property {String} url 当前小程序内的跳转链接 + * @property {version} [version=release] 当target="miniProgram"时有效,要打开的小程序版本,有效值 develop(开发版),trial(体验版),release(正式版),仅在当前小程序为开发版或体验版时此参数有效;如果当前小程序是 *式版,则打开的小程序必定是正式版。 + * @property {String} onFail 当target="miniProgram"时有效,跳转小程序失败 + * @property {String} onComplete 当target="miniProgram"时有效,跳转小程序完成 + * @property {String} onSuccess 当target="miniProgram"时有效,跳转小程序成功 */ /** @@ -29,7 +31,14 @@ import './navigator.css' * https://developers.weixin.qq.com/miniprogram/dev/component/navigator.html **/ +@hoverable({ + hoverClass: 'navigator-hover', + hoverStopPropergation: false, + hoverStartTime: 50, + hoverStayTime: 600 +}) class Navigator extends Taro.Component { + /** @type {NavigatorProps} */ static defaultProps = { target: 'self', url: null, @@ -39,13 +48,15 @@ class Navigator extends Taro.Component { path: null, extraData: {}, version: 'release', - bindSuccess: null, - bindFail: null, - bindComplete: null, + onSuccess: null, + onFail: null, + onComplete: null, isHover: false } + /** @type {NavigationProps} */ + props onClick = () => { - const { openType, bindSuccess, bindFail, bindComplete } = this.props + const { openType, onSuccess, onFail, onComplete } = this.props let promise switch (openType) { case 'navigate': @@ -81,11 +92,11 @@ class Navigator extends Taro.Component { } if (promise) { promise.then(res => { - bindSuccess && bindSuccess(res) + onSuccess && onSuccess(res) }).catch(res => { - bindFail && bindFail(res) + onFail && onFail(res) }).finally(res => { - bindComplete && bindComplete(res) + onComplete && onComplete(res) }) } } @@ -105,9 +116,4 @@ class Navigator extends Taro.Component { } } -export default hoverable({ - hoverClass: 'navigator-hover', - hoverStopPropergation: false, - hoverStartTime: 50, - hoverStayTime: 600 -})(Navigator) +export default Navigator diff --git a/packages/taro-components/src/components/tabbar/index.js b/packages/taro-components/src/components/tabbar/index.js index 069b4433ed50..93735fd4ba5f 100644 --- a/packages/taro-components/src/components/tabbar/index.js +++ b/packages/taro-components/src/components/tabbar/index.js @@ -39,9 +39,9 @@ class Tabbar extends Nerv.Component { this.customRoutes.push([key, customRoutes[key]]) } - list.forEach( item => { - if (item.pagePath.indexOf('/') !== 0){ - item.pagePath = "/" + item.pagePath + list.forEach(item => { + if (item.pagePath.indexOf('/') !== 0) { + item.pagePath = '/' + item.pagePath } }) diff --git a/packages/taro-components/src/components/video/controls.js b/packages/taro-components/src/components/video/controls.js new file mode 100644 index 000000000000..c3cfcf49e5c6 --- /dev/null +++ b/packages/taro-components/src/components/video/controls.js @@ -0,0 +1,171 @@ +import Nerv, { Component } from 'nervjs' +import classnames from 'classnames' +import { formatTime, calcDist } from './utils' + +/** + * @typedef {Object} ControlsProps + * @property {Boolean} controls={controls} + * @property {Number} currentTime={this.currentTime} + * @property {Number} duration={this.state.duration} + * @property {Boolean} isPlaying={this.state.isPlaying} + * @property {Function} pauseFunc={this.pause} + * @property {Function} playFunc={this.play} + * @property {Function} seekFunc={this.seek} + * @property {Boolean} showPlayBtn={showPlayBtn} + * @property {Boolean} showProgress={showProgress} + */ +class Controls extends Component { + visible = false + isDraggingProgressBall = false + + /** @type {number} */ + hideControlsTimer + + /** @type {ControlsProps} */ + props + + progressDimentions = { + left: 0, + right: 0, + width: 0 + } + + calcPercentage = pageX => { + let pos = pageX - this.progressDimentions.left + pos = Math.max(pos, 0) + pos = Math.min(pos, this.progressDimentions.width) + return pos / this.progressDimentions.width + } + + getControlsRef = ref => { + if (!ref) return + this.controlsRef = ref + } + getCurrentTimeRef = ref => { + if (!ref) return + this.currentTimeRef = ref + } + getProgressBallRef = ref => { + if (!ref) return + this.progressBallRef = ref + } + + setCurrentTime (time) { + this.currentTimeRef.innerHTML = formatTime(time) + } + setProgressBall (percentage) { + this.progressBallRef.style.left = `${percentage * 100}%` + } + + toggleVisible (nextVisible) { + const visible = nextVisible === undefined ? !this.visible : nextVisible + if (visible) { + this.hideControlsTimer && clearTimeout(this.hideControlsTimer) + if (this.props.isPlaying) { + this.hideControlsTimer = setTimeout(() => { + this.toggleVisible(false) + }, 2000) + } + this.controlsRef.style.visibility = 'visible' + } else { + this.controlsRef.style.visibility = 'hidden' + } + this.visible = !!visible + } + + onDragProgressBallStart = () => { + this.isDraggingProgressBall = true + this.hideControlsTimer && clearTimeout(this.hideControlsTimer) + } + onClickProgress = e => { + e.stopPropagation() + const seekFunc = this.props.seekFunc + const percentage = this.calcPercentage(e.pageX) + seekFunc(percentage * this.props.duration) + this.toggleVisible(true) + } + bindTouchEvents = () => { + let percentage = 0 + const touchMove = e => { + if (!this.isDraggingProgressBall) return + const touchX = e.touches[0].pageX + percentage = this.calcPercentage(touchX) + this.setProgressBall(percentage) + } + const touchEnd = e => { + if (!this.isDraggingProgressBall) return + const seekFunc = this.props.seekFunc + this.isDraggingProgressBall = false + seekFunc(percentage * this.props.duration) + this.toggleVisible(true) + } + + document.body.addEventListener('touchmove', touchMove) + document.body.addEventListener('touchend', touchEnd) + document.body.addEventListener('touchcancel', touchEnd) + return () => { + document.body.removeEventListener('touchmove', touchMove) + document.body.removeEventListener('touchend', touchEnd) + document.body.removeEventListener('touchcancel', touchEnd) + } + } + + componentDidMount () { + this.unbindTouchEvents = this.bindTouchEvents() + } + componentWillUnmount () { + this.unbindTouchEvents() + } + + render () { + const { controls, currentTime, duration, isPlaying, pauseFunc, playFunc, showPlayBtn, showProgress } = this.props + const formattedDuration = formatTime(duration) + let playBtn + + if (!showPlayBtn) { + return null + } else if (isPlaying) { + playBtn =
+ } else { + playBtn = + } + + return ( +{ + if (ref) { + this.danmuElList.push(ref) + } + }}> + {text} +
+ ) + })} +ERfoBA)^dWRkl7$3upmS(49+NaS>B7>yEF2rt1cs&|dV>oqGYq0N{s8Kh zRe;(ocATfIB #`P=uS1<( zzQzCyt>2%uF{+k$JZnPJy^YX~Oh86OYoTTZ5J8~H36%%lMksT-;f=z&Rlw=88j|JM zaH0akl2NzYNQ@JG(9iLrWcV?2#4h0Bbl}1ug272EPEjF>WGR?BayCpLNe)5%vLHs9 zM}yKdOVigtVciIv&iyA?;Ms_*#yRM<80YE%p-A-@nvEsvmSeIC4MTM$Bj 5!SZ>i&+E2RjNOjY7Ah^*?`M6IrgIVxd>$7DBV-Xpfn^+F&KY(>CpnuPxAHa$ zcUo-%UJ{N(XfkXS9M;J2eviy2j5;v(4;=|X<{`&F@|-rffo238kah zS=Z!<-o&Y(tRBYh=1P&co(CG&cw_xHm(L+#qv^k Lvj$0I-TTu{*#m0C& z_nLn`_->!DzNc^TlBug-Z7(^ux8&@hzQCE_^!ELO6Oqomz4=QQ99vl3JG=biF7wtc z)l;rk@2)ikiaLvqb?=&Q>NWooUc2l(a%NX)QQZ^ftpl?I2YNTASmorNoSBV}^(;TK z>6>{e|B3O6J@Eqa^{Vc2>aca 1xJF3NQ^3wc^XLnxNbGl9b z_- 76tA{EazNZtwgTxl6{f^Gv9$HGNw@#dI_<<1a(m^z z+?UYfx1I~0U->KIJKFW(x06#{p^nqJb$c##)Q$}lnF_kz`)*b%JE3@6q!c^;ifKhW zb$Q#Byrg;2*p 8t$d38_y4an`ez5oCK literal 0 HcmV?d00001 diff --git a/packages/taro-components/src/components/video/images/volume.png b/packages/taro-components/src/components/video/images/volume.png new file mode 100644 index 0000000000000000000000000000000000000000..e4fbbaeebbdb629a78da795a1b199fa095a77e42 GIT binary patch literal 1471 zcmV;w1wi_VP) J~+X@y$S*n*S_wG$E|K{aWmrLnifR!Oa`T0&4n zXe~jch^0b=q-`v*w&dH)mD}l=JNLbNd+)pVz4Om|nVH|r`F`)2nX^nUy1De~=J@F$ zpvMC0WuP~(AMiWyKJahV>eRUfP5~wYTRYJ20K 1Xe$B701pEjWdQsM*sN^?tOZ;TT$cmbA3&cR<4PN{rq6(Wz+=FXIW=w- z0Ylw>-zFz`4QmYnV}Kc*Fz3b-ur_cjFrfoqJw3G11Z)b-1`g^Z__DUZFn7~@2`rB3 zQr023Col)tJhot@zo-RV3Ebk=_!&4Bcs*;)7y-J%1_E0`Fml|@nf*Ei!56iF0l?>g z;)_In0S R^8Ucv$!`)12%Vr+a}wGD z`ueg0OSBc9H9F1*?l9V)14jA;oCDllHCki65COW|Ujhy_e)9z&3;x^dlV75~B;}TL zYs+Y>uk;cV3pfwBvkMa-)pQFi1O^#;UJ6V$+C!Ejk5Ms8qX>|`y8s^=*W4mtCxd`3 z0LuY-4LGEU1W4!GfeQ?<+6JE*?Mk=r?zQVmTo+j0q%DwO5Ey~-C6rN?PaxmG7g@}r zCK4bZ-vV2Ch#msQ8bHql?lRhs2Ie)DfO)`C9;jDq!Kf}$b+2DUrv;%x9JR! zSGdHeFv*jZL+8tdfMNz;RG#~+i58#=vZ6X)&IWWg_%aq7x1nD^2tECT^S_B558RG-X=6E7RU;Q_68OhK=lCQ z3Zq?_ZY6;JCqVn>USnX@O`Qpcl?gt Ep pf!0UQb&Tm%($jSgH-JT{uX(EN0N Hi?9?y$QvH3k-X*%z3)8mQ|;1@@KaMkTX?*lplLUnk#q(#c xho}M( z%N5PyqBR60AobEjNs7EKcIyZb5%S)2E+{&SNh=8uXjN!cmfktSKx+vQZbh7rxCk!9 zgWmR4>V}tn4aQ7d>)nv?bYAcLc8E< s1W=ZocGUm?002ovPDHLkV1fzOiS_^h literal 0 HcmV?d00001 diff --git a/packages/taro-components/src/components/video/index.js b/packages/taro-components/src/components/video/index.js index e9a85af16abc..11109fe6ca88 100644 --- a/packages/taro-components/src/components/video/index.js +++ b/packages/taro-components/src/components/video/index.js @@ -1,88 +1,500 @@ -import 'weui' -import Nerv from 'nervjs' +import Nerv, { Component, createPortal } from 'nervjs' +import classnames from 'classnames' +import Danmu from './danmu' +import Controls from './controls' +import { formatTime, calcDist, normalizeNumber } from './utils' + import './style/index.scss' +import 'weui' + +/** + * @typedef {Object} Danmu + * @property {string} text 弹幕文字 + * @property {string} color 弹幕颜色 + * @property {number} [time] 弹幕时间 + */ -class Video extends Nerv.Component { - constructor () { - super(...arguments) +/** + * Video组件参数 + * @typedef {Object} VideoProps + * @property {string} src 要播放视频的资源地址,支持云文件ID(2.3.0) + * @property {boolean} [autoPauseIfNavigate=true] 当跳转到其它小程序页面时,是否自动暂停本页面的视频 + * @property {boolean} [autoPauseIfOpenNative=true] 当跳转到其它微信原生页面时,是否自动暂停本页面的视频 + * @property {boolean} [autoplay=false] 是否自动播放 + * @property {boolean} [controls=true] 是否显示默认播放控件(播放/暂停按钮、播放进度、时间) + * @property {boolean} [danmuBtn=false] 是否显示弹幕按钮,只在初始化时有效,不能动态变更 + * @property {Array. } [danmuList=[]] 弹幕列表 + * @property {boolean} [enableDanmu=false] 是否展示弹幕,只在初始化时有效,不能动态变更 + * @property {boolean} [enablePlayGesture=false] 是否开启播放手势,即双击切换播放/暂停 + * @property {boolean} [enableProgressGesture=true] 是否开启控制进度的手势 + * @property {number} [initialTime=0] 指定视频初始播放位置 + * @property {boolean} [loop=false] 是否循环播放 + * @property {boolean} [muted=false] 是否静音播放 + * @property {string} [objectFit=contain] 当视频大小与 video 容器大小不一致时,视频的表现形式 + * @property {string} [playBtnPosition=bottom] 播放按钮的位置 + * @property {boolean} [showCenterPlayBtn=true] 是否显示视频中间的播放按钮 + * @property {boolean} [showFullscreenBtn=true] 是否显示全屏按钮 + * @property {boolean} [showMuteBtn=false] 是否显示静音按钮 + * @property {boolean} [showPlayBtn=true] 是否显示视频底部控制栏的播放按钮 + * @property {boolean} [showProgress=true] 若不设置,宽度大于240时才会显示 + * @property {boolean} [vslideGesture=false] 在非全屏模式下,是否开启亮度与音量调节手势(同 pageGesture) + * @property {boolean} [vslideGestureInFullscreen=true] 在全屏模式下,是否开启亮度与音量调节手势 + * @property {number} [direction] 设置全屏时视频的方向,不指定则根据宽高比自动判断 + * @property {number} [duration] 指定视频时长 + * @property {string} [poster] 视频封面的图片网络资源地址或云文件ID(2.3.0)。若 controls 属性值为 false 则设置 poster 无效 + * @property {string} [title] 视频的标题,全屏时在顶部展示 + * @property {Function} [onPlay] 当开始/继续播放时触发play事件 + * @property {Function} [onPause] 当暂停播放时触发 pause 事件 + * @property {Function} [onEnded] 当播放到末尾时触发 ended 事件 + * @property {Function} [onTimeupdate] 播放进度变化时触发,event.detail = {currentTime, duration} 。触发频率 250ms 一次 + * @property {Function} [onFullscreenChange] 视频进入和退出全屏时触发,event.detail = {fullScreen, direction},direction 有效值为 vertical 或 horizontal + * @property {Function} [onWaiting] 视频出现缓冲时触发 + * @property {Function} [onError] 视频播放出错时触发 + * @property {Function} [onProgress] 加载进度变化时触发,只支持一段加载。event.detail = {buffered},百分比 + */ + +class Video extends Component { + /** @type {VideoProps} */ + static defaultProps = { + autoPauseIfNavigate: true, + autoPauseIfOpenNative: true, + autoplay: false, + controls: true, + danmuBtn: false, + danmuList: [], + enableDanmu: false, + enablePlayGesture: false, + enableProgressGesture: true, + initialTime: 0, + loop: false, + muted: false, + objectFit: 'contain', + playBtnPosition: 'bottom', + showCenterPlayBtn: true, + showFullscreenBtn: true, + showMuteBtn: false, + showPlayBtn: true, + showProgress: true, + vslideGesture: false, + vslideGestureInFullscreen: true } - componentDidMount () { - this.bindevent() + + /** @type {VideoProps} */ + props + + /** @type {HTMLVideoElement} */ + videoRef + + /** @type {Contorls} */ + controlsRef + + /** @type {HTMLDivElement} */ + currentTimeRef + + /** @type {HTMLDivElement} */ + danmuRef + + /** @type {number} */ + currentTime = 0 + + /** @type {number} */ + lastClickedTime + + /** @type {number} */ + lastTouchScreenX + + /** @type {number} */ + lastTouchScreenY + + progressDimentions = { + left: 0, + right: 0, + width: 0 } - bindevent () { - this.video.addEventListener('timeupdate', (e) => { - Object.defineProperty(e, 'detail', { - enumerable: true, - value: { - duration: e.srcElement.duration, - currentTime: e.srcElement.currentTime - } + constructor (props, context) { + super(props, context) + const stateObj = this.getInitialState(this.props) + this.state = Object.assign( + { + duration: null, + isPlaying: false, + isFirst: true, + enableDanmu: false, + isFullScreen: false, + isMute: false + }, + stateObj + ) + } + + sendDanmu (danmu) { + this.danmuRef.sendDanmu(danmu) + } + + onTimeUpdate = e => { + Object.defineProperty(e, 'detail', { + enumerable: true, + value: { + duration: e.srcElement.duration, + currentTime: e.srcElement.currentTime + } + }) + this.currentTime = this.videoRef.currentTime + const duration = this.state.duration + if (!this.controlsRef.isDraggingProgressBall && !this.isDraggingProgress) { + this.controlsRef.setProgressBall(this.currentTime / duration) + } + this.controlsRef.setCurrentTime(this.currentTime) + + this.danmuRef.tick(this.currentTime) + this.props.onTimeUpdate && this.props.onTimeUpdate(e) + } + + onEnded = e => { + this.pause() + this.props.onEnded && this.props.onEnded(e) + } + + onPlay = e => { + this.props.onPlay && this.props.onPlay(e) + this.controlsRef.toggleVisible(true) + if (!this.state.isPlaying) { + this.setState({ + isPlaying: true + }) + } + } + + onPause = e => { + this.props.onPause && this.props.onPause(e) + this.controlsRef.toggleVisible(true) + if (this.state.isPlaying) { + this.setState({ + isPlaying: false }) - this.props.onTimeUpdate && this.props.onTimeUpdate(e) + } + } + + onError = e => { + Object.defineProperty(e, 'detail', { + enumerable: true, + value: { errMsg: e.srcElement.error.code } }) + this.props.onError && this.props.onError(e) + } - this.video.addEventListener('ended', (e) => { - this.props.onEnded && this.props.onEnded(e) + onClickContainer = e => { + if (this.props.enablePlayGesture) { + const now = Date.now() + if (now - this.lastClickedTime < 300) { + // 双击 + if (this.state.isPlaying) { + this.pause() + } else { + this.play() + } + } + this.lastClickedTime = now + } + this.controlsRef.toggleVisible() + } + + onLoadedMetadata = e => { + this.setState({ + duration: this.videoRef.duration }) + this.duration = this.videoRef.duration + if (this.state.isFirst) { + this.seek(this.props.initialTime) + } + } - this.video.addEventListener('play', (e) => { - this.props.onPlay && this.props.onPlay(e) + toggleDanmu = e => { + e.stopPropagation() + this.controlsRef.toggleVisible(true) + this.setState({ + enableDanmu: !this.state.enableDanmu }) + } + + toggleFullScreen = e => { + e.stopPropagation() + const currentTime = this.currentTime + const danmuList = this.danmuRef.danmuList + this.setState( + { + isFullScreen: !this.state.isFullScreen + }, + () => { + const evt = new Event('fullscreenChange', { + fullScreen: this.state.isFullScreen, + direction: 'vertical' + }) + this.props.onFullscreenChange && this.props.onFullscreenChange(evt) + this.danmuRef.danmuList = danmuList + this.seek(currentTime) + this.state.isPlaying && this.play() + this.controlsRef.toggleVisible(true) + } + ) + } - this.video.addEventListener('pause', (e) => { - this.props.onPause && this.props.onPause(e) + toggleMute = e => { + e.stopPropagation() + this.setState(() => { + const nextMuteState = !this.state.isMute + this.videoRef.muted = nextMuteState + this.controlsRef.toggleVisible(true) + return { isMute: nextMuteState } }) + } - // 网络错误 - this.video.addEventListener('error', (e) => { - Object.defineProperty(e, 'detail', { - enumerable: true, - value: {errMsg: e.srcElement.error.code} - }) - this.props.onError && this.props.onError(e) + play = () => { + this.videoRef.play() + this.setState({ + isPlaying: true, + isFirst: false }) } + pause = () => { + this.videoRef.pause() + this.setState({ + isPlaying: false + }) + } + + seek = position => { + this.videoRef.currentTime = position + } + + getInitialState (props) { + const stateObj = { + enableDanmu: props.enableDanmu + } + return stateObj + } + + onTouchStartContainer = e => { + this.lastTouchScreenX = e.touches[0].screenX + this.lastTouchScreenY = e.touches[0].screenY + } + + bindTouchEvents = () => { + let lastVolume + let lastPercentage + let nextPercentage + let gestureType = 'none' + + const analyseGesture = e => { + const obj = {} + const nowX = e.touches[0].screenX + const nowY = e.touches[0].screenY + const distX = nowX - this.lastTouchScreenX + const distY = nowY - this.lastTouchScreenY + if (gestureType === 'none') { + const dist = calcDist(distX, distY) + if (dist < 10) { + obj.type = 'none' + return obj + } + if (distX === 0 || Math.abs(distY / distX) > 1) { + let enableVslideGesture = this.state.isFullScreen ? this.props.vslideGestureInFullscreen : this.props.vslideGesture + if (enableVslideGesture) { + gestureType = 'adjustVolume' + lastVolume = this.videoRef.volume + } + } else if (this.props.enableProgressGesture && Math.abs(distY / distX) <= 1) { + gestureType = 'adjustProgress' + lastPercentage = this.currentTime / this.state.duration + } + } + obj.type = gestureType + obj.dataX = normalizeNumber(distX / window.screen.width) + obj.dataY = normalizeNumber(distY / window.screen.height) + return obj + } + const touchMove = e => { + if (this.controlsRef.isDraggingProgressBall) return + + const gestureObj = analyseGesture(e) + if (gestureObj.type === 'adjustVolume') { + this.toastVolumeRef.style.visibility = 'visible' + const nextVolume = Math.max(Math.min(lastVolume - gestureObj.dataY, 1), 0) + this.videoRef.volume = nextVolume + this.toastVolumeBarRef.style.width = `${nextVolume * 100}%` + } else if (gestureObj.type === 'adjustProgress') { + this.isDraggingProgress = true + nextPercentage = Math.max(Math.min(lastPercentage + gestureObj.dataX, 1), 0) + this.controlsRef.setProgressBall(nextPercentage) + this.controlsRef.toggleVisible(true) + this.toastProgressTitleRef.innerHTML = `${formatTime(nextPercentage * this.duration)} / ${formatTime(this.duration)}` + this.toastProgressRef.style.visibility = 'visible' + } + } + const touchEnd = e => { + if (gestureType === 'adjustVolume') { + this.toastVolumeRef.style.visibility = 'hidden' + } else if (gestureType === 'adjustProgress') { + this.toastProgressRef.style.visibility = 'hidden' + } + gestureType = 'none' + if (this.isDraggingProgress) { + this.isDraggingProgress = false + this.seek(nextPercentage * this.videoRef.duration) + } + } + + document.body.addEventListener('touchmove', touchMove) + document.body.addEventListener('touchend', touchEnd) + document.body.addEventListener('touchcancel', touchEnd) + return () => { + document.body.removeEventListener('touchmove', touchMove) + document.body.removeEventListener('touchend', touchEnd) + document.body.removeEventListener('touchcancel', touchEnd) + } + } + + componentWillMount () { + const getRef = refName => { + return ref => { + if (!ref) return + this[refName] = ref + } + } + this.getVideoRef = getRef('videoRef') + this.getControlsRef = getRef('controlsRef') + this.getDanmuRef = getRef('danmuRef') + this.getToastProgressRef = getRef('toastProgressRef') + this.getToastProgressTitleRef = getRef('toastProgressTitleRef') + this.getToastVolumeRef = getRef('toastVolumeRef') + this.getToastVolumeBarRef = getRef('toastVolumeBarRef') + } + + componentDidMount () { + this.unbindTouchEvents = this.bindTouchEvents() + this.sendDanmu(this.props.danmuList) + } + + componentWillReceiveProps (nProps) { + const nState = this.getInitialState(nProps) + this.setState(nState) + } + + componentWillUnmount () { + this.unbindTouchEvents() + } + render () { - let { + const { src, - controls, autoplay, - poster, - initialTime, + className, id, + initialTime, loop, muted, - className + objectFit, + poster, + + controls, + showFullscreenBtn, + showMuteBtn, + showPlayBtn, + showProgress, + showCenterPlayBtn, + danmuBtn } = this.props - if (!controls) { - poster = '' + const { enableDanmu, isFirst, isMute, isFullScreen } = this.state + const duration = formatTime(this.state.duration) + + const videoProps = { + id, + src, + autoplay, + poster: controls ? poster : null, + loop, + muted, + start: initialTime, + className: classnames('taro-video-video', className), + ref: this.getVideoRef, + playsinline: true, + 'webkit-playsinline': true, + 'object-fit': objectFit, + + controls: false, + onTimeUpdate: this.onTimeUpdate, + onEnded: this.onEnded, + onPlay: this.onPlay, + onPause: this.onPause, + onError: this.onError, + onDurationChange: this.onLoadedMetadata } - return ( - + + const videoNode = ( + + +) + return this.state.isFullScreen ? createPortal(videoNode, document.body) :+ {showMuteBtn && ( + + )} + {danmuBtn && ( + ++ 弹幕 ++ )} + {showFullscreenBtn && ( + + )} ++ {isFirst && showCenterPlayBtn && !this.state.isPlaying && ( + + ++ )} +{duration}
+++音量+ ++++++ {new Array(10).fill().map(v => ( + + ))} +++ ++{videoNode}} } -// 默认配置 -Video.defaultProps = { - autoplay: false, - controls: true, - loop: false, - muted: false -} - export default Video diff --git a/packages/taro-components/src/components/video/style/index.scss b/packages/taro-components/src/components/video/style/index.scss index 016922c28efb..e2c048ba8c32 100644 --- a/packages/taro-components/src/components/video/style/index.scss +++ b/packages/taro-components/src/components/video/style/index.scss @@ -1,6 +1,349 @@ @charset "UTF-8"; -video { - max-width:100%; - height:auto; +.taro-video { + // width: 300px; + width: 100%; + height: 225px; + display: inline-block; + line-height: 0; + overflow: hidden; + position: relative } + +.taro-video[hidden] { + display: none +} + +.taro-video-container { + width: 100%; + height: 100%; + background-color: #000; + display: inline-block; + position: absolute; + top: 0; + left: 0; + overflow: hidden; + object-position: inherit +} + +.taro-video-container.taro-video-type-fullscreen { + position: fixed; + top: 50%; + left: 50%; + -webkit-transform: translate(-50%,-50%); + transform: translate(-50%,-50%); + z-index: 999 +} + +.taro-video-container.taro-video-type-fullscreen.taro-video-type-rotate-left { + -webkit-transform: translate(-50%,-50%) rotate(-90deg); + transform: translate(-50%,-50%) rotate(-90deg) +} + +.taro-video-container.taro-video-type-fullscreen.taro-video-type-rotate-right { + -webkit-transform: translate(-50%,-50%) rotate(90deg); + transform: translate(-50%,-50%) rotate(90deg) +} + +.taro-video-video { + width: 100%; + height: 100%; + object-position: inherit +} + +.taro-video-cover { + position: absolute; + top: 0; + left: 0; + bottom: 0; + width: 100%; + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -webkit-flex-direction: column; + flex-direction: column; + -webkit-box-pack: center; + -webkit-justify-content: center; + justify-content: center; + -webkit-box-align: center; + -webkit-align-items: center; + align-items: center; + background-color: rgba(1,1,1,.5); + z-index: 1 +} + +.taro-video-cover-play-button { + width: 40px; + height: 40px; + background-size: 50%; + background-repeat: no-repeat; + background-position: 50% 50% +} + +.taro-video-cover-duration { + color: #fff; + font-size: 16px; + line-height: 1; + margin-top: 10px +} + +.taro-video-bar { + visibility: hidden; + height: 44px; + background-color: rgba(0,0,0,.5); + overflow: hidden; + position: absolute; + bottom: 0; + right: 0; + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-align: center; + -webkit-align-items: center; + align-items: center; + padding: 0 10px; + z-index: 0 +} + +.taro-video-bar.taro-video-bar-full { + left: 0 +} + +.taro-video-controls { + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-flex: 1; + -webkit-flex-grow: 1; + flex-grow: 1; + margin: 0 8.5px +} + +.taro-video-control-button { + width: 13px; + height: 15px; + padding: 14.5px 12.5px 14.5px 12.5px; + margin-left: -8.5px; + box-sizing: content-box +} + +.taro-video-control-button:after { + content: ""; + display: block; + width: 100%; + height: 100%; + background-size: 100%; + background-position: 50% 50%; + background-repeat: no-repeat +} + +.taro-video-control-button.taro-video-control-button-play:after,.taro-video-cover-play-button { + background-image: url("../images/play.png") +} + +.taro-video-control-button.taro-video-control-button-pause:after { + background-image: url("../images//pause.png") +} + +.taro-video-current-time,.taro-video-duration { + height: 14.5px; + line-height: 14.5px; + margin-top: 15px; + margin-bottom: 14.5px; + font-size: 12px; + color: #cbcbcb +} + +.taro-video-progress-container { + -webkit-box-flex: 2; + -webkit-flex-grow: 2; + flex-grow: 2; + position: relative +} + +.taro-video-progress { + height: 2px; + margin: 21px 12px; + background-color: hsla(0,0%,100%,.4); + position: relative +} + +.taro-video-progress-buffered { + position: absolute; + left: 0; + top: 0; + width: 0; + height: 100%; + -webkit-transition: width .1s; + transition: width .1s; + background-color: hsla(0,0%,100%,.8) +} + +.taro-video-ball { + width: 16px; + height: 16px; + padding: 14px; + position: absolute; + top: -21px; + box-sizing: content-box; + left: 0; + margin-left: -22px +} + +.taro-video-inner { + width: 100%; + height: 100%; + background-color: #fff; + border-radius: 50% +} + +.taro-video-danmu-button { + white-space: nowrap; + line-height: 1; + padding: 2px 10px; + border: 1px solid #fff; + border-radius: 5px; + font-size: 13px; + color: #fff; + margin: 0 8.5px +} + +.taro-video-danmu-button.taro-video-danmu-button-active { + border-color: #48c23d; + color: #48c23d +} + +.taro-video-fullscreen, +.taro-video-mute { + width: 17px; + height: 17px; + padding: 8.5px; + box-sizing: content-box; + background-size: 50%; + background-position: 50% 50%; + background-repeat: no-repeat +} +.taro-video-fullscreen { + background-image: url("../images/full.png"); +} + +.taro-video-fullscreen.taro-video-type-fullscreen { + background-image: url("../images/shrink.png") +} + +.taro-video-mute { + background-image: url("../images/unmute.png"); +} + +.taro-video-mute.taro-video-type-mute { + background-image: url("../images/mute.png") +} + +.taro-video-danmu { + position: absolute; + top: 0; + left: 0; + bottom: 0; + width: 100%; + margin-top: 14px; + margin-bottom: 44px; + font-size: 14px; + line-height: 14px; + overflow: visible +} + +.taro-video-danmu-item { + line-height: 1; + position: absolute; + color: #fff; + white-space: nowrap; + left: 100%; + -webkit-transform: translatex(0); + transform: translatex(0); + -webkit-transition-property: left,-webkit-transform; + transition-property: left,-webkit-transform; + transition-property: left,transform; + transition-property: left,transform,-webkit-transform; + -webkit-transition-duration: 3s; + transition-duration: 3s; + -webkit-transition-timing-function: linear; + transition-timing-function: linear +} + +.taro-video-toast { + pointer-events: none; + position: absolute; + left: 50%; + top: 50%; + -webkit-transform: translate(-50%,-50%); + transform: translate(-50%,-50%); + border-radius: 5px; + background-color: hsla(0,0%,100%,.8); + color: #000; + display: block; + visibility: hidden; +} + +.taro-video-toast.taro-video-toast-volume { + width: 100px; + height: 100px; + display: block +} + +.taro-video-toast-volume .taro-video-toast-title { + width: 100%; + font-size: 12px; + line-height: 16px; + text-align: center; + margin-top: 10px; + display: block +} + +.taro-video-toast-volume .taro-video-toast-icon { + fill: #000; + width: 50%; + height: 50%; + margin-left: 25%; + display: block; + background-image: url('../images/volume.png'); + background-size: 50%; + background-position: 50% 50%; + background-repeat: no-repeat +} + +.taro-video-toast-volume .taro-video-toast-value { + width: 80px; + height: 5px; + margin-top: 5px; + margin-left: 10px +} + +.taro-video-toast-volume .taro-video-toast-value>.taro-video-toast-value-content { + overflow: hidden +} + +.taro-video-toast-volume-grids { + width: 80px; + height: 5px +} + +.taro-video-toast-volume-grids-item { + float: left; + width: 7.1px; + height: 5px; + background-color: #000 +} + +.taro-video-toast-volume-grids-item:not(:first-child) { + margin-left: 1px +} + +.taro-video-toast.taro-video-toast-progress { + background-color: rgba(0,0,0,.8); + color: #fff; + font-size: 14px; + line-height: 18px; + padding: 6px +} \ No newline at end of file diff --git a/packages/taro-components/src/components/video/utils.js b/packages/taro-components/src/components/video/utils.js new file mode 100644 index 000000000000..d1e52025fe6c --- /dev/null +++ b/packages/taro-components/src/components/video/utils.js @@ -0,0 +1,14 @@ +export const formatTime = time => { + if (time === null) return '' + const sec = Math.round(time % 60) + const min = Math.round((time - sec) / 60) + return `${min < 10 ? `0${min}` : min}:${sec < 10 ? `0${sec}` : sec}` +} + +export const calcDist = (x, y) => { + return Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)) +} + +export const normalizeNumber = number => { + return Math.max(-1, Math.min(number, 1)) +} diff --git a/packages/taro-components/src/utils/touchable.js b/packages/taro-components/src/utils/touchable.js index 2db90382e3cc..9bae3a73cc80 100644 --- a/packages/taro-components/src/utils/touchable.js +++ b/packages/taro-components/src/utils/touchable.js @@ -8,35 +8,35 @@ const touchable = (opt = { return ComponentClass => { return class TouchableComponent extends Taro.Component { static defaultProps = { - bindTouchStart: null, - bindTouchMove: null, - bindTouchEnd: null, - bindTouchCancel: null, - bindLongTap: null + onTouchStart: null, + onTouchMove: null, + onTouchEnd: null, + onTouchCancel: null, + onLongTap: null } timer = null onTouchStart = e => { - const { bindTouchStart, bindLongTap } = this.props - bindTouchStart && bindTouchStart(e) + const { onTouchStart, onLongTap } = this.props + onTouchStart && onTouchStart(e) this.timer = setTimeout(() => { - bindLongTap && bindLongTap(e) + onLongTap && onLongTap(e) }, opt.longTapTime) } onTouchMove = e => { this.timer && clearTimeout(this.timer) - const { bindTouchMove } = this.props - bindTouchMove && bindTouchMove(e) + const { onTouchMove } = this.props + onTouchMove && onTouchMove(e) } onTouchEnd = e => { this.timer && clearTimeout(this.timer) - const { bindTouchEnd } = this.props - bindTouchEnd && bindTouchEnd(e) + const { onTouchEnd } = this.props + onTouchEnd && onTouchEnd(e) } onTouchCancel = e => { this.timer && clearTimeout(this.timer) - const { bindTouchCancel } = this.props - bindTouchCancel && bindTouchCancel(e) + const { onTouchCancel } = this.props + onTouchCancel && onTouchCancel(e) } render () { const props = { @@ -45,11 +45,11 @@ const touchable = (opt = { onTouchEnd: this.onTouchEnd, onTouchCancel: this.onTouchCancel, ...omit(this.props, [ - 'bindTouchStart', - 'bindTouchMove', - 'bindTouchEnd', - 'bindTouchCancel', - 'bindLongTap' + 'onTouchStart', + 'onTouchMove', + 'onTouchEnd', + 'onTouchCancel', + 'onLongTap' ]) } returndiff --git a/packages/taro-h5/src/api/index.js b/packages/taro-h5/src/api/index.js index b0f86fe6647d..ac1a638d160c 100644 --- a/packages/taro-h5/src/api/index.js +++ b/packages/taro-h5/src/api/index.js @@ -2,18 +2,19 @@ export * from './unsupportedApi' /* 已实现api */ -export * from './request' export * from './canvas' export * from './createAnimation' export * from './createSelectorQuery' -export * from './webSocket' -export * from './storage' +export * from './imageUtils' export * from './interactive' -export * from './tabBar' -export * from './system' -export * from './others' export * from './navigationBar' -export * from './imageUtils' +export * from './others' export * from './pullDownRefresh' +export * from './request' +export * from './storage' +export * from './system' +export * from './tabBar' +export * from './videoUtils' +export * from './webSocket' export * from './privateApis' diff --git a/packages/taro-h5/src/api/videoUtils/index.js b/packages/taro-h5/src/api/videoUtils/index.js new file mode 100644 index 000000000000..faffffacec55 --- /dev/null +++ b/packages/taro-h5/src/api/videoUtils/index.js @@ -0,0 +1,63 @@ +import { shouleBeObject, getParameterError } from '../utils' + +export function chooseImage (options) { + // options must be an Object + const isObject = shouleBeObject(options) + if (!isObject.res) { + const res = { errMsg: `chooseImage${isObject.msg}` } + console.error(res.errMsg) + return Promise.reject(res) + } + + const { count = 1, success, fail, complete } = options + const res = { + errMsg: 'chooseImage:ok', + tempFilePaths: [], + tempFiles: [] + } + + if (count && typeof count !== 'number') { + res.errMsg = getParameterError({ + name: 'chooseImage', + para: 'count', + correct: 'Number', + wrong: count + }) + console.error(res.errMsg) + typeof fail === 'function' && fail(res) + typeof complete === 'function' && complete(res) + return Promise.reject(res) + } + + let taroChooseImageId = document.getElementById('taroChooseImage') + if (!taroChooseImageId) { + let obj = document.createElement('input') + obj.setAttribute('type', 'file') + obj.setAttribute('id', 'taroChooseImage') + obj.setAttribute('multiple', 'multiple') + obj.setAttribute('accept', 'image/*') + obj.setAttribute('style', 'position: fixed; top: -4000px; left: -3000px; z-index: -300;') + document.body.appendChild(obj) + taroChooseImageId = document.getElementById('taroChooseImage') + } + let taroChooseImageCallback + const taroChooseImagePromise = new Promise(resolve => { + taroChooseImageCallback = resolve + }) + let TaroMouseEvents = document.createEvent('MouseEvents') + TaroMouseEvents.initEvent('click', true, true) + taroChooseImageId.dispatchEvent(TaroMouseEvents) + taroChooseImageId.onchange = function (e) { + let arr = Array.from(e.target.files) + arr && arr.forEach(item => { + let blob = new Blob([item]) + let url = URL.createObjectURL(blob) + res.tempFilePaths.push(url) + res.tempFiles.push({path: url, size: item.size, type: item.type}) + }) + typeof success === 'function' && success(res) + typeof complete === 'function' && complete(res) + taroChooseImageCallback(res) + } + return taroChooseImagePromise +} diff --git a/packages/taro-router/src/router/route.tsx b/packages/taro-router/src/router/route.tsx index 7a7cfb4665c7..77b0a1bb3375 100644 --- a/packages/taro-router/src/router/route.tsx +++ b/packages/taro-router/src/router/route.tsx @@ -133,7 +133,10 @@ class Route extends Component { const WrappedComponent = this.wrappedComponent return ( - +) diff --git a/packages/taro-router/src/router/router.tsx b/packages/taro-router/src/router/router.tsx index b9551ed0010d..e5b928f3525d 100644 --- a/packages/taro-router/src/router/router.tsx +++ b/packages/taro-router/src/router/router.tsx @@ -142,7 +142,9 @@ class Router extends Component{ const currentLocation = Taro._$router router.currentPages.length = this.state.routeStack.length return ( - +{this.state.routeStack.map(({ path, componentLoader, isIndex, key, isRedirect }, k) => { return (Date: Thu, 11 Apr 2019 16:02:37 +0800 Subject: [PATCH 2/4] =?UTF-8?q?feat(components):=20video=E7=BB=84=E4=BB=B6?= =?UTF-8?q?md=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/video/index.js | 6 +-- .../src/components/video/index.md | 46 ++++++++++++++----- 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/packages/taro-components/src/components/video/index.js b/packages/taro-components/src/components/video/index.js index 11109fe6ca88..b5d545cff400 100644 --- a/packages/taro-components/src/components/video/index.js +++ b/packages/taro-components/src/components/video/index.js @@ -17,7 +17,7 @@ import 'weui' /** * Video组件参数 * @typedef {Object} VideoProps - * @property {string} src 要播放视频的资源地址,支持云文件ID(2.3.0) + * @property {string} src 要播放视频的资源地址 * @property {boolean} [autoPauseIfNavigate=true] 当跳转到其它小程序页面时,是否自动暂停本页面的视频 * @property {boolean} [autoPauseIfOpenNative=true] 当跳转到其它微信原生页面时,是否自动暂停本页面的视频 * @property {boolean} [autoplay=false] 是否自动播放 @@ -41,12 +41,12 @@ import 'weui' * @property {boolean} [vslideGestureInFullscreen=true] 在全屏模式下,是否开启亮度与音量调节手势 * @property {number} [direction] 设置全屏时视频的方向,不指定则根据宽高比自动判断 * @property {number} [duration] 指定视频时长 - * @property {string} [poster] 视频封面的图片网络资源地址或云文件ID(2.3.0)。若 controls 属性值为 false 则设置 poster 无效 + * @property {string} [poster] 视频封面的图片网络资源地址。若 controls 属性值为 false 则设置 poster 无效 * @property {string} [title] 视频的标题,全屏时在顶部展示 * @property {Function} [onPlay] 当开始/继续播放时触发play事件 * @property {Function} [onPause] 当暂停播放时触发 pause 事件 * @property {Function} [onEnded] 当播放到末尾时触发 ended 事件 - * @property {Function} [onTimeupdate] 播放进度变化时触发,event.detail = {currentTime, duration} 。触发频率 250ms 一次 + * @property {Function} [onTimeUpdate] 播放进度变化时触发,event.detail = {currentTime, duration} 。触发频率 250ms 一次 * @property {Function} [onFullscreenChange] 视频进入和退出全屏时触发,event.detail = {fullScreen, direction},direction 有效值为 vertical 或 horizontal * @property {Function} [onWaiting] 视频出现缓冲时触发 * @property {Function} [onError] 视频播放出错时触发 diff --git a/packages/taro-components/src/components/video/index.md b/packages/taro-components/src/components/video/index.md index c4132aec0d2f..b767442a6ce6 100644 --- a/packages/taro-components/src/components/video/index.md +++ b/packages/taro-components/src/components/video/index.md @@ -2,15 +2,37 @@ | 是否支持 | 属性 | 类型 | 默认值 | 说明 | | -------- | -------------- | ----------- | ------ | ------------------------------------------------------------ | -| √ | src | String | | 要播放视频的资源地址 | -| √ | controls | Boolean | true | 是否显示默认播放控件(播放/暂停按钮、播放进度、时间) | -| √ | autoplay | Boolean | false | 是否自动播放 | -| √ | poster | String | | 视频封面的图片网络资源地址,如果 controls 属性值为 false 则设置 poster 无效 | -| | initial-time | Number | | 指定视频初始播放位置 | -| √ | loop | Boolean | false | 是否循环播放 | -| √ | muted | Boolean | false | 是否静音播放 | -| √ | bindplay | EventHandle | | 当开始/继续播放时触发play事件 | -| √ | bindpause | EventHandle | | 当暂停播放时触发 pause 事件 | -| √ | bindended | EventHandle | | 当播放到末尾时触发 ended 事件 | -| √ | bindtimeupdate | EventHandle | | 播放进度变化时触发。触发频率 250ms 一次 | -| √ | binderror | EventHandle | | 视频播放出错时触发 | +| | autoPauseIfNavigate | boolean | true |当跳转到其它小程序页面时,是否自动暂停本页面的视频 +| | autoPauseIfOpenNative | boolean | true |当跳转到其它微信原生页面时,是否自动暂停本页面的视频 +| | direction | number | |设置全屏时视频的方向,不指定则根据宽高比自动判断 +| √ | autoplay | boolean | false |是否自动播放 +| √ | controls | boolean | true |是否显示默认播放控件(播放/暂停按钮、播放进度、时间) +| √ | danmuBtn | boolean | false |是否显示弹幕按钮,只在初始化时有效,不能动态变更 +| √ | danmuList | Array. | [] |弹幕列表 +| √ | duration | number | |指定视频时长 +| √ | enableDanmu | boolean | false |是否展示弹幕,只在初始化时有效,不能动态变更 +| √ | enablePlayGesture | boolean | false |是否开启播放手势,即双击切换播放/暂停 +| √ | enableProgressGesture | boolean | true |是否开启控制进度的手势 +| √ | initialTime | number | 0 |指定视频初始播放位置 +| √ | loop | boolean | false |是否循环播放 +| √ | muted | boolean | false |是否静音播放 +| √ | objectFit | string | contain |当视频大小与 video 容器大小不一致时,视频的表现形式 +| √ | onEnded | Function | |当播放到末尾时触发 ended 事件 +| √ | onError | Function | |视频播放出错时触发 +| √ | onFullscreenChange | Function | |视频进入和退出全屏时触发,event.detail = {fullScreen, direction},direction 有效值为 vertical 或 horizontal +| √ | onPause | Function | |当暂停播放时触发 pause 事件 +| √ | onPlay | Function | |当开始/继续播放时触发play事件 +| √ | onProgress | Function | |加载进度变化时触发,只支持一段加载。event.detail = {buffered},百分比 +| √ | onTimeUpdate | Function | |播放进度变化时触发,event.detail = {currentTime, duration}。触发频率 250ms 一次 +| √ | onWaiting | Function | |视频出现缓冲时触发 +| √ | playBtnPosition | string | bottom |播放按钮的位置 +| √ | poster | string | |视频封面的图片网络资源地址。若 controls 属性值为 false 则设置 poster 无效 +| √ | showCenterPlayBtn | boolean | true |是否显示视频中间的播放按钮 +| √ | showFullscreenBtn | boolean | true |是否显示全屏按钮 +| √ | showMuteBtn | boolean | false |是否显示静音按钮 +| √ | showPlayBtn | boolean | true |是否显示视频底部控制栏的播放按钮 +| √ | showProgress | boolean | true |若不设置,宽度大于240时才会显示 +| √ | src | string | |要播放视频的资源地址 +| √ | title | string | |视频的标题,全屏时在顶部展示 +| √ | vslideGesture | boolean | false |在非全屏模式下,是否开启亮度与音量调节手势(同 pageGesture) +| √ | vslideGestureInFullscreen | boolean | true |在全屏模式下,是否开启亮度与音量调节手势 From 04f0580f7d415a62608af7e26089ab307e33d626 Mon Sep 17 00:00:00 2001 From: littly <544028951@qq.com> Date: Thu, 11 Apr 2019 16:09:57 +0800 Subject: [PATCH 3/4] =?UTF-8?q?fix(webpack-runner):=20=E6=8A=8Amobx-h5?= =?UTF-8?q?=E5=8A=A0=E5=85=A5=E9=BB=98=E8=AE=A4esnextModules=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/taro-webpack-runner/src/util/chain.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/taro-webpack-runner/src/util/chain.ts b/packages/taro-webpack-runner/src/util/chain.ts index ec33d2fe2f41..168e44da9a2e 100644 --- a/packages/taro-webpack-runner/src/util/chain.ts +++ b/packages/taro-webpack-runner/src/util/chain.ts @@ -150,7 +150,8 @@ const defaultEsnextModuleRegs = [ /@tarojs[/\\_]components/, /\btaro-components\b/, /@tarojs[/\\_]taro-h5/, /\btaro-h5\b/, /@tarojs[/\\_]router/, /\btaro-router\b/, - /@tarojs[/\\_]redux-h5/, /\btaro-redux-h5\b/ + /@tarojs[/\\_]redux-h5/, /\btaro-redux-h5\b/, + /@tarojs[/\\_]mobx-h5/, /\btaro-mobx-h5\b/ ] const getEsnextModuleRules = esnextModules => { From 3fc0ec01a7cc88c79b496ee368b08a3d557dc073 Mon Sep 17 00:00:00 2001 From: JinJinJin <709899428@qq.com> Date: Thu, 11 Apr 2019 16:39:26 +0800 Subject: [PATCH 4/4] =?UTF-8?q?fix(cli):=20=E4=BF=AE=E5=A4=8D=E4=BA=91?= =?UTF-8?q?=E5=BC=80=E5=8F=91=E6=A8=A1=E6=9D=BF=E7=94=9F=E6=88=90=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/taro-cli/templates/wxcloud/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/taro-cli/templates/wxcloud/index.js b/packages/taro-cli/templates/wxcloud/index.js index a6eff62324e4..d3b9c233cc84 100644 --- a/packages/taro-cli/templates/wxcloud/index.js +++ b/packages/taro-cli/templates/wxcloud/index.js @@ -103,7 +103,7 @@ module.exports = function (creater, params, helper, cb) { typescript: true }) } else { - creater.template(template, path.join(clientDirName, 'pagejs'), path.join(sourceDir, 'pages', 'index', 'index.weapp.js'), { + creater.template(template, path.join(clientDirName, 'pagejs'), path.join(sourceDir, 'pages', 'index', 'index.js'), { css: currentStyleExt }) creater.template(template, path.join(clientDirName, 'components', 'login', 'index'), path.join(sourceDir, 'components', 'login', 'index.weapp.js'), {