AudioPlay.vue 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. <template>
  2. <div class="audio-wrapper">
  3. <template v-if="'big' === viewSize">
  4. <div class="audio-icon">
  5. <SvgIcon icon-class="pre" @click="changeFile('prev')" />
  6. <SvgIcon :icon-class="iconClass" size="30" @click="playAudio" />
  7. <SvgIcon icon-class="next" @click="changeFile('next')" />
  8. <SvgIcon icon-class="1x" size="12" />
  9. </div>
  10. <div v-if="showSlider" class="slider-area">
  11. <span class="audio-time">{{ secondFormatConversion(audio.current_time) }} / {{ audio_allTime }}</span>
  12. <el-slider
  13. v-model="play_value"
  14. class="audio-slider"
  15. :format-tooltip="formatProcessToolTip"
  16. @change="changeCurrentTime"
  17. />
  18. </div>
  19. </template>
  20. <div v-else>
  21. <div class="audio-small">
  22. <span>{{ audioIndex + 1 }}.</span>
  23. <SvgIcon
  24. v-if="(audioIndex === curAudioIndex && 'list' != viewMethod) || 'list' === viewMethod"
  25. :icon-class="iconClass"
  26. size="14"
  27. @click="playAudio"
  28. />
  29. <span class="audio-time">{{ audio_allTime }}</span>
  30. </div>
  31. </div>
  32. <audio
  33. :id="fileId"
  34. :ref="fileId"
  35. :src="url"
  36. preload="metadata"
  37. @loadedmetadata="onLoadedmetadata"
  38. @timeupdate="onTimeupdate"
  39. @canplaythrough="oncanplaythrough"
  40. ></audio>
  41. </div>
  42. </template>
  43. <script>
  44. import { GetFileURLMap } from '@/api/app';
  45. import { secondFormatConversion } from '@/utils/transform';
  46. export default {
  47. name: 'AudioPlay',
  48. props: {
  49. fileId: {
  50. type: String,
  51. required: true,
  52. },
  53. viewSize: {
  54. type: String,
  55. required: true,
  56. },
  57. viewMethod: {
  58. type: String,
  59. default: 'independent',
  60. },
  61. audioIndex: {
  62. type: Number,
  63. default: 0,
  64. },
  65. curAudioIndex: {
  66. type: Number,
  67. default: 0,
  68. },
  69. showSlider: {
  70. type: Boolean,
  71. default: false,
  72. },
  73. // 是否显示音频进度条
  74. showProgress: {
  75. type: Boolean,
  76. default: true,
  77. },
  78. },
  79. data() {
  80. return {
  81. secondFormatConversion,
  82. url: '',
  83. audio: {
  84. paused: true,
  85. playing: false,
  86. // 音频当前播放时长
  87. current_time: 0,
  88. // 音频最大播放时长
  89. max_time: 0,
  90. isPlaying: false,
  91. loading: false,
  92. },
  93. play_value: 0,
  94. audio_allTime: null, // 展示总时间
  95. };
  96. },
  97. computed: {
  98. iconClass() {
  99. return this.audio.paused ? 'paused' : 'playing';
  100. },
  101. },
  102. watch: {
  103. fileId: {
  104. handler(val) {
  105. if (!val) return;
  106. GetFileURLMap({ file_id_list: [val] }).then(({ url_map }) => {
  107. this.url = url_map[val];
  108. });
  109. },
  110. immediate: true,
  111. },
  112. },
  113. mounted() {
  114. if (!this.fileId) return;
  115. this.$refs[this.fileId].addEventListener('ended', () => {
  116. this.audio.paused = true;
  117. });
  118. this.$refs[this.fileId].addEventListener('pause', () => {
  119. this.audio.paused = true;
  120. });
  121. this.$refs[this.fileId].addEventListener('play', () => {
  122. this.audio.paused = false;
  123. });
  124. },
  125. methods: {
  126. playAudio() {
  127. if (!this.url) return;
  128. const audio = this.$refs[this.fileId];
  129. let audioArr = document.getElementsByTagName('audio');
  130. if (audioArr && audioArr.length > 0) {
  131. for (let i = 0; i < audioArr.length; i++) {
  132. if (audioArr[i].src === this.url) {
  133. if (audioArr[i].id !== this.fileId) {
  134. audioArr[i].pause();
  135. }
  136. } else {
  137. audioArr[i].pause();
  138. }
  139. }
  140. }
  141. audio.paused ? audio.play() : audio.pause();
  142. },
  143. // 进度条格式化toolTip
  144. formatProcessToolTip(index) {
  145. let _index = parseInt((this.audio.max_time / 100) * index);
  146. return secondFormatConversion(_index);
  147. },
  148. // 点击 拖拽播放音频
  149. changeCurrentTime(value) {
  150. let audioId = this.fileId;
  151. this.$refs[audioId].play();
  152. this.audio.playing = true;
  153. this.$refs[audioId].currentTime = parseInt((value / 100) * this.audio.max_time);
  154. },
  155. // 音频加载完之后
  156. onLoadedmetadata(res) {
  157. this.audio.max_time = parseInt(res.target.duration);
  158. this.audio_allTime = secondFormatConversion(this.audio.max_time);
  159. },
  160. // 当音频当前时间改变后,进度条也要改变
  161. onTimeupdate(res) {
  162. let audioId = this.fileId;
  163. this.audio.current_time = res.target.currentTime;
  164. this.play_value = (this.audio.current_time / this.audio.max_time) * 100;
  165. if (this.audio.current_time * 1000 > this.ed) {
  166. this.$refs[audioId].pause();
  167. }
  168. },
  169. onTimeupdateTime(res, playFlag) {
  170. if (!res && res !== 0) return;
  171. let audioId = this.audioId;
  172. this.$refs[audioId].currentTime = res;
  173. this.play_value = (res / this.audio.max_time) * 100;
  174. if (playFlag) {
  175. let audio = document.getElementsByTagName('audio');
  176. audio.forEach((item) => {
  177. if (item.id !== audioId) {
  178. item.pause();
  179. }
  180. });
  181. this.$refs[audioId].play();
  182. }
  183. },
  184. oncanplaythrough() {
  185. this.audio.loading = false;
  186. },
  187. changeFile(type) {
  188. this.$emit('changeFile', type);
  189. },
  190. },
  191. };
  192. </script>
  193. <style lang="scss" scoped>
  194. .audio-wrapper {
  195. display: flex;
  196. flex-direction: column;
  197. row-gap: 38px;
  198. .audio-icon {
  199. display: flex;
  200. column-gap: 15%;
  201. align-items: center;
  202. justify-content: end;
  203. .svg-icon {
  204. cursor: pointer;
  205. }
  206. }
  207. .slider-area {
  208. font-size: 14px;
  209. text-align: left;
  210. .audio-slider {
  211. width: 100%;
  212. :deep .el-slider__runway {
  213. height: 4px;
  214. margin-top: 4px;
  215. background-color: #fff;
  216. }
  217. :deep .el-slider__button {
  218. width: 4px;
  219. height: 4px;
  220. border: none;
  221. }
  222. :deep .el-slider__bar {
  223. height: 4px;
  224. background-color: #076aff;
  225. }
  226. :deep .el-slider__button-wrapper {
  227. top: -16px;
  228. }
  229. }
  230. }
  231. .audio-small {
  232. display: flex;
  233. column-gap: 8px;
  234. align-items: center;
  235. min-width: 110px;
  236. padding: 13px;
  237. background-color: #d9d9d9;
  238. .svg-icon {
  239. cursor: pointer;
  240. }
  241. }
  242. }
  243. </style>