You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

632 lines
16 KiB

  1. <template>
  2. <view class="imt-audio" :class="[`${theme}`]" v-if="audiolist.length > 0">
  3. <template v-if="theme == 'theme4'">
  4. <view class="imt-audios">
  5. <view class="audio-wrapper">
  6. <view class="audio-flex">
  7. <text>
  8. {{renderData('current')}}
  9. </text>
  10. <slider class="audio-slider" :activeColor="themeColor" block-size="16"
  11. :value="renderData('current_value')" :max="renderData('duration_value')" @change="change"
  12. :disabled="!renderIsPlay"></slider>
  13. <text v-if="!duration">
  14. {{renderData('duration')}}
  15. </text>
  16. <text v-else>{{ duration }}</text>
  17. </view>
  18. </view>
  19. </view>
  20. <view class="forward">
  21. <view style="flex-grow: 1;display: flex;align-items: center;">
  22. <view class="forward-item">
  23. <!-- 块退15s -->
  24. <image :src="require('./static/prev.png')" class="prevbtn" @click="stepPlay(-15)"
  25. mode="widthFix">
  26. </image>
  27. </view>
  28. <view class="forward-item top audio-control-wrapper">
  29. <image :src="require('./static/loading.png')" v-if="loading" class="play loading"></image>
  30. <template v-else>
  31. <image :src="require('./static/pausebtn.png')" alt="" @click="operate" class="play"
  32. v-if="renderData('paused')"></image>
  33. <image :src="require('./static/playbtn.png')" alt="" @click="operate" class="play" v-else>
  34. </image>
  35. </template>
  36. </view>
  37. <view class="forward-item" style="margin-left: 0;">
  38. <!-- 快进15s -->
  39. <image :src="require('./static/next.png')" class="nextbtn" @click="stepPlay(15)"
  40. mode="widthFix">
  41. </image>
  42. </view>
  43. <view class="forward-item" style="margin-left: 0;">
  44. <block v-for="(data, index) in playbackRateArr" :key="index">
  45. <text class="playbackRate" @tap="checkPlaybackRate(data.name, index)"
  46. :class="{act: playbackRateIndex == index}">x{{ data.name }}</text>
  47. </block>
  48. </view>
  49. </view>
  50. <view class="forward-item" style="flex-shrink: 0;">
  51. <image @tap="showMenu" :src="require('./static/quanping.png')" mode=""></image>
  52. </view>
  53. </view>
  54. </template>
  55. <template v-if="theme == 'theme3'">
  56. <slider class="audio-slider" :activeColor="themeColor" block-size="0" :value="renderData('current_value')"
  57. :max="renderData('duration_value')" @change="change" :disabled="!renderIsPlay"></slider>
  58. <view class="top">
  59. <view class="audio-control-wrapper">
  60. <image :src="renderData('coverImgUrl')" mode="aspectFill" class="cover"
  61. :class="{ on: !renderData('paused') }"></image>
  62. <image :src="require('./static/loading.png')" v-if="loading" class="play loading"></image>
  63. <template v-else>
  64. <image :src="require('./static/pausebtn.png')" alt="" @click="operate" class="play"
  65. v-if="renderData('paused')"></image>
  66. <image :src="require('./static/playbtn.png')" alt="" @click="operate" class="play" v-else>
  67. </image>
  68. </template>
  69. </view>
  70. </view>
  71. <view class="audio-wrapper">
  72. <view class="titlebox">
  73. <view class="title">{{ renderData('title') }}</view>
  74. <view class="singer">{{ renderData('singer') }}</view>
  75. </view>
  76. <view class="slidebox">
  77. <view>{{ renderData('current') }}/ {{ renderData('duration') }}</view>
  78. <view>
  79. <text @click="changeplay(-1)">上一首</text>
  80. <text @click="changeplay(1)">下一首</text>
  81. </view>
  82. </view>
  83. </view>
  84. </template>
  85. <template v-if="theme == 'theme2'">
  86. <view class="top">
  87. <view class="audio-control-wrapper" :style="{backgroundImage: `url(${renderData('coverImgUrl')})`}">
  88. <image :src="require('./static/loading.png')" v-if="loading" class="play loading"></image>
  89. <template v-else>
  90. <image :src="require('./static/pausebtn.png')" alt="" @click="operate" class="play"
  91. v-if="renderData('paused')"></image>
  92. <image :src="require('./static/playbtn.png')" alt="" @click="operate" class="play" v-else>
  93. </image>
  94. </template>
  95. </view>
  96. <view>
  97. <view class="title">
  98. <text>{{ renderData('title') }}</text>
  99. <view class="audio-number">{{ renderData('current') }}/{{ renderData('duration') }}</view>
  100. </view>
  101. <view class="singer">{{ renderData('singer') }}</view>
  102. </view>
  103. </view>
  104. </template>
  105. <template v-if="theme == 'theme1'">
  106. <view class="top">
  107. <view class="audio-control-wrapper">
  108. <image :src="renderData('coverImgUrl')" mode="aspectFill" class="cover"
  109. :class="{ on: !renderData('paused') }"></image>
  110. </view>
  111. <view>
  112. <view class="title">{{ renderData('title') }}</view>
  113. <view class="singer">{{ renderData('singer') }}</view>
  114. </view>
  115. </view>
  116. <view class="audio-wrapper">
  117. <view class="audio-number">{{ renderData('current') }}</view>
  118. <slider class="audio-slider" :activeColor="themeColor" block-size="16"
  119. :value="renderData('current_value')" :max="renderData('duration_value')" @change="change"
  120. :disabled="!renderIsPlay"></slider>
  121. <view class="audio-number">{{ renderData('duration') }}</view>
  122. </view>
  123. <view class="audio-button-box">
  124. <!-- 块退15s -->
  125. <image :src="require('./static/prev.png')" class="prevbtn" @click="stepPlay(-15)" mode="widthFix">
  126. </image>
  127. <!-- 上一首 -->
  128. <image :src="require('./static/go.png')" class="prevplay" @click="changeplay(-1)" mode="widthFix">
  129. </image>
  130. <div class="playbox">
  131. <image :src="require('./static/loading2.png')" v-if="loading" class="play loading"></image>
  132. <template v-else>
  133. <!-- 播放 -->
  134. <image :src="require('./static/playbtn2.png')" alt="" @click="operate" class="play"
  135. v-if="renderData('paused')"></image>
  136. <!-- 暂停 -->
  137. <image :src="require('./static/pausebtn2.png')" alt="" @click="operate" class="pause" v-else>
  138. </image>
  139. </template>
  140. </div>
  141. <!-- 下一首 -->
  142. <image :src="require('./static/go.png')" class="nextplay" @click="changeplay(1)" mode="widthFix">
  143. </image>
  144. <!-- 快进15s -->
  145. <image :src="require('./static/next.png')" class="nextbtn" @click="stepPlay(15)" mode="widthFix">
  146. </image>
  147. </view>
  148. </template>
  149. <u-popup v-model="screenShow" mode="center" borderRadius="16">
  150. <view class="middles">
  151. <view class="audio-flex">
  152. <view class="scroll-box" @touchstart="touchS" @touchend="touchE">
  153. <view class="slider-container">
  154. <view class="place-box" :style="{height: `${boxHeight}%`}"></view>
  155. <view class="slider-btn"></view>
  156. </view>
  157. </view>
  158. </view>
  159. <view class="forwards">
  160. <view class="imt-audioss">
  161. <view class="forward-item texts">
  162. <text>
  163. {{renderData('current') || '00:00:00'}}
  164. </text>
  165. </view>
  166. <view class="forward-item">
  167. <image :src="require('./static/prev.png')" class="prevbtn" @click="stepPlay(-15)"
  168. mode="widthFix">
  169. </image>
  170. </view>
  171. <view class="forward-item top">
  172. <view class="audio-control-wrapper">
  173. <image :src="require('./static/loading.png')" v-if="loading" class="play loading">
  174. </image>
  175. <template v-else>
  176. <image :src="require('./static/pausebtn.png')" alt="" @click="operate" class="play"
  177. v-if="renderData('paused')"></image>
  178. <image :src="require('./static/playbtn.png')" alt="" @click="operate" class="play"
  179. v-else>
  180. </image>
  181. </template>
  182. </view>
  183. </view>
  184. <view class="forward-item">
  185. <image :src="require('./static/next.png')" class="nextbtn" @click="stepPlay(15)"
  186. mode="widthFix">
  187. </image>
  188. </view>
  189. <view class="forward-item">
  190. <text v-if="!duration">
  191. {{renderData('duration') || '00:00:00'}}
  192. </text>
  193. <text v-else>{{ duration || '00:00:00' }}</text>
  194. </view>
  195. <view class="forward-item" style="margin-left: 0;">
  196. <block v-for="(data, index) in playbackRateArr" :key="index">
  197. <text class="playbackRate" @tap="checkPlaybackRate(data.name, index)"
  198. :class="{act: playbackRateIndex == index}">x{{ data.name }}</text>
  199. </block>
  200. </view>
  201. </view>
  202. </view>
  203. <view class="forward-item ms-close" style="flex-shrink: 0;">
  204. <image @tap="screenShow = false" :src="require('./static/quxiaoquanping.png')" mode="">
  205. </image>
  206. </view>
  207. </view>
  208. </u-popup>
  209. </view>
  210. </template>
  211. <script>
  212. import ZAudio from './index.js'
  213. export default {
  214. props: {
  215. theme: {
  216. type: String, // 主题 'theme1' or 'theme2'
  217. default: 'theme1'
  218. },
  219. themeColor: {
  220. type: String,
  221. default: '#2671E2'
  222. },
  223. duration: {
  224. type: String,
  225. default: ""
  226. }
  227. },
  228. data() {
  229. return {
  230. playinfo: this.$zaudio.playinfo,
  231. audiolist: this.$zaudio.audiolist,
  232. paused: this.$zaudio.paused,
  233. renderIsPlay: this.$zaudio.renderIsPlay,
  234. audio: this.$zaudio.renderinfo,
  235. loading: this.$zaudio.loading,
  236. action: Symbol('zaudio'),
  237. screenShow: false, // 记得改回来
  238. pageY: 0, // 滑块按钮位置
  239. boxHeight: 0, // 染色盒子背景色
  240. scrollBoxH: 1, // 容器高度
  241. nowInfo: {}, // 当前播放info
  242. scrollObjS: {}, // 滚动对象
  243. playbackRateArr: [{
  244. name: 1,
  245. },
  246. // {
  247. // name: 1.25,
  248. // },
  249. {
  250. name: 1.5,
  251. },
  252. // {
  253. // name: 1.75,
  254. // },
  255. {
  256. name: 2,
  257. },
  258. ], // 倍速播放
  259. playbackRateIndex: 0, // 倍速播放下标
  260. };
  261. },
  262. computed: {
  263. renderData() {
  264. return name => {
  265. if (!this.renderIsPlay) {
  266. if (name == 'paused') {
  267. return true;
  268. }
  269. return this.audio[name];
  270. } else {
  271. if (name == 'paused') {
  272. return this.paused;
  273. }
  274. return this.playinfo[name];
  275. }
  276. };
  277. },
  278. },
  279. mounted() {
  280. this.$nextTick(() => {
  281. let action = this.action;
  282. this.$zaudio.syncStateOn(action, ({
  283. audiolist,
  284. paused,
  285. playinfo,
  286. renderIsPlay,
  287. renderinfo,
  288. loading,
  289. }) => {
  290. this.audiolist = audiolist;
  291. this.paused = paused;
  292. this.playinfo = playinfo;
  293. this.renderIsPlay = renderIsPlay;
  294. this.audio = renderinfo;
  295. this.loading = loading;
  296. });
  297. this.$zaudio.syncRender()
  298. this.playbackRateIndex = this.playbackRateArr.findIndex(item => {
  299. return item.name == (uni.getBackgroundAudioManager().playbackRate || 1)
  300. })
  301. });
  302. },
  303. methods: {
  304. // 切换倍速播放
  305. checkPlaybackRate(playbackRate, index) {
  306. this.playbackRateIndex = index
  307. this.$zaudio.stop()
  308. uni.getBackgroundAudioManager().playbackRate = Number(playbackRate)
  309. this.$emit('checkPlaybackRate')
  310. },
  311. selectorQuery() {
  312. const query = uni.createSelectorQuery().in(this);
  313. query.select('.slider-container').boundingClientRect(data => {
  314. console.log(data.height, 'scrollBoxH')
  315. this.scrollBoxH = Math.floor(data.height)
  316. }).exec();
  317. },
  318. touchS(e) {
  319. console.log(e, 'touchS')
  320. this.scrollObjS = Math.floor(e.changedTouches[0].clientY)
  321. },
  322. touchE(e) {
  323. console.log(e, 'touchE')
  324. let endObj = Math.floor(e.changedTouches[0].clientY)
  325. let percent = Math.floor(((endObj - this.scrollObjS) / this.scrollBoxH) * 100)
  326. if ((this.boxHeight + percent) > 100) {
  327. this.boxHeight = 100
  328. } else if ((this.boxHeight + percent) < 0) {
  329. this.boxHeight = 0
  330. } else {
  331. this.boxHeight += percent
  332. }
  333. let seek = Math.trunc((this.boxHeight / 100) * this.nowInfo.duration_value)
  334. console.log(seek, 'seek')
  335. this.$zaudio.seek(seek)
  336. },
  337. showMenu() {
  338. this.screenShow = true
  339. this.$nextTick(() => {
  340. this.selectorQuery()
  341. console.log(this.renderData('current_value'), '123')
  342. console.log(this.renderData('duration_value'), '321')
  343. this.$zaudio.on('playing', '123', (obj) => {
  344. this.nowInfo = obj
  345. this.boxHeight = Math.trunc((Math.trunc(obj.current_value) / Math.trunc(obj
  346. .duration_value)) * 100)
  347. })
  348. })
  349. },
  350. // 转换时间
  351. formatSeconds(seconds) {
  352. var result = typeof seconds === "string" ? parseFloat(seconds) : seconds;
  353. if (isNaN(result)) return "";
  354. let h = Math.floor(result / 3600) < 10 ?
  355. "0" + Math.floor(result / 3600) :
  356. Math.floor(result / 3600);
  357. let m = Math.floor((result / 60) % 60) < 10 ?
  358. "0" + Math.floor((result / 60) % 60) :
  359. Math.floor((result / 60) % 60) + h * 60;
  360. let s = Math.floor(result % 60) < 10 ?
  361. "0" + Math.floor(result % 60) :
  362. Math.floor(result % 60);
  363. return `${h}:${m}:${s}`;
  364. },
  365. //播放or暂停
  366. operate() {
  367. this.$zaudio.operate();
  368. },
  369. //进度拖到
  370. change(event) {
  371. if (this.renderIsPlay) {
  372. this.$zaudio.seek(event.detail.value);
  373. }
  374. },
  375. //快进
  376. stepPlay(value) {
  377. if (this.renderData('current') == '00:00:00') return
  378. console.log(this.renderData('current'))
  379. this.$zaudio.stepPlay(value);
  380. },
  381. //切歌
  382. changeplay(count) {
  383. this.$zaudio.changeplay(count);
  384. }
  385. },
  386. beforeDestroy() {
  387. this.$zaudio.playinfo.duration = '00:00:00'
  388. this.$zaudio.playinfo.duration_value = '00:00:00'
  389. //组件卸载时卸载业务逻辑
  390. let action = this.action;
  391. this.$zaudio.syncStateOff(action)
  392. this.$zaudio.stop()
  393. this.$zaudio.off('playing', '123')
  394. }
  395. };
  396. </script>
  397. <style scoped lang="scss">
  398. @import './index.scss';
  399. // #ifdef MP-WEIXIN
  400. .theme3 .audio-slider {
  401. margin-top: -8px !important;
  402. }
  403. // #endif
  404. @mixin textoverflow() {
  405. display: -webkit-box;
  406. overflow: hidden;
  407. text-overflow: ellipsis;
  408. -webkit-box-orient: vertical;
  409. -webkit-line-clamp: 1;
  410. }
  411. @keyframes rowup {
  412. 0% {
  413. -webkit-transform: translate(-50%, -50%) rotate(0deg);
  414. transform-origin: center center;
  415. }
  416. 100% {
  417. -webkit-transform: translate(-50%, -50%) rotate(360deg);
  418. transform-origin: center center;
  419. }
  420. }
  421. .imt-audios {
  422. position: relative;
  423. width: 100%;
  424. height: 81rpx;
  425. display: flex;
  426. box-sizing: border-box;
  427. background: #fff;
  428. .top {
  429. position: relative;
  430. width: 100rpx;
  431. }
  432. .audio-wrapper {
  433. position: relative;
  434. padding: 0 20rpx;
  435. display: flex;
  436. flex: 1;
  437. color: #fff;
  438. .popup {
  439. position: absolute;
  440. right: 32rpx;
  441. top: -122rpx;
  442. z-index: 100;
  443. width: 136rpx;
  444. height: 122rpx;
  445. display: flex;
  446. align-items: center;
  447. justify-content: center;
  448. flex-direction: column;
  449. background: #fff;
  450. border: 1rpx solid #E0E0E0;
  451. transition: all 0.25s linear;
  452. opacity: 0;
  453. image {
  454. width: 32rpx;
  455. height: 32rpx;
  456. }
  457. text {
  458. margin-top: 10rpx;
  459. color: #333;
  460. font-size: 24rpx;
  461. }
  462. .act-test {
  463. color: #2671E2;
  464. }
  465. }
  466. .close {
  467. opacity: 1;
  468. }
  469. }
  470. .slidebox {
  471. flex-shrink: 0;
  472. display: flex;
  473. align-items: center;
  474. .slide-img {
  475. width: 32rpx;
  476. height: 8rpx;
  477. }
  478. }
  479. /deep/ .uni-slider-tap-area {
  480. padding: 0;
  481. }
  482. /deep/ .uni-slider-wrapper {
  483. min-height: 0;
  484. }
  485. /deep/ .uni-slider-handle-wrapper {
  486. height: 6px;
  487. }
  488. .audio-slider {
  489. flex-grow: 1;
  490. }
  491. .play {
  492. width: 44rpx;
  493. height: 44rpx;
  494. z-index: 99;
  495. border-radius: 50%;
  496. position: absolute;
  497. top: 50%;
  498. left: 50%;
  499. transform: translate(-50%, -50%);
  500. &.loading {
  501. width: 48rpx;
  502. height: 48rpx;
  503. animation: rotating_theme3 2s linear infinite;
  504. }
  505. }
  506. }
  507. .forward {
  508. padding: 0 20rpx 20rpx;
  509. width: 100%;
  510. display: flex;
  511. align-items: flex-end;
  512. .forward-item {
  513. margin-right: 20rpx;
  514. display: flex;
  515. align-items: center;
  516. .playbackRate {
  517. margin: 0 10rpx;
  518. // padding: 0 10rpx;
  519. width: 55rpx;
  520. height: 55rpx;
  521. display: flex;
  522. align-items: center;
  523. justify-content: center;
  524. font-size: 24rpx;
  525. &.act {
  526. color: #2671E2;
  527. border: 5rpx solid #2671E2;
  528. border-radius: 50%;
  529. }
  530. }
  531. image {
  532. width: 50rpx;
  533. height: 50rpx;
  534. }
  535. }
  536. }
  537. .audio-flex {
  538. padding: 0 16rpx 0 0;
  539. flex-grow: 1;
  540. display: flex;
  541. align-items: center;
  542. text {
  543. color: #70798D;
  544. }
  545. }
  546. @keyframes rotating {
  547. 0% {
  548. transform: rotateZ(0deg)
  549. }
  550. 100% {
  551. transform: rotateZ(360deg)
  552. }
  553. }
  554. @keyframes rotating_theme3 {
  555. 0% {
  556. transform: translate(-50%, -50%) rotateZ(0deg)
  557. }
  558. 100% {
  559. transform: translate(-50%, -50%) rotateZ(360deg)
  560. }
  561. }
  562. </style>