index.vue 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. <script setup>
  2. import { ElMessage } from 'element-plus';
  3. const emit = defineEmits(['loadDone']);
  4. const props = defineProps({
  5. audioUrl: {
  6. type: String,
  7. default: ''
  8. },
  9. id: {
  10. type: Number,
  11. default: 0
  12. }
  13. })
  14. const speedList = [
  15. { label: '3 x', value: 3 },
  16. { label: '2 x', value: 2 },
  17. { label: '1.5 x', value: 1.5 },
  18. { label: '1(正常)', value: 1 },
  19. { label: '0.5 x', value: 0.5 },
  20. ]
  21. const audioUrl = import.meta.env.VITE_AUDIO_BASE_URL;
  22. const isDisabled = ref(true);
  23. const audioRef = ref(null);
  24. const audioIsPlay = ref(true);
  25. const audioStart = ref("00:00");
  26. const durationTime = ref("00:00");
  27. const duration = ref(0);
  28. const currentProgress = ref(0);
  29. watch(() => props.audioUrl, () => {
  30. audioIsPlay.value = true;
  31. currentProgress.value = 0;
  32. audioStart.value = "00:00";
  33. durationTime.value = "00:00";
  34. audioRef.value.removeEventListener('loadedmetadata', loadEndEvent);
  35. audioRef.value.removeEventListener('ended', endAudio);
  36. calculateDuration();
  37. })
  38. // 控制是否播放
  39. const playAudio = () => {
  40. if ( isDisabled.value ) {
  41. return ElMessage({
  42. message: '提示: 音频播放失败',
  43. type: 'warning',
  44. });
  45. }
  46. if (audioRef.value.paused) {
  47. audioRef.value.play();
  48. audioIsPlay.value = false;
  49. } else {
  50. audioRef.value.pause();
  51. audioIsPlay.value = true;
  52. }
  53. }
  54. // 根据当前播放时间,实时更新进度条
  55. const updateProgress = (e) => {
  56. var value = e.target.currentTime / e.target.duration;
  57. if (audioRef.value?.play) {
  58. currentProgress.value = value * 100;
  59. audioStart.value = transTime(audioRef.value.currentTime);
  60. }
  61. }
  62. const handleProgressChange = (val) => {
  63. if (!val) {
  64. return;
  65. }
  66. let currentTime = duration.value * (val / 100);
  67. // 更新音频的当前播放时间
  68. audioRef.value.currentTime = currentTime;
  69. }
  70. // 音频加载完成
  71. const loadEndEvent = () => {
  72. isDisabled.value = false;
  73. }
  74. // 音频播放完成
  75. const endAudio = () => {
  76. audioIsPlay.value = true;
  77. currentProgress.value = 0;
  78. }
  79. // 音频播放时间换算
  80. const transTime = (duration) => {
  81. const minutes = Math.floor(duration / 60);
  82. const seconds = Math.floor(duration % 60);
  83. const formattedMinutes = String(minutes).padStart(2, "0");
  84. const formattedSeconds = String(seconds).padStart(2, "0");
  85. return `${formattedMinutes}:${formattedSeconds}`;
  86. }
  87. const calculateDuration = () => {
  88. const myVid = audioRef.value;
  89. myVid.loop = false;
  90. if (!props.audioUrl) {
  91. isDisabled.value = true;
  92. myVid.src = '';
  93. return
  94. }
  95. myVid.src = audioUrl + props.audioUrl
  96. myVid.addEventListener( 'loadedmetadata', loadEndEvent, false );
  97. myVid.addEventListener( "ended", endAudio, false );
  98. if (myVid != null) {
  99. myVid.oncanplay = () => {
  100. duration.value = myVid.duration;
  101. durationTime.value = transTime(myVid.duration);
  102. emit('loadDone', {durationTime: durationTime.value, id: props.id});
  103. };
  104. myVid.volume = 1;
  105. }
  106. }
  107. const handleCommand = (speed) => {
  108. audioRef.value && (audioRef.value.playbackRate = speed);
  109. }
  110. onMounted(() => {
  111. calculateDuration()
  112. })
  113. defineExpose({
  114. durationTime
  115. })
  116. </script>
  117. <template>
  118. <div class="custom-audio-payer">
  119. <audio @timeupdate="updateProgress" controls controlslist= "noplaybackrate nodownload" ref="audioRef" class="audio hidden">
  120. <source src="" type="audio/mpeg" />
  121. </audio>
  122. <div class="audio-player">
  123. <div class="player-btn" @click="playAudio">
  124. <el-icon class="is-loading" v-if="isDisabled">
  125. <Loading />
  126. </el-icon>
  127. <div :class="['control-btn', audioIsPlay ? 'play' : 'pause']" v-show="!isDisabled"></div>
  128. </div>
  129. <div class="audio-plane">
  130. <span class="timestamp">{{ audioStart }}</span>
  131. <el-slider
  132. v-model="currentProgress"
  133. @input="handleProgressChange"
  134. style="width: 124px;"
  135. :show-tooltip="false"
  136. :disabled="isDisabled"
  137. />
  138. <span class="timestamp">{{ durationTime }}</span>
  139. <el-dropdown placement="top" @command="handleCommand" popper-class="custom-popper-speed">
  140. <el-icon><MoreFilled /></el-icon>
  141. <template #dropdown>
  142. <el-dropdown-menu>
  143. <el-dropdown-item :command="item.value" v-for="item in speedList" :key="item.key">{{ item.label }}</el-dropdown-item>
  144. </el-dropdown-menu>
  145. </template>
  146. </el-dropdown>
  147. </div>
  148. </div>
  149. </div>
  150. </template>
  151. <style lang="scss" scoped>
  152. .custom-audio-payer {
  153. width: 300px;
  154. height: 36px;
  155. flex-shrink: 0;
  156. border-radius: 18px;
  157. border: 0.5px solid #E5E6EB;
  158. background: #FFF;
  159. }
  160. .audio-player {
  161. display: flex;
  162. align-items: center;
  163. height: 100%;
  164. padding: 6px 8px;
  165. line-height: 0;
  166. .player-btn {
  167. display: flex;
  168. align-items: center;
  169. justify-content: center;
  170. flex-shrink: 0;
  171. width: 24px;
  172. height: 24px;
  173. border-radius: 100%;
  174. background: #E5E6EB;
  175. font-size: 14px;
  176. line-height: 0;
  177. color: #555;
  178. .control-btn {
  179. width: 24px;
  180. height: 24px;
  181. &.play {
  182. background: url('@/assets/images/workbench/icon-audio-play.svg') center center no-repeat;
  183. background-size: 100% 100%;
  184. }
  185. &.pause {
  186. background: url('@/assets/images/workbench/icon-audio-pause.svg') center center no-repeat;
  187. background-size: 100% 100%;
  188. }
  189. }
  190. }
  191. .audio-plane {
  192. display: flex;
  193. align-items: center;
  194. justify-content: space-around;
  195. width: 100%;
  196. .timestamp {
  197. display: block;
  198. line-height: 22px;
  199. font-size: 12px;
  200. color: #919397;
  201. }
  202. :deep(.el-slider__button) {
  203. width: 12px;
  204. height: 12px;
  205. border-color: #555;
  206. background: #555;
  207. }
  208. :deep(.el-slider__runway) {
  209. height: 6px;
  210. }
  211. :deep(.el-slider__bar) {
  212. height: 6px;
  213. background: #898787;
  214. }
  215. }
  216. }
  217. </style>
  218. <style lang="scss">
  219. .custom-popper-speed {
  220. width: 100px;
  221. }
  222. </style>