AudioLine.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. <template>
  2. <div class="Audio">
  3. <div class="audioLine" v-if="!hideSlider">
  4. <div
  5. class="play"
  6. :class="audio.playing ? 'playBtn' : 'pauseBtn'"
  7. @click="PlayAudio"
  8. />
  9. <template v-if="!isRepeat">
  10. <el-slider
  11. v-model="playValue"
  12. :style="{ width: sliderWidth + 'px', height: '2px' }"
  13. :format-tooltip="formatProcessToolTip"
  14. @change="changeCurrentTime"
  15. />
  16. <span
  17. ><template v-if="audio.playing">-</template
  18. >{{
  19. audio.maxTime
  20. ? realFormatSecond(audio.maxTime - audio.currentTime)
  21. : ""
  22. }}</span
  23. >
  24. </template>
  25. <audio
  26. ref="audio"
  27. :src="mp3"
  28. @loadedmetadata="onLoadedmetadata"
  29. @timeupdate="onTimeupdate"
  30. />
  31. </div>
  32. <div class="audioLine2" v-else>
  33. <div
  34. class="play-icon"
  35. :class="audio.playing ? 'playBtn-icon' : 'pauseBtn-icon'"
  36. @click="PlayAudio"
  37. />
  38. </div>
  39. <audio
  40. :ref="audioId"
  41. :src="mp3"
  42. @loadedmetadata="onLoadedmetadata"
  43. @timeupdate="onTimeupdate"
  44. :id="audioId"
  45. />
  46. </div>
  47. </template>
  48. <script>
  49. // 这里可以导入其它文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
  50. // 例如:import 《组件名称》from ‘《组件路径》';
  51. export default {
  52. // import引入的组件需要注入到对象中才能使用
  53. components: {},
  54. props: [
  55. "mp3",
  56. "getCurTime",
  57. "stopAudio",
  58. "width",
  59. "isRepeat",
  60. "themeColor",
  61. "hideSlider",
  62. "ed",
  63. "bg",
  64. "audioId",
  65. ],
  66. data() {
  67. // 这里存放数据
  68. return {
  69. playValue: 0,
  70. audio: {
  71. // 该字段是音频是否处于播放状态的属性
  72. playing: false,
  73. // 音频当前播放时长
  74. currentTime: 0,
  75. // 音频最大播放时长
  76. maxTime: 0,
  77. isPlaying: false,
  78. },
  79. audioAllTime: null, // 展示总时间
  80. duioCurrentTime: null, // 剩余时间
  81. };
  82. },
  83. // 计算属性 类似于data概念
  84. computed: {
  85. sliderWidth() {
  86. let width = 0;
  87. if (this.width) {
  88. width = this.width;
  89. } else {
  90. width = 662;
  91. }
  92. return width;
  93. },
  94. },
  95. // 监控data中数据变化
  96. watch: {
  97. stopAudio: {
  98. handler(val, oldVal) {
  99. const _this = this;
  100. if (val) {
  101. _this.$refs[_this.audioId].pause();
  102. _this.audio.playing = false;
  103. }
  104. },
  105. // 深度观察监听
  106. deep: true,
  107. },
  108. "audio.playing": {
  109. handler(val) {
  110. this.$emit("playChange", val);
  111. if (val) this.$emit("handleChangeStopAudio");
  112. },
  113. },
  114. },
  115. // 生命周期 - 创建完成(可以访问当前this实例)
  116. created() {},
  117. // 生命周期 - 挂载完成(可以访问DOM元素)
  118. mounted() {
  119. let _this = this;
  120. let audioId = _this.audioId;
  121. _this.$refs[audioId].addEventListener("play", function () {
  122. _this.audio.playing = true;
  123. _this.audio.isPlaying = true;
  124. });
  125. _this.$refs[audioId].addEventListener("pause", function () {
  126. _this.audio.playing = false;
  127. });
  128. _this.$refs[audioId].addEventListener("ended", function () {
  129. _this.audio.playing = false;
  130. _this.audio.isPlaying = false;
  131. });
  132. this.$nextTick(() => {
  133. document
  134. .getElementsByClassName("el-slider__button-wrapper")[0]
  135. .addEventListener("mousedown", function () {
  136. _this.$refs[audioId].pause();
  137. _this.audio.playing = false;
  138. });
  139. });
  140. },
  141. // 生命周期-挂载之前
  142. beforeMount() {},
  143. // 生命周期-更新之后
  144. updated() {},
  145. // 如果页面有keep-alive缓存功能,这个函数会触发
  146. activated() {},
  147. // 方法集合
  148. methods: {
  149. PlayAudio() {
  150. let audioId = this.audioId;
  151. let audio = document.getElementsByTagName("audio");
  152. audio.forEach((item) => {
  153. if (item.src == this.mp3) {
  154. if (item.id !== audioId) {
  155. item.pause();
  156. }
  157. } else {
  158. item.pause();
  159. }
  160. });
  161. if (this.audio.playing) {
  162. this.$refs[audioId].pause();
  163. this.audio.playing = false;
  164. this.$emit("handleListenRead", false);
  165. } else {
  166. if (this.hideSlider) {
  167. this.$refs[audioId].play();
  168. this.onTimeupdateTime(this.bg / 1000);
  169. } else {
  170. this.$refs[audioId].play();
  171. }
  172. this.audio.playing = true;
  173. this.$emit("handleChangeStopAudio");
  174. this.$emit("handleListenRead", true);
  175. }
  176. },
  177. // 点击 拖拽播放音频
  178. changeCurrentTime(value) {
  179. let audioId = this.audioId;
  180. this.$refs[audioId].play();
  181. this.audio.playing = true;
  182. this.$refs[audioId].currentTime = parseInt(
  183. (value / 100) * this.audio.maxTime
  184. );
  185. },
  186. mousedown() {
  187. let audioId = this.audioId;
  188. this.$refs[audioId].pause();
  189. this.audio.playing = false;
  190. },
  191. // 进度条格式化toolTip
  192. formatProcessToolTip(index) {
  193. index = parseInt((this.audio.maxTime / 100) * index);
  194. return this.realFormatSecond(index);
  195. },
  196. // 音频加载完之后
  197. onLoadedmetadata(res) {
  198. this.audio.maxTime = parseInt(res.target.duration);
  199. this.audioAllTime = this.realFormatSecond(this.audio.maxTime);
  200. },
  201. // 当音频当前时间改变后,进度条也要改变
  202. onTimeupdate(res) {
  203. let audioId = this.audioId;
  204. this.audio.currentTime = res.target.currentTime;
  205. this.getCurTime(res.target.currentTime);
  206. this.playValue = (this.audio.currentTime / this.audio.maxTime) * 100;
  207. if (this.audio.currentTime * 1000 > this.ed) {
  208. this.$refs[audioId].pause();
  209. }
  210. },
  211. onTimeupdateTime(res, playFlag) {
  212. let audioId = this.audioId;
  213. this.$refs[audioId].currentTime = res;
  214. this.playValue = (res / this.audio.maxTime) * 100;
  215. if (playFlag) {
  216. let audio = document.getElementsByTagName("audio");
  217. audio.forEach((item) => {
  218. if (item.id !== audioId) {
  219. item.pause();
  220. }
  221. });
  222. this.$refs[audioId].play();
  223. }
  224. },
  225. // 将整数转换成 时:分:秒的格式
  226. realFormatSecond(value) {
  227. let theTime = parseInt(value); // 秒
  228. let theTime1 = 0; // 分
  229. let theTime2 = 0; // 小时
  230. if (theTime > 60) {
  231. theTime1 = parseInt(theTime / 60);
  232. theTime = parseInt(theTime % 60);
  233. if (theTime1 > 60) {
  234. theTime2 = parseInt(theTime1 / 60);
  235. theTime1 = parseInt(theTime1 % 60);
  236. }
  237. }
  238. let result = String(parseInt(theTime));
  239. if (result < 10) {
  240. result = "0" + result;
  241. }
  242. if (theTime1 > 0) {
  243. result = String(parseInt(theTime1)) + ":" + result;
  244. if (theTime1 < 10) {
  245. result = "0" + result;
  246. }
  247. } else {
  248. result = "00:" + result;
  249. }
  250. if (theTime2 > 0) {
  251. result = String(parseInt(theTime2)) + ":" + result;
  252. if (theTime2 < 10) {
  253. result = "0" + result;
  254. }
  255. } else {
  256. // result = "00:" + result;
  257. }
  258. return result;
  259. },
  260. },
  261. // 生命周期-创建之前
  262. beforeCreated() {},
  263. // 生命周期-更新之前
  264. beforUpdate() {},
  265. // 生命周期-销毁之前
  266. beforeDestory() {},
  267. // 生命周期-销毁完成
  268. destoryed() {},
  269. };
  270. </script>
  271. <style lang="scss" scoped>
  272. /* @import url(); 引入css类 */
  273. .Audio {
  274. width: 100%;
  275. .audioLine {
  276. display: flex;
  277. align-items: center;
  278. width: 100%;
  279. height: 40px;
  280. background: #ffffff;
  281. // border: 1px solid rgba(0, 0, 0, 0.1);
  282. // box-shadow: 0px 2px 6px rgba(0, 0, 0, 0.1);
  283. box-sizing: border-box;
  284. border-radius: 4px;
  285. .play {
  286. margin-right: 12px;
  287. margin-left: 8px;
  288. width: 16px;
  289. height: 16px;
  290. cursor: pointer;
  291. display: block;
  292. // &.playBtn {
  293. // background: url("../../../assets/pause.png") no-repeat left top;
  294. // background-size: 100% 100%;
  295. // }
  296. // &.pauseBtn {
  297. // background: url("../../../assets/play.png") no-repeat left top;
  298. // background-size: 100% 100%;
  299. // }
  300. }
  301. span {
  302. font-size: 16px;
  303. line-height: 19px;
  304. color: #000;
  305. margin-left: 8px;
  306. margin-right: 12px;
  307. min-width: 56px;
  308. text-align: right;
  309. }
  310. }
  311. > .audioLine2 {
  312. .play-icon {
  313. width: 16px;
  314. height: 16px;
  315. cursor: pointer;
  316. &.playBtn-icon {
  317. background: url("../../../assets/NPC/icon-voice-play-red.png") no-repeat
  318. left top;
  319. background-size: 100% 100%;
  320. }
  321. &.pauseBtn-icon {
  322. background: url("../../../assets/NPC/play-red.png") no-repeat left top;
  323. background-size: 100% 100%;
  324. }
  325. }
  326. }
  327. }
  328. .NPC-Big-Book-preview-green {
  329. .playBtn-icon {
  330. background: url("../../../assets/NPC/icon-voice-play-green.png") no-repeat
  331. left top;
  332. background-size: 100% 100%;
  333. }
  334. .pauseBtn-icon {
  335. background: url("../../../assets/NPC/play-green.png") no-repeat left top;
  336. background-size: 100% 100%;
  337. }
  338. }
  339. .NPC-Big-Book-preview-red {
  340. .playBtn-icon {
  341. background: url("../../../assets/NPC/icon-voice-play-red.png") no-repeat
  342. left top;
  343. background-size: 100% 100%;
  344. }
  345. .pauseBtn-icon {
  346. background: url("../../../assets/NPC/play-red.png") no-repeat left top;
  347. background-size: 100% 100%;
  348. }
  349. }
  350. .NPC-Big-Book-preview-brown {
  351. .playBtn-icon {
  352. background: url("../../../assets/NPC/icon-voice-play-brown.png") no-repeat
  353. left top;
  354. background-size: 100% 100%;
  355. }
  356. .pauseBtn-icon {
  357. background: url("../../../assets/NPC/play-brown.png") no-repeat left top;
  358. background-size: 100% 100%;
  359. }
  360. }
  361. </style>
  362. <style lang="scss">
  363. .Audio {
  364. .el-slider__button-wrapper {
  365. position: relative;
  366. z-index: 0;
  367. }
  368. .el-slider__button {
  369. width: 8px;
  370. height: 8px;
  371. top: 12px;
  372. position: absolute;
  373. }
  374. .el-slider__runway {
  375. margin: 0;
  376. padding: 0;
  377. background: #e5e5e5;
  378. border-radius: 0px;
  379. height: 2px;
  380. }
  381. .el-slider {
  382. position: relative;
  383. }
  384. .el-slider__bar {
  385. height: 2px;
  386. background: rgba(118, 99, 236, 1);
  387. }
  388. .el-slider__button {
  389. background: rgba(118, 99, 236, 1);
  390. border: none;
  391. }
  392. }
  393. .NPC-Book-Sty {
  394. .Audio {
  395. .el-slider__bar {
  396. height: 2px;
  397. background: #de4444;
  398. }
  399. .el-slider__button {
  400. background: #de4444;
  401. border: none;
  402. }
  403. }
  404. }
  405. .NPC-Big-Book-preview-green {
  406. .Audio {
  407. .el-slider__bar {
  408. background: #24b99e !important;
  409. }
  410. .el-slider__button {
  411. background: #24b99e !important;
  412. }
  413. }
  414. }
  415. .NPC-Big-Book-preview-brown {
  416. .Audio {
  417. .el-slider__bar {
  418. background: #bd8865 !important;
  419. }
  420. .el-slider__button {
  421. background: #bd8865 !important;
  422. }
  423. }
  424. }
  425. </style>