270 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			Vue
		
	
	
	
			
		
		
	
	
			270 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			Vue
		
	
	
	
| <template>
 | |
| 	<view v-if="controls" @click="onClick" class="_contain">
 | |
|     <!-- 海报和按钮 -->
 | |
| 		<view class="_poster" :style="'background-image:url('+poster+')'">
 | |
| 			<view class="_button" @tap="_buttonTap">
 | |
| 				<view :class="playing?'_pause':'_play'" />
 | |
| 			</view>
 | |
| 		</view>
 | |
|     <!-- 曲名和作者 -->
 | |
| 		<view class="_title">
 | |
| 			<view class="_name">{{name||'未知音频'}}</view>
 | |
| 			<view class="_author">{{author||'未知作者'}}</view>
 | |
| 		</view>
 | |
|     <!-- 进度条 -->
 | |
|     <slider class="_slider" activeColor="#585959" block-size="12" handle-size="12" :disabled="error" :value="value" @changing="_seeking" @change="_seeked" />
 | |
|     <!--播放时间-->
 | |
| 		<view class="_time">{{time||'00:00'}}</view>
 | |
| 	</view>
 | |
| </template>
 | |
| 
 | |
| <script>
 | |
| /**
 | |
|  * @fileoverview audio 组件
 | |
|  */
 | |
| import context from './context'
 | |
| export default {
 | |
|   data () {
 | |
|     return {
 | |
|       error: false,
 | |
|       playing: false,
 | |
|       time: '00:00',
 | |
|       value: 0
 | |
|     }
 | |
|   },
 | |
|   props: {
 | |
|     aid: String,
 | |
|     name: String, // 音乐名
 | |
|     author: String, // 作者
 | |
|     poster: String, // 海报图片地址
 | |
|     autoplay: [Boolean, String], // 是否自动播放
 | |
|     controls: [Boolean, String], // 是否显示控件
 | |
|     loop: [Boolean, String], // 是否循环播放
 | |
|     src: String // 源地址
 | |
|   },
 | |
|   watch: {
 | |
|     src (src) {
 | |
|       this.setSrc(src)
 | |
|     }
 | |
|   },
 | |
|   mounted () {
 | |
|     this._ctx = uni.createInnerAudioContext()
 | |
|     this._ctx.onError((err) => {
 | |
|       this.error = true
 | |
|       this.$emit('error', err)
 | |
|     })
 | |
|     this._ctx.onTimeUpdate(() => {
 | |
|       const time = this._ctx.currentTime
 | |
|       const min = parseInt(time / 60)
 | |
|       const sec = Math.ceil(time % 60)
 | |
|       this.time = (min > 9 ? min : '0' + min) + ':' + (sec > 9 ? sec : '0' + sec)
 | |
|       if (!this.lastTime) {
 | |
|         this.value = time / this._ctx.duration * 100 // 不在拖动状态下
 | |
|       }
 | |
|     })
 | |
|     this._ctx.onEnded(() => {
 | |
|       if (!this.loop) {
 | |
|         this.playing = false
 | |
|       }
 | |
|     })
 | |
|     context.set(this.aid, this)
 | |
|     this.setSrc(this.src)
 | |
|   },
 | |
|   beforeDestroy () {
 | |
|     this._ctx.destroy()
 | |
|     context.remove(this.aid)
 | |
|   },
 | |
|   onPageShow () {
 | |
|     if (this.playing && this._ctx.paused) {
 | |
|       this._ctx.play()
 | |
|     }
 | |
|   },
 | |
|   methods: {
 | |
|     // 设置源
 | |
|     setSrc (src) {
 | |
|       this._ctx.autoplay = this.autoplay
 | |
|       this._ctx.loop = this.loop
 | |
|       this._ctx.src = src
 | |
|       if (this.autoplay && !this.playing) {
 | |
|         this.playing = true
 | |
|       }
 | |
|     },
 | |
|     // 播放
 | |
|     play () {
 | |
|       this._ctx.play()
 | |
|       this.playing = true
 | |
|       this.$emit('play', {
 | |
|         target: {
 | |
|           id: this.aid
 | |
|         }
 | |
|       })
 | |
|     },
 | |
|     // 暂停
 | |
|     pause () {
 | |
|       this._ctx.pause()
 | |
|       this.playing = false
 | |
|       this.$emit('pause')
 | |
|     },
 | |
|     // 设置播放速率
 | |
|     playbackRate (rate) {
 | |
|       this._ctx.playbackRate = rate
 | |
|     },
 | |
|     // 移动进度条
 | |
|     seek (sec) {
 | |
|       this._ctx.seek(sec)
 | |
|     },
 | |
|     // 内部方法
 | |
|     _buttonTap () {
 | |
|       if (this.playing) this.pause()
 | |
|       else this.play()
 | |
|     },
 | |
|     _seeking (e) {
 | |
|       // 避免过于频繁 setData
 | |
|       if (e.timeStamp - this.lastTime < 200) return
 | |
|       const time = Math.round(e.detail.value / 100 * this._ctx.duration)
 | |
|       const min = parseInt(time / 60)
 | |
|       const sec = time % 60
 | |
|       this.time = (min > 9 ? min : '0' + min) + ':' + (sec > 9 ? sec : '0' + sec)
 | |
|       this.lastTime = e.timeStamp
 | |
|     },
 | |
|     _seeked (e) {
 | |
|       this.seek(e.detail.value / 100 * this._ctx.duration)
 | |
|       this.lastTime = undefined
 | |
|     },
 | |
|     onClick(e) {
 | |
|       this.$emit('onClick', e)
 | |
|     }
 | |
|   }
 | |
| }
 | |
| </script>
 | |
| 
 | |
| <style>
 | |
| /* 顶层容器 */
 | |
| ._contain {
 | |
|   position: relative;
 | |
|   display: inline-flex;
 | |
|   width: 290px;
 | |
|   background-color: #fcfcfc;
 | |
|   border: 1px solid #e0e0e0;
 | |
|   border-radius: 2px;
 | |
| }
 | |
| 
 | |
| /* 播放、暂停按钮 */
 | |
| ._button {
 | |
|   display: flex;
 | |
|   align-items: center;
 | |
|   justify-content: center;
 | |
|   width: 20px;
 | |
|   height: 20px;
 | |
|   overflow: hidden;
 | |
|   background-color: rgb(0, 0, 0, 0.2);
 | |
|   border: 1px solid white;
 | |
|   border-radius: 50%;
 | |
|   opacity: 0.9;
 | |
| }
 | |
| 
 | |
| ._play {
 | |
|   margin-left: 2px;
 | |
|   border-top: 4px solid transparent;
 | |
|   border-bottom: 4px solid transparent;
 | |
|   border-left: 8px solid white;
 | |
| }
 | |
| 
 | |
| ._pause {
 | |
|   width: 8px;
 | |
|   height: 8px;
 | |
|   background-color: white;
 | |
| }
 | |
| 
 | |
| /* 海报 */
 | |
| ._poster {
 | |
|   display: flex;
 | |
|   align-items: center;
 | |
|   justify-content: center;
 | |
|   width: 70px;
 | |
|   height: 70px;
 | |
|   background-color: #e6e6e6;
 | |
|   background-size: contain;
 | |
| }
 | |
| 
 | |
| /* 标题栏 */
 | |
| ._title {
 | |
|   flex: 1;
 | |
|   margin: 4px 0 0 14px;
 | |
|   text-align: left;
 | |
| }
 | |
| 
 | |
| ._author {
 | |
|   width: 45px;
 | |
|   font-size: 12px;
 | |
|   color: #888;
 | |
| }
 | |
| 
 | |
| ._name {
 | |
|   width: 140px;
 | |
|   font-size: 15px;
 | |
|   line-height: 39px;
 | |
| }
 | |
| 
 | |
| ._author,
 | |
| ._name {
 | |
|   overflow: hidden;
 | |
|   text-overflow: ellipsis;
 | |
|   white-space: nowrap;
 | |
| }
 | |
| 
 | |
| /* 进度条 */
 | |
| ._slider {
 | |
|   position: absolute;
 | |
|   right: 16px;
 | |
|   bottom: 8px;
 | |
|   width: 140px;
 | |
|   margin: 0;
 | |
| }
 | |
| 
 | |
| /* 播放时间 */
 | |
| ._time {
 | |
|   margin: 7px 14px 0 0;
 | |
|   font-size: 12px;
 | |
|   color: #888;
 | |
| }
 | |
| 
 | |
| /* 响应式布局,大屏幕用更大的尺寸 */
 | |
| @media (min-width: 400px) {
 | |
|   ._contain {
 | |
|     width: 380px;
 | |
|   }
 | |
| 
 | |
|   ._button {
 | |
|     width: 26px;
 | |
|     height: 26px;
 | |
|   }
 | |
| 
 | |
|   ._poster {
 | |
|     width: 90px;
 | |
|     height: 90px;
 | |
|   }
 | |
| 
 | |
|   ._author {
 | |
|     width: 60px;
 | |
|     font-size: 15px;
 | |
|   }
 | |
| 
 | |
|   ._name {
 | |
|     width: 180px;
 | |
|     font-size: 19px;
 | |
|     line-height: 55px;
 | |
|   }
 | |
| 
 | |
|   ._slider {
 | |
|     right: 20px;
 | |
|     bottom: 10px;
 | |
|     width: 180px;
 | |
|   }
 | |
| 
 | |
|   ._time {
 | |
|     font-size: 15px;
 | |
|   }
 | |
| }
 | |
| </style>
 |