原生:
miniprogram/components/mp-audio/index.wxml
html
<view class="imt-audio">
<view class="top">
<view class="audio-control-wrapper">
<image src="{{poster}}" mode="aspectFill" class="cover"></image>
<image src="/static/components/loading.png" wx:if="{{playState ==='loading'}}" class="play loading"></image>
<block wx:else>
<image src="/static/components/playbtn.png" alt="play" bind:tap="play" class="play" wx:if="{{playState === 'pause'}}"></image>
<image src="/static/components/pausebtn.png" alt="pause" bind:tap="pause" class="play" wx:else></image>
</block>
</view>
</view>
<view class="audio-wrapper">
<view class="titlebox">
<view class="title text-max1">{{name}}</view>
</view>
<slider class="audio-slider" block-size="12" max="{{duration}}" value="{{currentTime}}" bindchange="sliderChange" block-color="#999999" bindchanging="sliderChanging"></slider>
<view class="slidebox">
<view>{{showCurrentTime}} / {{showDuration}}</view>
</view>
</view>
</view>
miniprogram/components/mp-audio/index.ts
TypeScript
Component({
observers: {
currentTime(value) {
this.setData({
showCurrentTime: this.formatSeconds(value)
})
},
duration(value) {
this.setData({
showDuration: this.formatSeconds(value)
})
}
},
detached(){
this.data.audioCtx.destroy()
this.setData({
audioCtx: null as unknown as WechatMiniprogram.InnerAudioContext
})
},
lifetimes: {
created() {
this.setData({
audioCtx: wx.createInnerAudioContext({
useWebAudioImplement: false
})
})
this.data.audioCtx.onTimeUpdate((e) => {
const currentTime = this.data.audioCtx.currentTime
this.setData({
currentTime,
})
})
this.data.audioCtx.onPlay(() => console.log("播放"))
this.data.audioCtx.onPause(() => console.log("暂停"))
this.data.audioCtx.onStop(() => console.log("停止"))
this.data.audioCtx.onEnded(() => {
console.log("结束")
this.setData({
playState: 'pause',
currentTime: 0
})
this.data.audioCtx.seek(0)
})
}
},
/**
* 组件的属性列表
*/
properties: {
/**
* 音频源地址
*/
src: {
type: String,
value: ''
},
/**
* 封面图片地址
*/
poster: {
type: String,
value: ''
},
/**
* 音频标题
*/
name: {
type: String,
value: '标题'
},
/**
* 歌手名称
*/
singer: {
type: String,
value: ''
},
},
/**
* 组件的初始数据
*/
data: {
src: "",
poster: "",
name: "标题",
singer: "",
duration: 10,
currentTime: 0,
showCurrentTime: '00:00',
showDuration: '00:00',
playState: "pause", //"loading"/"playing"/"pause"
isSliderChanging: false,
audioCtx: null as unknown as WechatMiniprogram.InnerAudioContext
},
/**
* 组件的方法列表
*/
methods: {
setSrc(value: string) {
this.data.audioCtx.src = value
let v1 = setInterval(() => {
if (this.data.audioCtx.duration > 0) {
this.setData({
duration: this.data.audioCtx.duration
})
clearInterval(v1)
}
},500)
},
setPoster(value: string) {
this.setData({ poster: value });
},
setName(value: string) {
this.setData({ name: value });
},
setSinger(value: string) {
this.setData({ singer: value });
},
playerOnPlay() {
this.setData({
playState: 'playing'
})
this.triggerEvent('play');
},
playerOnPause() {
this.setData({
playState: 'pause'
})
this.triggerEvent('pause');
},
playerOnEnded() {
this.setData({
playState: 'pause'
})
this.triggerEvent('ended');
},
playerOnWaiting() {
this.setData({
playState: 'loading'
})
},
playerOnError(e: WechatMiniprogram.VideoError) {
wx.$log.error("playerOnError音频播放失败:" + JSON.stringify(e))
this.setData({
playState: 'pause'
})
this.triggerEvent('error', e);
},
formatSeconds(seconds: number | string): string {
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) + (Number(h)) * 60;
let s = Math.floor(result % 60) < 10 ?
"0" + Math.floor(result % 60) :
Math.floor(result % 60);
return `${m}:${s}`;
},
stop() {
try {
if (this.data.audioCtx) {
this.data.audioCtx.stop();
}
this.setData({ currentTime: 0 });
} catch (error: any) {
console.error('停止失败:', error);
this.triggerEvent('error', error);
}
},
seek(t: number) {
if (this.data.audioCtx) {
this.data.audioCtx.seek(t);
}
},
play() {
try {
if (this.data.audioCtx) {
this.data.audioCtx.play();
this.setData({
playState: "playing"
})
}
} catch (error: any) {
console.error('播放失败:', error);
this.triggerEvent('error', error);
}
},
pause() {
try {
if (this.data.audioCtx) {
this.data.audioCtx.pause();
this.setData({
playState: "pause"
})
}
} catch (error: any) {
console.error('暂停失败:', error);
this.triggerEvent('error', error);
}
},
sliderChange(e: WechatMiniprogram.SliderChanging) {
console.log("sliderChange", e)
this.setData({
isSliderChanging: false
})
//就会存在滚动条拖不动的情况
if (this.data.audioCtx) {
this.data.audioCtx.seek(e.detail.value);
this.setData({
currentTime: e.detail.value
})
}
},
sliderChanging() {
this.setData({
isSliderChanging: true
})
},
}
})
miniprogram/components/mp-audio/index.scss
css
@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{
background: #ffffff;
box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.2);
width: 100%;
overflow: hidden;
display: flex;
box-sizing: border-box;
position:relative;
padding: 12rpx;
border-radius: 12rpx;
.top {
width: 140rpx;
position: relative;
}
.audio-wrapper {
display: flex;
flex-direction: column;
justify-content: space-between;
flex: 1;
color: #fff;
margin-left: 20rpx;
width: 100%;
height: 100%;
.titlebox {
display: flex;
color: #000000;
.title {
font-size: 30rpx;
@include textoverflow;
}
.singer {
margin-left: 20rpx;
font-size: 28rpx;
max-width: 50%;
@include textoverflow;
}
}
}
.slidebox {
display: flex;
justify-content: space-between;
width: 96%;
color: #333;
}
.uni-slider-tap-area {
padding: 0;
}
.uni-slider-wrapper {
min-height: 0;
}
.uni-slider-handle-wrapper {
height: 6px;
}
.audio-slider {
margin: 0 !important;
}
.cover {
width: 120rpx;
height: 120rpx;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
animation-fill-mode: forwards;
-webkit-animation-fill-mode: forwards;
}
.play {
width: 80rpx;
height: 80rpx;
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: 60rpx;
height: 60rpx;
animation: rotating_theme3 2s linear infinite;
}
}
}
@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;
}
使用:
css
<mp-audio id="mpAudio" />
handleSetSrcInfo() {
nextTick(() => {
const child = this.selectComponent('#mpAudio');
if (child) {
child.setSrc(this.data.mission!.Media![0]);
child.setName(this.data.mission.Title);
}
})
},
uniapp:
components/yz-audio/yz-audio.vue
html
<template>
<view class="imt-audio">
<view class="top">
<view class="audio-control-wrapper">
<image :src="poster" mode="aspectFill" class="cover"></image>
<image :src="require('./static/loading.png')" v-if="playState ==='loading'" class="play loading"></image>
<block 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>
</block>
</view>
</view>
<view class="audio-wrapper">
<view class="titlebox">
<view class="title text-max1">{{name}}</view>
</view>
<slider class="audio-slider" block-size="12" :max="duration" :value="currentTime" @change="sliderChange"
block-color="#999999" @changing="sliderChanging"></slider>
<view class="slidebox">
<view>{{formatSeconds(currentTime)}} / {{formatSeconds(duration)}}</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
src: "",
poster: "",
name: "标题",
singer: "",
duration: 10,
currentTime: 0,
playState: "pause", //"loading"/"playing"/"pause"
isSliderChanging: false,
audioCtx: null
};
},
created() {
this.audioCtx = uni.createInnerAudioContext()
this.audioCtx.onTimeUpdate((e) => {
const currentTime = this.audioCtx.currentTime
this.currentTime = currentTime
})
this.audioCtx.onPlay(() => console.log("播放"))
this.audioCtx.onPause(() => console.log("暂停"))
this.audioCtx.onStop(() => console.log("停止"))
this.audioCtx.onEnded(() => {
console.log("结束")
this.playState = 'pause',
this.currentTime = 0
this.audioCtx.seek(0)
})
},
destroyed() {
this.audioCtx.destroy()
this.audioCtx = null
},
methods: {
setSrc(value) {
this.audioCtx.src = value
let v1 = setInterval(() => {
console.log("this.audioCtx.duration", this.audioCtx.duration)
if (this.audioCtx.duration > 0) {
this.duration = this.audioCtx.duration
clearInterval(v1)
}
}, 500)
},
setPoster(value) {
this.poster = value;
},
setName(value) {
this.name = value;
},
play() {
if (this.audioCtx) {
this.audioCtx.play();
this.playState = "playing"
}
},
pause() {
this.audioCtx.pause();
this.playState = "pause"
},
sliderChange(e) {
this.isSliderChanging = false;
if (this.audioCtx) {
this.audioCtx.seek(e.detail.value);
this.currentTime = e.detail.value
}
},
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) + (Number(h)) * 60;
let s = Math.floor(result % 60) < 10 ?
"0" + Math.floor(result % 60) :
Math.floor(result % 60);
return `${m}:${s}`;
},
sliderChanging() {
this.isSliderChanging = true;
},
},
}
</script>
<style lang="scss">
@import './index.scss';
</style>
components/yz-audio/index.scss
css
@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{
background: #ffffff;
box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.2);
width: 100%;
overflow: hidden;
display: flex;
box-sizing: border-box;
position:relative;
padding: 12rpx;
border-radius: 12rpx;
.top {
width: 140rpx;
position: relative;
}
.audio-wrapper {
display: flex;
flex-direction: column;
justify-content: space-between;
flex: 1;
color: #fff;
margin-left: 20rpx;
width: 100%;
height: 100%;
.titlebox {
display: flex;
color: #000000;
.title {
font-size: 30rpx;
@include textoverflow;
}
.singer {
margin-left: 20rpx;
font-size: 28rpx;
max-width: 50%;
@include textoverflow;
}
}
}
.slidebox {
display: flex;
justify-content: space-between;
width: 96%;
color: #333;
}
.uni-slider-tap-area {
padding: 0;
}
.uni-slider-wrapper {
min-height: 0;
}
.uni-slider-handle-wrapper {
height: 6px;
}
.audio-slider {
margin: 0 !important;
}
.cover {
width: 120rpx;
height: 120rpx;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
animation-fill-mode: forwards;
-webkit-animation-fill-mode: forwards;
}
.play {
width: 80rpx;
height: 80rpx;
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: 60rpx;
height: 60rpx;
animation: rotating_theme3 2s linear infinite;
}
}
}
@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;
}
使用:
css
<YzAudio v-if="obj.Media && obj.Media.length" ref="player1" />
handleSetAudioSrcInfo() {
if (!this.obj.Media) return
this.$nextTick(() => {
const player1 = this.$refs.player1;
player1.setSrc(this.obj.Media[0]);
player1.setName(this.obj.Title);
})
}