AudioPlay.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  1. <template>
  2. <div class="audio-wrapper">
  3. <div v-if="'big' === viewSize" class="audio-big">
  4. <div v-if="'list' === viewMethod">
  5. <div v-if="fileNameDisplay == 'true'" class="audio-name">{{ curAudioIndex + 1 }}. {{ fileName }}</div>
  6. <div v-if="showSlider" class="slider-area">
  7. <span class="audio-time">{{ secondFormatConversion(audio.current_time) }}</span>
  8. <el-slider
  9. v-model="play_value"
  10. class="audio-slider"
  11. :format-tooltip="formatProcessToolTip"
  12. :style="{ '--slider-color': topicColor }"
  13. @change="changeCurrentTime"
  14. />
  15. <span class="audio-time">{{ audio_allTime }}</span>
  16. </div>
  17. <div class="audio-icon">
  18. <SvgIcon icon-class="pre" size="12" :color="topicColor" @click="changeFile('prev')" />
  19. <SvgIcon :icon-class="iconClass" :color="topicColor" size="26" @click="playAudio" />
  20. <SvgIcon icon-class="next" size="12" :color="topicColor" @click="changeFile('next')" />
  21. </div>
  22. </div>
  23. <div v-else class="independent-big">
  24. <div v-if="fileNameDisplay == 'true'" class="audio-name">{{ audioIndex + 1 }}. {{ fileName }}</div>
  25. <div class="slider-area">
  26. <SvgIcon :icon-class="iconClass" size="18" :color="topicColor" @click="playAudio" style="cursor: pointer" />
  27. <span class="audio-time">{{ secondFormatConversion(audio.current_time) }}</span>
  28. <el-slider
  29. v-model="play_value"
  30. class="audio-slider"
  31. :format-tooltip="formatProcessToolTip"
  32. :style="{ '--slider-color': topicColor }"
  33. @change="changeCurrentTime"
  34. />
  35. <span class="audio-time">{{ audio_allTime }}</span>
  36. </div>
  37. </div>
  38. </div>
  39. <!-- <div v-else-if="'middle' === viewSize" class="audio-middle">
  40. <div v-if="fileNameDisplay == 'true'" class="audio-name">{{ audioIndex + 1 }}. {{ fileName }}</div>
  41. <div class="slider-area">
  42. <SvgIcon :icon-class="iconClass" size="18" :color="topicColor" @click="playAudio" />
  43. <span class="audio-time">{{ secondFormatConversion(audio.current_time) }}</span>
  44. <el-slider
  45. v-model="play_value"
  46. class="audio-slider"
  47. :format-tooltip="formatProcessToolTip"
  48. :style="{ '--slider-color': topicColor }"
  49. @change="changeCurrentTime"
  50. />
  51. <span class="audio-time">{{ audio_allTime }}</span>
  52. </div>
  53. </div> -->
  54. <div v-else-if="'middle' === viewSize" class="audio-middle">
  55. <div v-if="fileNameDisplay == 'true'" class="audio-name">{{ audioIndex + 1 }}. {{ fileName }}</div>
  56. <span class="icon-box" :style="{ borderColor: topicColor }">
  57. <SvgIcon
  58. :icon-class="iconClass"
  59. size="12"
  60. :color="topicColor"
  61. :style="{ marginLeft: audio.paused ? '4px' : '0' }"
  62. @click="playAudio"
  63. />
  64. </span>
  65. </div>
  66. <div v-else-if="'small' === viewSize" class="audio-small">
  67. <div v-if="fileNameDisplay == 'true'" class="audio-name">{{ audioIndex + 1 }}. {{ fileName }}</div>
  68. <SvgIcon
  69. icon-class="sound"
  70. size="20"
  71. :color="topicColor"
  72. @click="playAudio"
  73. :class="{ 'sound-icon--playing': !audio.paused }"
  74. />
  75. </div>
  76. <div v-else :class="['audio-list', { active: audioIndex === curAudioIndex }]" :style="cssVars">
  77. <div class="active-mark">
  78. <SvgIcon
  79. v-if="audioIndex === curAudioIndex"
  80. icon-class="paused"
  81. size="12"
  82. :color="topicColor"
  83. style="cursor: default"
  84. />
  85. </div>
  86. <span class="audio-name">{{ audioIndex + 1 }}. {{ fileName }}</span>
  87. <span class="audio-time">{{ audio_allTime }}</span>
  88. </div>
  89. <audio
  90. :id="fileId"
  91. :ref="fileId"
  92. :src="url"
  93. preload="metadata"
  94. @loadedmetadata="onLoadedmetadata"
  95. @timeupdate="onTimeupdate"
  96. @canplaythrough="oncanplaythrough"
  97. ></audio>
  98. </div>
  99. </template>
  100. <script>
  101. import { GetFileURLMap } from '@/api/app';
  102. import { secondFormatConversion } from '@/utils/transform';
  103. export default {
  104. name: 'AudioPlay',
  105. props: {
  106. fileId: {
  107. type: String,
  108. required: true,
  109. },
  110. fileName: {
  111. type: String,
  112. default: '',
  113. },
  114. viewSize: {
  115. type: String,
  116. default: 'big',
  117. },
  118. viewMethod: {
  119. type: String,
  120. default: 'independent',
  121. },
  122. audioIndex: {
  123. type: Number,
  124. default: 0,
  125. },
  126. curAudioIndex: {
  127. type: Number,
  128. default: 0,
  129. },
  130. showSlider: {
  131. type: Boolean,
  132. default: false,
  133. },
  134. // 是否显示音频进度条
  135. showProgress: {
  136. type: Boolean,
  137. default: true,
  138. },
  139. fileNameDisplay: {
  140. type: String,
  141. default: 'true',
  142. },
  143. topicColor: {
  144. type: String,
  145. default: '#076aff',
  146. },
  147. assistColor: {
  148. type: String,
  149. default: '#076aff',
  150. },
  151. },
  152. data() {
  153. return {
  154. secondFormatConversion,
  155. url: '',
  156. audio: {
  157. paused: true,
  158. playing: false,
  159. // 音频当前播放时长
  160. current_time: 0,
  161. // 音频最大播放时长
  162. max_time: 0,
  163. isPlaying: false,
  164. loading: false,
  165. },
  166. play_value: 0,
  167. audio_allTime: null, // 展示总时间
  168. };
  169. },
  170. computed: {
  171. iconClass() {
  172. return this.audio.paused ? 'paused' : 'playing';
  173. },
  174. cssVars() {
  175. return {
  176. '--assist-color': this.assistColor,
  177. };
  178. },
  179. },
  180. watch: {
  181. fileId: {
  182. handler(val) {
  183. if (!val) return;
  184. GetFileURLMap({ file_id_list: [val] }).then(({ url_map }) => {
  185. this.url = url_map[val];
  186. });
  187. },
  188. immediate: true,
  189. },
  190. },
  191. mounted() {
  192. if (!this.fileId) return;
  193. this.$refs[this.fileId].addEventListener('ended', () => {
  194. this.audio.paused = true;
  195. });
  196. this.$refs[this.fileId].addEventListener('pause', () => {
  197. this.audio.paused = true;
  198. });
  199. this.$refs[this.fileId].addEventListener('play', () => {
  200. this.audio.paused = false;
  201. });
  202. },
  203. methods: {
  204. getCurTime(curTime) {
  205. this.curTime = curTime * 1000;
  206. },
  207. playAudio() {
  208. if (!this.url) return;
  209. const audio = this.$refs[this.fileId];
  210. let audioArr = document.getElementsByTagName('audio');
  211. if (audioArr && audioArr.length > 0) {
  212. for (let i = 0; i < audioArr.length; i++) {
  213. if (audioArr[i].src === this.url) {
  214. if (audioArr[i].id !== this.fileId) {
  215. audioArr[i].pause();
  216. }
  217. } else {
  218. audioArr[i].pause();
  219. }
  220. }
  221. }
  222. audio.paused ? audio.play() : audio.pause();
  223. },
  224. // 进度条格式化toolTip
  225. formatProcessToolTip(index) {
  226. let _index = parseInt((this.audio.max_time / 100) * index);
  227. return secondFormatConversion(_index);
  228. },
  229. // 点击 拖拽播放音频
  230. changeCurrentTime(value) {
  231. let audioId = this.fileId;
  232. this.$refs[audioId].play();
  233. this.audio.playing = true;
  234. this.$refs[audioId].currentTime = parseInt((value / 100) * this.audio.max_time);
  235. },
  236. // 音频加载完之后
  237. onLoadedmetadata(res) {
  238. this.audio.max_time = parseInt(res.target.duration);
  239. this.audio_allTime = secondFormatConversion(this.audio.max_time);
  240. },
  241. // 当音频当前时间改变后,进度条也要改变
  242. onTimeupdate(res) {
  243. let audioId = this.fileId;
  244. this.audio.current_time = res.target.currentTime;
  245. this.play_value = (this.audio.current_time / this.audio.max_time) * 100;
  246. if (this.audio.current_time * 1000 > this.ed) {
  247. this.$refs[audioId].pause();
  248. }
  249. },
  250. onTimeupdateTime(res, playFlag) {
  251. if (!res && res !== 0) return;
  252. let audioId = this.audioId;
  253. this.$refs[audioId].currentTime = res;
  254. this.play_value = (res / this.audio.max_time) * 100;
  255. if (playFlag) {
  256. let audio = document.getElementsByTagName('audio');
  257. audio.forEach((item) => {
  258. if (item.id !== audioId) {
  259. item.pause();
  260. }
  261. });
  262. this.$refs[audioId].play();
  263. }
  264. },
  265. oncanplaythrough() {
  266. this.audio.loading = false;
  267. },
  268. changeFile(type) {
  269. this.$emit('changeFile', type);
  270. },
  271. },
  272. };
  273. </script>
  274. <style lang="scss" scoped>
  275. .audio-wrapper {
  276. .audio-time {
  277. white-space: nowrap;
  278. }
  279. .audio-big {
  280. display: flex;
  281. flex-direction: column;
  282. padding: 12px 0;
  283. text-align: center;
  284. .audio-name {
  285. padding: 8px;
  286. margin-bottom: 8px;
  287. font-size: 14px;
  288. background-color: #eee;
  289. border-radius: 4px;
  290. }
  291. .audio-icon {
  292. display: flex;
  293. column-gap: 32px;
  294. align-items: center;
  295. justify-content: center;
  296. .svg-icon {
  297. cursor: pointer;
  298. }
  299. }
  300. }
  301. .independent-big {
  302. .audio-name {
  303. text-align: left;
  304. }
  305. }
  306. .audio-middle1 {
  307. width: 280px;
  308. padding: 12px 16px;
  309. border-radius: 4px;
  310. .audio-name {
  311. padding: 8px;
  312. margin-bottom: 8px;
  313. font-size: 14px;
  314. text-align: left;
  315. background-color: #eee;
  316. border-radius: 4px;
  317. }
  318. }
  319. .audio-middle {
  320. display: flex;
  321. flex-direction: column;
  322. column-gap: 10px;
  323. align-items: left;
  324. margin-left: 5px;
  325. .audio-name {
  326. padding: 8px;
  327. margin-bottom: 8px;
  328. font-size: 14px;
  329. text-align: center;
  330. background-color: #eee;
  331. border-radius: 4px;
  332. }
  333. .icon-box {
  334. display: flex;
  335. align-items: center;
  336. justify-content: center;
  337. width: 32px;
  338. height: 32px;
  339. cursor: pointer;
  340. border: 4px solid #076aff;
  341. border-radius: 20px;
  342. // .svg-icon {
  343. // color: #fff;
  344. // }
  345. }
  346. }
  347. .audio-small {
  348. display: flex;
  349. flex-direction: column;
  350. column-gap: 10px;
  351. align-items: left;
  352. margin-left: 10px;
  353. cursor: pointer;
  354. .audio-name {
  355. padding: 8px;
  356. margin-bottom: 8px;
  357. font-size: 14px;
  358. text-align: center;
  359. background-color: #eee;
  360. border-radius: 4px;
  361. }
  362. }
  363. .audio-list {
  364. display: flex;
  365. align-items: center;
  366. justify-content: space-between;
  367. padding: 8px 24px 8px 16px;
  368. &.active {
  369. //background-color: #ebb572;
  370. background-color: var(--assist-color, #f4f9ff);
  371. }
  372. .active-mark {
  373. width: 20px;
  374. height: 28px;
  375. }
  376. .audio-name {
  377. flex: 1;
  378. }
  379. }
  380. .slider-area {
  381. display: flex;
  382. column-gap: 16px;
  383. align-items: center;
  384. justify-content: space-between;
  385. font-size: 14px;
  386. .audio-slider {
  387. width: 90%;
  388. :deep .el-slider__runway {
  389. height: 4px;
  390. }
  391. :deep .el-slider__button {
  392. width: 4px;
  393. height: 4px;
  394. margin-top: -1px;
  395. border: none;
  396. }
  397. :deep .el-slider__bar {
  398. height: 4px;
  399. background-color: var(--slider-color, #076aff);
  400. }
  401. }
  402. }
  403. }
  404. .sound-icon--playing {
  405. animation: soundWave 1s infinite;
  406. }
  407. @keyframes soundWave {
  408. 0%,
  409. 100% {
  410. opacity: 1;
  411. transform: scale(1);
  412. }
  413. 50% {
  414. opacity: 0.5;
  415. transform: scale(1.2);
  416. }
  417. }
  418. </style>