index.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. import { defineComponent, PropType } from 'vue'
  2. import styles from './index.module.less'
  3. import Plyr from 'plyr'
  4. import 'plyr/dist/plyr.css'
  5. import { Button, Icon, Loading, Toast } from 'vant'
  6. import iconVideoPlay from '@/common/images/icon_video_play.png'
  7. import { browser } from '@/helpers/utils'
  8. import { state } from '@/state'
  9. import { listenerMessage, postMessage } from '@/helpers/native-message'
  10. export default defineComponent({
  11. name: 'col-video',
  12. props: {
  13. trySee: {
  14. // 是否试看
  15. type: Boolean,
  16. default: false
  17. },
  18. freeTitleStatus: {
  19. type: Boolean,
  20. default: true
  21. },
  22. // 试看比例
  23. freeRate: {
  24. type: Number,
  25. default: 100
  26. },
  27. setting: {
  28. type: Object,
  29. default: () => {}
  30. },
  31. controls: Boolean,
  32. height: String,
  33. src: {
  34. type: String,
  35. default: ''
  36. },
  37. poster: {
  38. type: String,
  39. default: ''
  40. },
  41. styleValue: {
  42. type: Object,
  43. default: () => ({})
  44. },
  45. preload: {
  46. type: String as PropType<'auto' | 'metadata' | 'none'>,
  47. default: 'auto'
  48. },
  49. currentTime: {
  50. type: Boolean,
  51. default: true
  52. },
  53. playsinline: {
  54. type: Boolean,
  55. default: true
  56. },
  57. onPlay: {
  58. type: Function,
  59. default: () => {}
  60. },
  61. isBuy: {
  62. // 是否把购买方法在外部调用
  63. type: Boolean,
  64. default: false
  65. },
  66. onBuyEmit: {
  67. type: Function,
  68. default: () => {}
  69. }
  70. },
  71. data() {
  72. return {
  73. player: null as any,
  74. // playTime: 0,
  75. loading: true, // 首次进入加载中
  76. trySeeOver: false, // 试看是否结束
  77. showSeeStatus: true // 是否显示试看状态
  78. }
  79. },
  80. mounted() {
  81. this._init()
  82. listenerMessage('setVideoPlayer', result => {
  83. const content = result?.content
  84. if (content.status === 'pause') {
  85. this.player.pause()
  86. }
  87. })
  88. },
  89. beforeUnmount() {
  90. postMessage({
  91. api: 'limitScreenRecord',
  92. content: {
  93. type: 0
  94. }
  95. })
  96. },
  97. computed: {
  98. computedSeeStatus() {
  99. // console.log(
  100. // this.showSeeStatus,
  101. // this.trySee,
  102. // this.trySeeOver,
  103. // 'this.showSeeStatus, this.trySee'
  104. // )
  105. return this.showSeeStatus && this.trySee
  106. },
  107. playTime() {
  108. // 允许播放时间
  109. const player = this.player
  110. const playTime = (player.duration * this.freeRate) / 100
  111. return playTime || 0
  112. }
  113. },
  114. methods: {
  115. _init() {
  116. // controls: [
  117. // 'play-large' , // 中间的大播放按钮
  118. // 'restart' , // 重新开始播放
  119. // 'rewind' , // 按寻道时间倒带(默认 10 秒)
  120. // 'play' , // 播放/暂停播放
  121. // 'fast-forward' , // 快进查找时间(默认 10 秒)
  122. // 'progress' , // 播放和缓冲的进度条和滑动条
  123. // 'current-time' , // 播放的当前时间
  124. // ' duration' , // 媒体的完整持续时间
  125. // 'mute' , // 切换静音
  126. // 'volume', // 音量控制
  127. // 'captions' , // 切换字幕
  128. // 'settings' , // 设置菜单
  129. // 'pip' , // 画中画(当前仅 Safari)
  130. // 'airplay' , // Airplay(当前仅 Safari)
  131. // 'download ' , // 显示一个下载按钮,其中包含指向当前源或您在选项中指定的自定义 URL 的链接
  132. // 'fullscreen' , // 切换全屏
  133. // ] ;
  134. const controls = [
  135. 'play-large',
  136. 'play',
  137. 'progress',
  138. 'captions',
  139. 'fullscreen'
  140. ]
  141. if (this.currentTime) {
  142. controls.push('current-time')
  143. }
  144. const params: any = {
  145. controls: controls,
  146. ...this.setting,
  147. invertTime: false
  148. }
  149. if (browser().iPhone) {
  150. params.fullscreen = {
  151. enabled: true,
  152. fallback: 'force',
  153. iosNative: true
  154. }
  155. }
  156. this.player = new Plyr((this as any).$refs.video, params)
  157. // fullscreen: {
  158. // enabled: true,
  159. // fallback: 'force',
  160. // iosNative: true
  161. // }
  162. this.player.elements.container
  163. ? (this.player.elements.container.style.height = this.height || '210px')
  164. : null
  165. if (this.preload === 'none') {
  166. this.loading = false
  167. }
  168. this.player.on('loadedmetadata', () => {
  169. this.loading = false
  170. if (this.trySee) {
  171. this.domPlayVisibility()
  172. } else {
  173. this.domPlayVisibility(false)
  174. }
  175. // 监听播放事件
  176. const _this = this
  177. this.player.on('timeupdate', () => {
  178. const players = _this.player
  179. if (players.currentTime >= this.playTime && _this.trySee) {
  180. players.pause()
  181. _this.trySeeOver = true
  182. _this.showSeeStatus = true
  183. _this.domPlayVisibility() // 试看结束后隐藏播放按钮
  184. }
  185. })
  186. })
  187. this.player.on('play', () => {
  188. postMessage(
  189. {
  190. api: 'getDeviceStatus',
  191. content: {
  192. type: 'video'
  193. }
  194. },
  195. (res: any) => {
  196. // 判断是否在录屏中, 如果在录屏则不允许播放
  197. if (res.content.status == '1') {
  198. Toast('为了保证数据安全,请不要录屏')
  199. this.player.pause()
  200. }
  201. }
  202. )
  203. postMessage({
  204. api: 'limitScreenRecord',
  205. content: {
  206. type: 1
  207. }
  208. })
  209. this.onPlay && this.onPlay()
  210. })
  211. this.player.on('enterfullscreen', () => {
  212. console.log('fullscreen')
  213. const i = document.createElement('i')
  214. i.id = 'fullscreen-back'
  215. i.className = 'van-icon van-icon-arrow-left video-back'
  216. i.addEventListener('click', () => {
  217. this.player.fullscreen.exit()
  218. })
  219. console.log(document.getElementsByClassName('plyr'))
  220. document.getElementsByClassName('plyr')[0].appendChild(i)
  221. })
  222. this.player.on('exitfullscreen', () => {
  223. console.log('exitfullscreen')
  224. const i = document.getElementById('fullscreen-back')
  225. i && i.remove()
  226. })
  227. },
  228. // 操作功能
  229. domPlayVisibility(hide = true) {
  230. const controls = document.querySelector('.plyr__controls')
  231. const controls2 = document.querySelector('.plyr__control--overlaid')
  232. if (hide) {
  233. controls?.setAttribute('style', 'display:none')
  234. controls2?.setAttribute('style', 'display:none')
  235. } else {
  236. controls?.removeAttribute('style')
  237. setTimeout(() => {
  238. controls2?.removeAttribute('style')
  239. }, 200)
  240. }
  241. },
  242. onClickPlay() {
  243. this.player.play()
  244. this.domPlayVisibility(false)
  245. this.showSeeStatus = false
  246. },
  247. onBuy() {
  248. if (this.isBuy) {
  249. this.onBuyEmit()
  250. return
  251. }
  252. this.$router.back()
  253. },
  254. onReplay() {
  255. this.player.restart()
  256. this.player.play()
  257. this.domPlayVisibility(false)
  258. this.trySeeOver = false
  259. this.showSeeStatus = false
  260. }
  261. },
  262. unmounted() {
  263. this.player?.destroy()
  264. },
  265. render() {
  266. return (
  267. <div class={styles['video-container']}>
  268. {/* <div ref="video" class={styles['video']} style={{ ...this.styleValue }}> */}
  269. {/* <Icon
  270. name="arrow-left"
  271. class={styles.videoBack}
  272. size="20"
  273. color="#fff"
  274. /> */}
  275. <video
  276. ref="video"
  277. class={styles['video']}
  278. src={this.src}
  279. playsinline={this.playsinline}
  280. poster={this.poster}
  281. preload={this.preload}
  282. style={{ ...this.styleValue }}
  283. ></video>
  284. {/* </div> */}
  285. {/* 加载视频使用 */}
  286. {this.loading && (
  287. <div
  288. class={styles.loadingVideo}
  289. style={{
  290. height: this.height || '210px'
  291. }}
  292. >
  293. <Loading
  294. size={36}
  295. color="#2dc7aa"
  296. vertical
  297. style={{ height: '100%', justifyContent: 'center' }}
  298. >
  299. 加载中...
  300. </Loading>
  301. </div>
  302. )}
  303. {/* 试看结束 */}
  304. {this.trySee && this.computedSeeStatus && !this.loading && (
  305. <div
  306. class={[styles.loadingVideo, styles.playOver]}
  307. style={{
  308. height: this.height || '210px'
  309. }}
  310. >
  311. {!this.trySeeOver ? (
  312. <>
  313. <Icon
  314. name={iconVideoPlay}
  315. size={50}
  316. onClick={this.onClickPlay}
  317. />
  318. <p class={styles.freeTxt}>
  319. 免费试看
  320. {/* {this.freeTitleStatus ? '试看' : '领取'} */}
  321. </p>
  322. {/* <p class={styles.freeRate}>每课时可试看{this.freeRate}%</p> */}
  323. </>
  324. ) : (
  325. <>
  326. {state.platformType === 'STUDENT' ? (
  327. <p class={styles.tips}>
  328. {this.freeTitleStatus
  329. ? '免费试看结束,购买完整课程后继续学习'
  330. : '试看结束,领取课程后继续学习'}
  331. </p>
  332. ) : (
  333. <p class={styles.tips}>
  334. 若需完整观看,请下载酷乐秀领取或购买
  335. </p>
  336. )}
  337. {state.platformType === 'STUDENT' && (
  338. <Button
  339. class={styles.btn}
  340. type="primary"
  341. round
  342. size="small"
  343. onClick={this.onBuy}
  344. >
  345. {state.platformType === 'STUDENT'
  346. ? this.freeTitleStatus
  347. ? '立即购买'
  348. : '免费领取'
  349. : '返回免费'}
  350. </Button>
  351. )}
  352. <div
  353. class={state.platformType !== 'STUDENT' && styles.replay}
  354. onClick={this.onReplay}
  355. >
  356. <Icon
  357. name="replay"
  358. style={{ marginRight: '5px' }}
  359. size={16}
  360. />
  361. 重播
  362. </div>
  363. </>
  364. )}
  365. </div>
  366. )}
  367. </div>
  368. )
  369. }
  370. })