|
|
@@ -0,0 +1,432 @@ |
|
|
|
<template> |
|
|
|
<view class="imt-audio"> |
|
|
|
<template> |
|
|
|
<view class="top"> |
|
|
|
<view class="audio-control-wrapper"> |
|
|
|
<image :src="require('./static/loading.png')" v-if="playState=='loading'" class="play loading"> |
|
|
|
</image> |
|
|
|
<template v-else> |
|
|
|
<image :src="require('./static/playbtn.png')" alt="play" @click="play" class="play" |
|
|
|
v-if="playState=='pause'"></image> |
|
|
|
<image :src="require('./static/pausebtn.png')" alt="pause" @click="pause" class="play" v-else> |
|
|
|
</image> |
|
|
|
</template> |
|
|
|
</view> |
|
|
|
</view> |
|
|
|
<view class="audio-wrapper"> |
|
|
|
<view class="audio-flex"> |
|
|
|
<text> |
|
|
|
{{formatSeconds(currentTime)}} |
|
|
|
</text> |
|
|
|
<slider class="audio-slider" block-size="12" :max="duration" :value="currentTime" |
|
|
|
@change="sliderChange" @changing="sliderChanging"></slider> |
|
|
|
<text> |
|
|
|
{{formatSeconds(duration)}} |
|
|
|
</text> |
|
|
|
</view> |
|
|
|
|
|
|
|
<view class="slidebox" @click="showTip"> |
|
|
|
<slot name="extraCtrls"> |
|
|
|
<image class="slide-img" :src="require('./static/backimg.png')" mode=""></image> |
|
|
|
</slot> |
|
|
|
</view> |
|
|
|
<!-- 后台播放按钮区域 --> |
|
|
|
<view class="popup" v-if="show" :class="{close: closeing}" @click="checkPlayer"> |
|
|
|
<template v-if="!isBgPlay"> |
|
|
|
<image :src="require('./static/bg.png')" mode=""></image> |
|
|
|
</template> |
|
|
|
<template v-else> |
|
|
|
<image :src="require('./static/bg_act.png')" mode=""></image> |
|
|
|
</template> |
|
|
|
<text :class="{'act-test': isBgPlay}">后台播放</text> |
|
|
|
</view> |
|
|
|
</view> |
|
|
|
<!--video在ios中不能完全隐藏,否则无法播放--> |
|
|
|
<video id="videoPlayer" :autoplay="true" class="videoPlayer" :src="src" :muted="false" |
|
|
|
style="width: 10rpx;height:10rpx;" @play="playerOnPlay" @pause="playerOnPause" @ended="playerOnEnded" |
|
|
|
@timeupdate="playerOnTimeupdate" @waiting="playerOnWaiting" @error="playerOnError"></video> |
|
|
|
</template> |
|
|
|
</view> |
|
|
|
</template> |
|
|
|
|
|
|
|
<script> |
|
|
|
/* |
|
|
|
createInnerAudioContext()是audio组件的内部实现,不能熄屏播放、不能后台播放、不能倍速播放。 |
|
|
|
getBackgroundAudioManager() 可以熄屏播放、后台播放,不能倍速播放。缺点是响应速度很慢,无法实现精细、及时的进度控制,而且可能被别的程序占用。 |
|
|
|
因此这里只能用video来实现,video能倍速播放,不能熄屏播放、不能后台播放。而且避免了用createInnerAudioContext()实现的跳转到别的页面,还在播放的问题 |
|
|
|
因此应用程序可以在需要后台播放的时候(需要用户操作触发),再暂停这个控件的播放,然后自己用getBackgroundAudioManager实现后台播放 |
|
|
|
*/ |
|
|
|
|
|
|
|
import Vue from 'vue'; |
|
|
|
import { |
|
|
|
mapMutations |
|
|
|
} from 'vuex' |
|
|
|
import { |
|
|
|
audios |
|
|
|
} from './audioBg.js' |
|
|
|
export default { |
|
|
|
mixins: [audios], |
|
|
|
props: { |
|
|
|
nowFileTime: { |
|
|
|
type: [String, Number], |
|
|
|
default: 0 |
|
|
|
}, |
|
|
|
}, |
|
|
|
|
|
|
|
watch: { |
|
|
|
nowFileTime(oValue, nValue) { |
|
|
|
this.duration = nValue |
|
|
|
} |
|
|
|
}, |
|
|
|
|
|
|
|
data() { |
|
|
|
return { |
|
|
|
src: '', // |
|
|
|
poster: "", |
|
|
|
name: "...", |
|
|
|
singer: "...", |
|
|
|
duration: 0, |
|
|
|
currentTime: 0, |
|
|
|
playState: "pause", //"loading"/"playing"/"pause" |
|
|
|
isSliderChanging: false, |
|
|
|
isFirst: false, // 是否阻止第一次赋值 |
|
|
|
audio: null, // 音频对象 |
|
|
|
show: false, // 控制展示用的 |
|
|
|
closeing: false, // 默认关闭 |
|
|
|
}; |
|
|
|
}, |
|
|
|
created() { |
|
|
|
// 自定义组件,需要传递第二个参数为this,否则后续的pause等操作不起作用 |
|
|
|
this.videoCtx = uni.createVideoContext("videoPlayer", this); |
|
|
|
this.audio = uni.createInnerAudioContext(); |
|
|
|
this.audio.autoplay = false; |
|
|
|
this.createAudio() |
|
|
|
this.setAudioFunc() |
|
|
|
}, |
|
|
|
|
|
|
|
mounted() { |
|
|
|
this.audio.onCanplay((e) => { |
|
|
|
if (this.audio.duration != 0) { |
|
|
|
this.playState = "pause" |
|
|
|
this.$forceUpdate() |
|
|
|
} |
|
|
|
}) |
|
|
|
}, |
|
|
|
|
|
|
|
methods: { |
|
|
|
...mapMutations(['createAudio', 'stopAduio']), |
|
|
|
setSrc(value) { |
|
|
|
console.log(this, ' 我打印this') |
|
|
|
this.src = value; |
|
|
|
console.log(this.src, '我在这儿里更换src') |
|
|
|
// 获取当前音频的总时长 |
|
|
|
this.audio.src = value; |
|
|
|
}, |
|
|
|
|
|
|
|
setPoster(value) { |
|
|
|
this.poster = value; |
|
|
|
}, |
|
|
|
setName(value) { |
|
|
|
this.name = value; |
|
|
|
}, |
|
|
|
setSinger(value) { |
|
|
|
this.singer = value; |
|
|
|
}, |
|
|
|
|
|
|
|
playerOnPlay(e) { |
|
|
|
this.playState = "playing"; |
|
|
|
console.log('playerOnPlay', e) |
|
|
|
this.$emit("play"); |
|
|
|
}, |
|
|
|
playerOnPause(e) { |
|
|
|
this.playState = "pause"; |
|
|
|
console.log('playerOnPause', e) |
|
|
|
this.$emit("pause"); |
|
|
|
}, |
|
|
|
playerOnEnded(e) { |
|
|
|
this.playState = "pause"; |
|
|
|
console.log('playerOnEnded', e) |
|
|
|
this.$emit("ended"); |
|
|
|
}, |
|
|
|
|
|
|
|
playerOnTimeupdate(e) { |
|
|
|
if (this.isFirst) this.playState = "playing"; |
|
|
|
this.isFirst = true |
|
|
|
this.duration = e.detail.duration; |
|
|
|
this.currentTime = e.detail.currentTime; |
|
|
|
this.$emit("timeUpdate", e.detail); |
|
|
|
}, |
|
|
|
|
|
|
|
playerOnWaiting(e) { |
|
|
|
this.playState = "loading"; |
|
|
|
console.log('playerOnWaiting', e) |
|
|
|
}, |
|
|
|
playerOnError(e) { |
|
|
|
console.log('playerOnError', e) |
|
|
|
this.playState = "pause"; |
|
|
|
this.$emit("error", e); |
|
|
|
}, |
|
|
|
formatSeconds(seconds) { |
|
|
|
var result = typeof seconds === "string" ? parseFloat(seconds) : seconds; |
|
|
|
if (isNaN(result)) return ""; |
|
|
|
let h = Math.floor(result / 3600) < 10 ? |
|
|
|
"0" + Math.floor(result / 3600) : |
|
|
|
Math.floor(result / 3600); |
|
|
|
let m = Math.floor((result / 60) % 60) < 10 ? |
|
|
|
"0" + Math.floor((result / 60) % 60) : |
|
|
|
Math.floor((result / 60) % 60) + h * 60; |
|
|
|
let s = Math.floor(result % 60) < 10 ? |
|
|
|
"0" + Math.floor(result % 60) : |
|
|
|
Math.floor(result % 60); |
|
|
|
return `${h}:${m}:${s}`; |
|
|
|
}, |
|
|
|
stop() { |
|
|
|
this.videoCtx.stop(); |
|
|
|
}, |
|
|
|
seek(t) { |
|
|
|
this.videoCtx.seek(t); |
|
|
|
}, |
|
|
|
play() { |
|
|
|
// if (this.videoCtx.currentTime != this.videoCtx.currentTime) { |
|
|
|
// this.seek(this.currentTime) |
|
|
|
// } |
|
|
|
console.log('触发方法play') |
|
|
|
this.videoCtx.play(); //在有的H5浏览器里,如果play不是用户触发的,则play()会报错 |
|
|
|
// 暂停后台播放 |
|
|
|
this.stopAduio() |
|
|
|
}, |
|
|
|
pause() { |
|
|
|
console.log('触发方法pause') |
|
|
|
this.videoCtx.pause(); |
|
|
|
}, |
|
|
|
playbackRate(value) { |
|
|
|
this.videoCtx.playbackRate(value); |
|
|
|
//playbackRate不能在play之前或者之后立即调用,否则只有很少几率会成功 |
|
|
|
}, |
|
|
|
sliderChange(e) { |
|
|
|
this.isSliderChanging = false; |
|
|
|
//要通过e.detail.value获取,否则如果通过dom去读取slider的value |
|
|
|
//就会存在滚动条拖不动的情况 |
|
|
|
// this.videoCtx.seek(e.detail.value); |
|
|
|
let type = 'audio' |
|
|
|
|
|
|
|
if (this.bgAudioMannager.paused === false) { |
|
|
|
type = 'bgAudio' |
|
|
|
} |
|
|
|
this.$emit('sliderChangeComplate', { ...e, isType: type }) |
|
|
|
}, |
|
|
|
sliderChanging(e) { |
|
|
|
this.isSliderChanging = true; |
|
|
|
console.log(e, '当前正在改变') |
|
|
|
}, |
|
|
|
|
|
|
|
// 关闭后台播放按钮 |
|
|
|
closeTip() { |
|
|
|
this.closeing = false |
|
|
|
setTimeout(() => { |
|
|
|
this.show = false |
|
|
|
}, 250) |
|
|
|
}, |
|
|
|
|
|
|
|
// 展示后台播放按钮 |
|
|
|
showTip() { |
|
|
|
this.show = true |
|
|
|
setTimeout(() => { |
|
|
|
this.closeing = true |
|
|
|
}, 50) |
|
|
|
}, |
|
|
|
|
|
|
|
// 点击后台播放音频事件 |
|
|
|
backAudio() { |
|
|
|
this.pause() |
|
|
|
let obj = { |
|
|
|
src: this.src, |
|
|
|
currentTime: this.currentTime |
|
|
|
} |
|
|
|
this.setAudio(obj) |
|
|
|
}, |
|
|
|
|
|
|
|
// 切换播放源 |
|
|
|
checkPlayer() { |
|
|
|
this.closeTip() |
|
|
|
if (this.bgAudioMannager.paused === false) { |
|
|
|
this.stopAduio() |
|
|
|
} else { |
|
|
|
this.backAudio() |
|
|
|
} |
|
|
|
} |
|
|
|
}, |
|
|
|
} |
|
|
|
</script> |
|
|
|
|
|
|
|
<style lang="scss"> |
|
|
|
// @import './index.scss'; |
|
|
|
@mixin textoverflow() { |
|
|
|
display: -webkit-box; |
|
|
|
overflow: hidden; |
|
|
|
text-overflow: ellipsis; |
|
|
|
-webkit-box-orient: vertical; |
|
|
|
-webkit-line-clamp: 1; |
|
|
|
} |
|
|
|
|
|
|
|
@keyframes rowup { |
|
|
|
0% { |
|
|
|
-webkit-transform: translate(-50%, -50%) rotate(0deg); |
|
|
|
transform-origin: center center; |
|
|
|
} |
|
|
|
|
|
|
|
100% { |
|
|
|
-webkit-transform: translate(-50%, -50%) rotate(360deg); |
|
|
|
transform-origin: center center; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
.imt-audio { |
|
|
|
position: relative; |
|
|
|
width: 100%; |
|
|
|
height: 81rpx; |
|
|
|
display: flex; |
|
|
|
box-sizing: border-box; |
|
|
|
background: #fff; |
|
|
|
|
|
|
|
.top { |
|
|
|
position: relative; |
|
|
|
width: 100rpx; |
|
|
|
} |
|
|
|
|
|
|
|
.audio-wrapper { |
|
|
|
position: relative; |
|
|
|
padding: 0 20rpx; |
|
|
|
display: flex; |
|
|
|
flex: 1; |
|
|
|
color: #fff; |
|
|
|
|
|
|
|
.popup { |
|
|
|
position: absolute; |
|
|
|
right: 32rpx; |
|
|
|
top: -122rpx; |
|
|
|
z-index: 100; |
|
|
|
width: 136rpx; |
|
|
|
height: 122rpx; |
|
|
|
display: flex; |
|
|
|
align-items: center; |
|
|
|
justify-content: center; |
|
|
|
flex-direction: column; |
|
|
|
background: #fff; |
|
|
|
border: 1rpx solid #E0E0E0; |
|
|
|
transition: all 0.25s linear; |
|
|
|
opacity: 0; |
|
|
|
|
|
|
|
image { |
|
|
|
width: 32rpx; |
|
|
|
height: 32rpx; |
|
|
|
} |
|
|
|
|
|
|
|
text { |
|
|
|
margin-top: 10rpx; |
|
|
|
color: #333; |
|
|
|
font-size: 24rpx; |
|
|
|
} |
|
|
|
|
|
|
|
.act-test { |
|
|
|
color: #2671E2; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
.close { |
|
|
|
opacity: 1; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
.slidebox { |
|
|
|
flex-shrink: 0; |
|
|
|
display: flex; |
|
|
|
align-items: center; |
|
|
|
|
|
|
|
.slide-img { |
|
|
|
width: 32rpx; |
|
|
|
height: 8rpx; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/deep/ .uni-slider-tap-area { |
|
|
|
padding: 0; |
|
|
|
} |
|
|
|
|
|
|
|
/deep/ .uni-slider-wrapper { |
|
|
|
min-height: 0; |
|
|
|
} |
|
|
|
|
|
|
|
/deep/ .uni-slider-handle-wrapper { |
|
|
|
height: 6px; |
|
|
|
} |
|
|
|
|
|
|
|
.audio-slider { |
|
|
|
flex-grow: 1; |
|
|
|
} |
|
|
|
|
|
|
|
.play { |
|
|
|
width: 48rpx; |
|
|
|
height: 48rpx; |
|
|
|
z-index: 99; |
|
|
|
background: rgba(0, 0, 0, 0.4); |
|
|
|
border-radius: 50%; |
|
|
|
position: absolute; |
|
|
|
top: 50%; |
|
|
|
left: 50%; |
|
|
|
transform: translate(-50%, -50%); |
|
|
|
|
|
|
|
&.loading { |
|
|
|
width: 48rpx; |
|
|
|
height: 48rpx; |
|
|
|
animation: rotating_theme3 2s linear infinite; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
.audio-flex { |
|
|
|
padding: 0 32rpx 0 0; |
|
|
|
flex-grow: 1; |
|
|
|
display: flex; |
|
|
|
align-items: center; |
|
|
|
|
|
|
|
text { |
|
|
|
color: #70798D; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@keyframes rotating { |
|
|
|
0% { |
|
|
|
transform: rotateZ(0deg) |
|
|
|
} |
|
|
|
|
|
|
|
100% { |
|
|
|
transform: rotateZ(360deg) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@keyframes rotating_theme3 { |
|
|
|
0% { |
|
|
|
transform: translate(-50%, -50%) rotateZ(0deg) |
|
|
|
} |
|
|
|
|
|
|
|
100% { |
|
|
|
transform: translate(-50%, -50%) rotateZ(360deg) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
.hItem { |
|
|
|
margin-left: 16rpx; |
|
|
|
} |
|
|
|
|
|
|
|
.extrButton { |
|
|
|
font-size: 36rpx; |
|
|
|
} |
|
|
|
|
|
|
|
.videoPlayer { |
|
|
|
position: absolute; |
|
|
|
left: 0; |
|
|
|
bottom: 0; |
|
|
|
z-index: -1; |
|
|
|
} |
|
|
|
</style> |