Initial commit

This commit is contained in:
admin
2025-12-08 14:39:07 +08:00
commit 9d4f78656b
782 changed files with 66418 additions and 0 deletions

View File

@@ -0,0 +1,189 @@
/**
* @fileoverview audio 组件
*/
const context = require('./context')
Component({
data: {
time: '00:00'
},
properties: {
name: String, // 音乐名
author: String, // 作者
poster: String, // 海报图片地址
autoplay: Boolean, // 是否自动播放
controls: Boolean, // 是否显示控件
loop: Boolean, // 是否循环播放
src: { // 源地址
type: String,
observer (src) {
this.setSrc(src)
}
}
},
created () {
// 创建内部 context
this._ctx = wx.createInnerAudioContext()
this._ctx.onError(err => {
this.setData({
error: true
})
this.triggerEvent('error', err)
})
this._ctx.onTimeUpdate(() => {
const time = this._ctx.currentTime
const min = parseInt(time / 60)
const sec = Math.ceil(time % 60)
const data = {}
data.time = (min > 9 ? min : '0' + min) + ':' + (sec > 9 ? sec : '0' + sec)
// 不在拖动状态下需要更新进度条
if (!this.lastTime) {
data.value = time / this._ctx.duration * 100
}
this.setData(data)
})
this._ctx.onEnded(() => {
if (!this.properties.loop) {
this.setData({
playing: false
})
}
})
// #ifndef ALIPAY
},
attached () {
context.set(this.id, this)
// #endif
// #ifdef MP-ALIPAY
context.set(this.properties.id, this)
this.setSrc(this.properties.src)
// #endif
},
// #ifdef MP-ALIPAY
didUpdate (e) {
if (e.src !== this.properties.src) {
this.setSrc(this.properties.src)
}
},
// #endif
detached () {
this._ctx.destroy()
// #ifndef MP-ALIPAY
context.remove(this.id)
// #endif
// #ifdef MP_ALIPAY
context.remove(this.properties.id)
// #endif
},
// #ifndef ALIPAY | TOUTIAO
pageLifetimes: {
show () {
// 播放被后台打断时,页面显示后自动继续播放
if (this.data.playing && this._ctx.paused) {
this._ctx.play()
}
}
},
// #endif
methods: {
/**
* @description 设置源
* @param {string} src 源地址
*/
setSrc (src) {
this._ctx.autoplay = this.properties.autoplay
this._ctx.loop = this.properties.loop
this._ctx.src = src
if (this.properties.autoplay && !this.data.playing) {
this.setData({
playing: true
})
}
},
/**
* @description 播放音乐
*/
play () {
this._ctx.play()
this.setData({
playing: true
})
this.triggerEvent('play'
// #ifdef MP-ALIPAY
, {
target: {
id: this.props.id
}
}
// #endif
)
},
/**
* @description 暂停音乐
*/
pause () {
this._ctx.pause()
this.setData({
playing: false
})
this.triggerEvent('pause')
},
/**
* @description 设置播放速率
* @param {Number} rate 播放速率
*/
playbackRate (rate) {
this._ctx.playbackRate = rate
},
/**
* @description 停止音乐
*/
stop () {
this._ctx.stop()
this.setData({
playing: false,
time: '00:00'
})
this.triggerEvent('stop')
},
/**
* @description 控制进度
* @param {number} sec 秒数
*/
seek (sec) {
this._ctx.seek(sec)
},
/**
* @description 移动进度条
* @param {event} e
* @private
*/
_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.setData({
time: (min > 9 ? min : '0' + min) + ':' + (sec > 9 ? sec : '0' + sec)
})
this.lastTime = e.timeStamp
},
/**
* @description 进度条移动完毕
* @param {event} e
* @private
*/
_seeked (e) {
this._ctx.seek(e.detail.value / 100 * this._ctx.duration)
this.lastTime = undefined
}
}
})

View File

@@ -0,0 +1,3 @@
{
"component": true
}

View File

@@ -0,0 +1,17 @@
<view wx:if="{{controls}}" class="_contain">
<!-- 海报和按钮 -->
<view class="_poster" style="background-image:url('{{poster}}')">
<view class="_button" bindtap="{{playing?'pause':'play'}}">
<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" disabled="{{error}}" value="{{value}}" bindchanging="_seeking" bindchange="_seeked" />
<!--播放时间-->
<view class="_time">{{time}}</view>
</view>

View File

@@ -0,0 +1,127 @@
/* 顶层容器 */
._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;
}
}

View File

@@ -0,0 +1,3 @@
module.exports = {
template: '<my-audio wx:if="{{n.name==\'audio\'}}" id="{{n.attrs.id}}" class="{{n.attrs.class}}" style="{{n.attrs.style}}" author="{{n.attrs.author}}" controls="{{n.attrs.controls}}" autoplay="{{n.attrs.autoplay}}" loop="{{n.attrs.loop}}" name="{{n.attrs.name}}" poster="{{n.attrs.poster}}" src="{{n.src[ctrl[i]||0]}}" data-i="{{i}}" data-source="audio" bindplay="play" binderror="mediaError" />'
}