CVideo.vue 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. <template>
  2. <div
  3. class="c-video iconfont"
  4. :style="styles"
  5. >
  6. <template v-if="canPlay">
  7. <video
  8. ref="player"
  9. class="c-video__inst"
  10. :src="src"
  11. autoplay
  12. :muted.prop="muted"
  13. @ended="onEnded"
  14. @error="onError"
  15. />
  16. <canvas
  17. v-show="isSnapping"
  18. ref="canvas"
  19. class="c-video__canvas"
  20. />
  21. </template>
  22. </div>
  23. </template>
  24. <script>
  25. import { getAssetUrl } from '@/api/asset'
  26. export default {
  27. name: 'CVideo',
  28. inject: ['control'],
  29. props: {
  30. node: {
  31. type: Object,
  32. default: null
  33. }
  34. },
  35. data () {
  36. return {
  37. ignore: false,
  38. url: ''
  39. }
  40. },
  41. computed: {
  42. isSnapping () {
  43. return this.control.snapping
  44. },
  45. styles () {
  46. const {
  47. width,
  48. height,
  49. radius
  50. } = this.node
  51. return {
  52. width: `${width}px`,
  53. height: `${height}px`,
  54. 'font-size': `${Math.min(width, height) / 3 | 0}px`,
  55. 'border-radius': `${radius}px`
  56. }
  57. },
  58. src () {
  59. return this.url ? getAssetUrl(this.url) : ''
  60. },
  61. muted () {
  62. return this.node.mute || this.control.muted
  63. },
  64. canPlay () {
  65. return !this.ignore && this.url
  66. }
  67. },
  68. watch: {
  69. 'node.sources' () {
  70. this.reset()
  71. },
  72. isSnapping (val) {
  73. let ignore = false
  74. const player = this.$refs.player
  75. if (player) {
  76. if (val) {
  77. // 尽量保障出画面
  78. if (player.currentTime && player.currentTime >= 0.5) {
  79. player.pause()
  80. this.getSnap()
  81. } else {
  82. ignore = true
  83. }
  84. } else {
  85. player.play()
  86. const canvas = this.$refs.canvas
  87. if (canvas) {
  88. canvas.getContext('2d').clearRect(0, 0, canvas.width, canvas.height)
  89. }
  90. }
  91. }
  92. this.ignore = ignore
  93. }
  94. },
  95. created () {
  96. this.reset()
  97. },
  98. methods: {
  99. reset () {
  100. this.$index = 0
  101. this.choose()
  102. },
  103. choose () {
  104. const { sources, toggleType } = this.node
  105. const length = sources.length
  106. if (length === 0) {
  107. this.url = ''
  108. return
  109. }
  110. if (length === 1) {
  111. this.url = sources[0].keyName
  112. return
  113. }
  114. let index
  115. switch (toggleType) {
  116. case 'cycle':
  117. index = this.$index
  118. this.$index = (index + 1) % length
  119. break
  120. case 'random':
  121. index = Math.random() * length | 0
  122. if (sources[index]?.keyName === this.url) {
  123. if (index === 0) {
  124. index += 1
  125. } else {
  126. index -= 1
  127. }
  128. }
  129. break
  130. default:
  131. return
  132. }
  133. this.url = sources[index].keyName
  134. },
  135. onEnded () {
  136. this.$refs.player.currentTime = 0
  137. if (this.node.sources.length > 1) {
  138. this.choose()
  139. }
  140. this.$refs.player.play()
  141. },
  142. onError (e) {
  143. console.warn('video error', e)
  144. if (this.node.sources.length > 1) {
  145. this.choose()
  146. }
  147. },
  148. getSnap () {
  149. const video = this.$refs.player
  150. const canvas = this.$refs.canvas
  151. if (video && canvas) {
  152. const cxt = canvas.getContext('2d')
  153. canvas.width = this.node.width
  154. canvas.height = this.node.height
  155. cxt.drawImage(video, 0, 0, this.node.width, this.node.height)
  156. }
  157. }
  158. }
  159. }
  160. </script>
  161. <style lang="scss" scoped>
  162. .c-video {
  163. position: relative;
  164. display: inline-flex;
  165. justify-content: center;
  166. align-items: center;
  167. background-color: rgba(255, 255, 255, 0.8);
  168. overflow: hidden;
  169. &::before {
  170. content: "\ecc1";
  171. font-size: inherit;
  172. }
  173. &__inst,
  174. &__canvas {
  175. position: absolute;
  176. top: 0;
  177. left: 0;
  178. width: 100%;
  179. height: 100%;
  180. }
  181. &__inst {
  182. object-fit: fill;
  183. }
  184. }
  185. </style>