index.vue 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. <template>
  2. <!-- 显示进度条 、 播放时长、 兼容是否支持 、暂停、 开启-->
  3. <div class="nut-audio">
  4. <!-- 进度条 -->
  5. <div class="nut-audio__progress" v-if="type == 'progress'">
  6. <!-- 时间显示 -->
  7. <div class="nut-audio__time">{{ currentDuration }}</div>
  8. <div class="nut-audio__bar">
  9. <nut-range
  10. v-model="percent"
  11. hidden-range
  12. @change="progressChange"
  13. inactive-color="#cccccc"
  14. active-color="#fa2c19"
  15. >
  16. <template v-slot:button>
  17. <div class="nut-audio__button--custom"></div>
  18. </template>
  19. </nut-range>
  20. </div>
  21. <div class="nut-audio__time">{{ duration }}</div>
  22. </div>
  23. <!-- 自定义 -->
  24. <div class="nut-audio__icon" v-if="type == 'icon'">
  25. <div
  26. :class="['nut-audio__icon--box', playing ? 'nut-audio__icon--play' : 'nut-audio__icon--stop']"
  27. @click="changeStatus"
  28. >
  29. <Service v-if="playing" class="nut-icon-am-rotate nut-icon-am-infinite"></Service>
  30. <Service v-else></Service>
  31. </div>
  32. </div>
  33. <div v-if="type == 'none'" @click="changeStatus">
  34. <slot></slot>
  35. </div>
  36. <!-- 操作按钮 -->
  37. <template v-if="type != 'none'">
  38. <slot></slot>
  39. </template>
  40. <audio
  41. class="audioMain"
  42. :controls="type == 'controls'"
  43. ref="audioRef"
  44. :src="url"
  45. :preload="preload"
  46. :autoplay="autoplay"
  47. :loop="loop"
  48. @timeupdate="onTimeupdate"
  49. @canplay="onCanplay"
  50. @ended="audioEnd"
  51. :muted="hanMuted"
  52. >
  53. </audio>
  54. </div>
  55. </template>
  56. <script lang="ts">
  57. import { toRefs, ref, onMounted, reactive, watch, provide, Component } from 'vue';
  58. import { createComponent } from '@/packages/utils/create';
  59. import { Service } from '@nutui/icons-vue';
  60. import Range from '../range/index.vue';
  61. const { componentName, create } = createComponent('audio');
  62. export default create({
  63. props: {
  64. url: {
  65. type: String,
  66. default: ''
  67. },
  68. // 静音
  69. muted: {
  70. type: Boolean,
  71. default: false
  72. },
  73. // 自动播放
  74. autoplay: {
  75. type: Boolean,
  76. default: false
  77. },
  78. // 循环播放
  79. loop: {
  80. type: Boolean,
  81. default: false
  82. },
  83. // 是否预加载音频
  84. preload: {
  85. type: String,
  86. default: 'auto'
  87. },
  88. /* 总时长秒数 */
  89. second: {
  90. type: Number,
  91. default: 0
  92. },
  93. // 展示的形式 controls 控制面板 progress 进度条 icon 图标 none 自定义
  94. type: {
  95. type: String,
  96. default: 'progress'
  97. }
  98. },
  99. components: {
  100. Service,
  101. [Range.name]: Range as Component
  102. },
  103. emits: ['fastBack', 'play', 'forward', 'ended', 'changeProgress', 'mute', 'can-play'],
  104. setup(props, { emit, slots }) {
  105. const audioRef = ref(null);
  106. const audioData = reactive({
  107. currentTime: 0,
  108. currentDuration: '00:00:00',
  109. percent: 0,
  110. duration: '00:00:00',
  111. second: 0,
  112. hanMuted: props.muted,
  113. playing: props.autoplay,
  114. handPlaying: false
  115. });
  116. onMounted(() => {
  117. // 播放的兼容性
  118. var arr = ['webkitVisibilityState', 'visibilitychange'];
  119. try {
  120. for (let i = 0; i < arr.length; i++) {
  121. document.addEventListener(arr[i], () => {
  122. if (document.hidden) {
  123. // 页面被挂起
  124. // 这里要根据用户当前播放状态,做音频暂停操作
  125. (audioRef.value as any).pause();
  126. } else {
  127. // 页面呼出
  128. if (audioData.playing) {
  129. setTimeout(() => {
  130. // 这里要 根据页面挂起前音频的播放状态,做音频播放操作
  131. (audioRef.value as any).play();
  132. }, 200);
  133. }
  134. }
  135. });
  136. }
  137. } catch (e) {
  138. console.log((e as any).message);
  139. }
  140. });
  141. // audio canplay 事件触发时
  142. const onCanplay = (e: Event) => {
  143. const audioR = audioRef.value as any;
  144. // 自动播放
  145. if (props.autoplay) {
  146. if (audioR && audioR.paused) {
  147. audioR.play();
  148. }
  149. }
  150. // 获取当前音频播放时长
  151. audioData.second = audioR.duration;
  152. audioData.duration = formatSeconds(audioR.duration);
  153. emit('can-play', e);
  154. };
  155. //播放时间
  156. const onTimeupdate = (e: any) => {
  157. audioData.currentTime = parseInt(e.target.currentTime);
  158. };
  159. //后退
  160. const fastBack = () => {
  161. if (audioData.currentTime > 0) {
  162. audioData.currentTime--;
  163. }
  164. (audioRef.value as any).currentTime = audioData.currentTime;
  165. emit('fastBack', audioData.currentTime);
  166. };
  167. //改变播放状态
  168. const changeStatus = () => {
  169. const audioR = audioRef.value as any;
  170. if (audioData.playing) {
  171. audioR.pause();
  172. audioData.handPlaying = false;
  173. } else {
  174. audioR.play();
  175. audioData.handPlaying = true;
  176. }
  177. audioData.playing = !audioData.playing;
  178. emit('play', audioData.playing);
  179. };
  180. //快进
  181. const forward = () => {
  182. audioData.currentTime++;
  183. (audioRef.value as any).currentTime = audioData.currentTime;
  184. emit('forward', audioData.currentTime);
  185. };
  186. //处理
  187. const handle = (val: number | string) => {
  188. //毫秒数转为时分秒
  189. audioData.currentDuration = formatSeconds(val as string);
  190. audioData.percent = ((val as number) / audioData.second) * 100;
  191. };
  192. //播放结束 修改播放状态
  193. const audioEnd = () => {
  194. audioData.playing = false;
  195. emit('ended');
  196. };
  197. //点击进度条
  198. const progressChange = (val: number) => {
  199. const ar = audioRef.value as any;
  200. ar.currentTime = (audioData.second * val) / 100;
  201. emit('changeProgress', ar.currentTime);
  202. };
  203. // 静音
  204. const handleMute = () => {
  205. audioData.hanMuted = !audioData.hanMuted;
  206. emit('mute', audioData.hanMuted);
  207. };
  208. const formatSeconds = (value: string) => {
  209. if (!value) {
  210. return '00:00:00';
  211. }
  212. let time = parseInt(value);
  213. let hours = Math.floor(time / 3600);
  214. let minutes = Math.floor((time - hours * 3600) / 60);
  215. let seconds = time - hours * 3600 - minutes * 60;
  216. let result = '';
  217. result += ('0' + hours.toString()).slice(-2) + ':';
  218. result += ('0' + minutes.toString()).slice(-2) + ':';
  219. result += ('0' + seconds.toString()).slice(-2);
  220. return result;
  221. };
  222. watch(
  223. () => audioData.currentTime,
  224. (value) => {
  225. handle(value);
  226. }
  227. );
  228. provide('audioParent', {
  229. children: [],
  230. props,
  231. audioData,
  232. handleMute,
  233. forward,
  234. fastBack,
  235. changeStatus
  236. });
  237. return {
  238. ...toRefs(props),
  239. ...toRefs(audioData),
  240. audioRef,
  241. fastBack,
  242. forward,
  243. changeStatus,
  244. progressChange,
  245. audioEnd,
  246. onTimeupdate,
  247. handleMute,
  248. onCanplay,
  249. slots
  250. };
  251. }
  252. });
  253. </script>