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>
 |