uview-ultra 4.5.74 → 4.5.77
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/changelog.md +84 -0
- package/components/up-audio/audio-pool.js +2 -0
- package/components/up-audio/audio-pool.uts +2 -0
- package/components/up-audio/audio.js +19 -0
- package/components/up-audio/audio.uts +19 -0
- package/components/up-audio/props.js +43 -0
- package/components/up-audio/up-audio.uvue +418 -0
- package/components/up-audio/up-audio.vue +315 -0
- package/components/up-lottie/libs/LICENSE-lottie-miniprogram +21 -0
- package/components/up-lottie/libs/LICENSE-lottie-web +21 -0
- package/components/up-lottie/libs/gen-uvue-variant.py +47 -0
- package/components/up-lottie/libs/harmony-canvas-adapter.js +394 -0
- package/components/up-lottie/libs/lottie-miniprogram.min.js +14 -0
- package/components/up-lottie/libs/lottie-miniprogram.uvue.js +32 -0
- package/components/up-lottie/libs/lottie_svg.min.js +6 -0
- package/components/up-lottie/libs/wx-env.js +15 -0
- package/components/up-lottie/lottie.js +14 -0
- package/components/up-lottie/lottie.uts +14 -0
- package/components/up-lottie/props.js +32 -0
- package/components/up-lottie/up-lottie.uvue +514 -0
- package/components/up-lottie/up-lottie.vue +652 -0
- package/components/up-parse/node/node.vue +21 -3
- package/components/up-video/video.uts +1 -2
- package/libs/config/props.js +4 -0
- package/package.json +3 -3
- package/types/comps/audio.d.ts +57 -0
- package/types/comps/lottie.d.ts +34 -0
- package/types/comps.d.ts +121 -120
- package/types/index.d.ts +383 -382
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<view class="up-audio">
|
|
3
|
+
<!-- 播放/暂停按钮 -->
|
|
4
|
+
<view
|
|
5
|
+
class="up-audio__btn"
|
|
6
|
+
:style="{ backgroundColor: activeColor }"
|
|
7
|
+
@tap.stop="toggle"
|
|
8
|
+
>
|
|
9
|
+
<text class="up-audio__btn-icon">{{ playing ? '❚❚' : '▶' }}</text>
|
|
10
|
+
</view>
|
|
11
|
+
<!-- 主体:标题 + 时间 + 进度条 -->
|
|
12
|
+
<view class="up-audio__main">
|
|
13
|
+
<view class="up-audio__row">
|
|
14
|
+
<text
|
|
15
|
+
v-if="hasTitle"
|
|
16
|
+
class="up-audio__title"
|
|
17
|
+
>{{ displayTitle }}</text>
|
|
18
|
+
<text class="up-audio__time">{{ formatTime(currentTime) }} / {{ formatTime(duration) }}</text>
|
|
19
|
+
</view>
|
|
20
|
+
<slider
|
|
21
|
+
class="up-audio__slider"
|
|
22
|
+
:value="progress"
|
|
23
|
+
:max="100"
|
|
24
|
+
:block-size="10"
|
|
25
|
+
:activeColor="activeColor"
|
|
26
|
+
backgroundColor="#e4e7ed"
|
|
27
|
+
@changing="onChanging"
|
|
28
|
+
@change="onChange"
|
|
29
|
+
/>
|
|
30
|
+
</view>
|
|
31
|
+
<!-- 倍速 -->
|
|
32
|
+
<view
|
|
33
|
+
v-if="showRate"
|
|
34
|
+
class="up-audio__rate"
|
|
35
|
+
@tap.stop="changeRate"
|
|
36
|
+
>
|
|
37
|
+
<text class="up-audio__rate-text">{{ rateText }}</text>
|
|
38
|
+
</view>
|
|
39
|
+
</view>
|
|
40
|
+
</template>
|
|
41
|
+
|
|
42
|
+
<script setup>
|
|
43
|
+
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
|
44
|
+
import { props as audioProps } from './props.js'
|
|
45
|
+
import { commonProps } from '../../libs/composable/useUltraUI.js'
|
|
46
|
+
import { audioInstancePool } from './audio-pool.js'
|
|
47
|
+
/**
|
|
48
|
+
* up-audio 音频播放器
|
|
49
|
+
* @description uni-app 在 Vue3 中已废弃 <audio> 组件,本组件基于 uni.createInnerAudioContext 实现播放,可单独使用,up-parse 的音频节点也由本组件渲染。
|
|
50
|
+
* @tutorial https://uview-plus.jiangruyi.com/components/audio.html
|
|
51
|
+
* @property {String | Array} src 音频地址,支持数组多源,加载失败自动切换
|
|
52
|
+
* @property {String} title 标题,显示在音频条上
|
|
53
|
+
* @property {Boolean} loop 是否循环播放 (默认 false )
|
|
54
|
+
* @property {Boolean} showRate 是否显示倍速切换 (默认 true )
|
|
55
|
+
* @property {Array} rateList 可切换的倍速档位 (默认 [0.5, 0.75, 1, 1.25, 1.5, 2] )
|
|
56
|
+
* @property {String} activeColor 主题色(播放按钮、进度条选中色) (默认 '#2979ff' )
|
|
57
|
+
* @property {Boolean} showTitle 是否显示标题 (默认 true )
|
|
58
|
+
* @event {Function} play 开始播放时触发,参数 { source, src }
|
|
59
|
+
* @event {Function} error 加载/播放出错时触发,参数 { source, errMsg }
|
|
60
|
+
* @example <up-audio src="https://cdn.example.com/a.mp3" title="示例音频"></up-audio>
|
|
61
|
+
*/
|
|
62
|
+
defineOptions({
|
|
63
|
+
name: 'up-audio',
|
|
64
|
+
// #ifdef MP-WEIXIN
|
|
65
|
+
options: {
|
|
66
|
+
virtualHost: true
|
|
67
|
+
}
|
|
68
|
+
// #endif
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
const props = defineProps({
|
|
72
|
+
...commonProps,
|
|
73
|
+
...audioProps.props
|
|
74
|
+
})
|
|
75
|
+
const emit = defineEmits(['play', 'error'])
|
|
76
|
+
|
|
77
|
+
const playing = ref(false)
|
|
78
|
+
const currentTime = ref(0)
|
|
79
|
+
const duration = ref(0)
|
|
80
|
+
const progress = ref(0)
|
|
81
|
+
const srcIndex = ref(0)
|
|
82
|
+
const rate = ref(1)
|
|
83
|
+
const seeking = ref(false)
|
|
84
|
+
const errorMsg = ref('')
|
|
85
|
+
let ctx = null
|
|
86
|
+
|
|
87
|
+
const srcList = computed(() => {
|
|
88
|
+
if (Array.isArray(props.src)) return props.src.filter(Boolean)
|
|
89
|
+
return props.src ? [props.src] : []
|
|
90
|
+
})
|
|
91
|
+
const displayTitle = computed(() => props.title || '')
|
|
92
|
+
const hasTitle = computed(() => props.showTitle && displayTitle.value !== '')
|
|
93
|
+
const currentSrc = computed(() => srcList.value[srcIndex.value] || '')
|
|
94
|
+
const rateText = computed(() => {
|
|
95
|
+
const r = rate.value
|
|
96
|
+
return (Number.isInteger(r) ? r.toFixed(0) : String(r)) + 'x'
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
// src 变化时重建播放器
|
|
100
|
+
watch(() => props.src, () => {
|
|
101
|
+
reset()
|
|
102
|
+
}, { deep: true })
|
|
103
|
+
|
|
104
|
+
onMounted(() => {
|
|
105
|
+
init()
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
onBeforeUnmount(() => {
|
|
109
|
+
destroy()
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
// 源变化时重建
|
|
113
|
+
function reset() {
|
|
114
|
+
destroy()
|
|
115
|
+
srcIndex.value = 0
|
|
116
|
+
currentTime.value = 0
|
|
117
|
+
duration.value = 0
|
|
118
|
+
progress.value = 0
|
|
119
|
+
playing.value = false
|
|
120
|
+
init()
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function init() {
|
|
124
|
+
if (!currentSrc.value) return
|
|
125
|
+
const audioCtx = uni.createInnerAudioContext()
|
|
126
|
+
ctx = audioCtx
|
|
127
|
+
audioCtx.src = currentSrc.value
|
|
128
|
+
if (props.loop) audioCtx.loop = true
|
|
129
|
+
audioCtx.onPlay(() => {
|
|
130
|
+
playing.value = true
|
|
131
|
+
// 暂停实例池中的其它音频,保证多实例互斥
|
|
132
|
+
for (let i = audioInstancePool.length; i--;) {
|
|
133
|
+
if (audioInstancePool[i] !== audioCtx) {
|
|
134
|
+
try { audioInstancePool[i].pause() } catch (e) {}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
emit('play', {
|
|
138
|
+
source: 'audio',
|
|
139
|
+
src: currentSrc.value
|
|
140
|
+
})
|
|
141
|
+
})
|
|
142
|
+
audioCtx.onPause(() => { playing.value = false })
|
|
143
|
+
audioCtx.onStop(() => { playing.value = false })
|
|
144
|
+
audioCtx.onEnded(() => {
|
|
145
|
+
playing.value = false
|
|
146
|
+
currentTime.value = 0
|
|
147
|
+
progress.value = 0
|
|
148
|
+
})
|
|
149
|
+
audioCtx.onTimeUpdate(() => {
|
|
150
|
+
if (seeking.value) return
|
|
151
|
+
currentTime.value = audioCtx.currentTime || 0
|
|
152
|
+
if (audioCtx.duration) duration.value = audioCtx.duration
|
|
153
|
+
progress.value = duration.value ? (currentTime.value / duration.value) * 100 : 0
|
|
154
|
+
})
|
|
155
|
+
audioCtx.onCanplay(() => {
|
|
156
|
+
if (audioCtx.duration) duration.value = audioCtx.duration
|
|
157
|
+
})
|
|
158
|
+
audioCtx.onError(err => handleError(err))
|
|
159
|
+
audioInstancePool.push(audioCtx)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function destroy() {
|
|
163
|
+
if (ctx) {
|
|
164
|
+
const idx = audioInstancePool.indexOf(ctx)
|
|
165
|
+
if (idx > -1) audioInstancePool.splice(idx, 1)
|
|
166
|
+
try { ctx.destroy() } catch (e) {}
|
|
167
|
+
ctx = null
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function toggle() {
|
|
172
|
+
if (!ctx) return
|
|
173
|
+
if (playing.value) {
|
|
174
|
+
ctx.pause()
|
|
175
|
+
} else {
|
|
176
|
+
ctx.play()
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// 拖动进度条中(只更新本地显示)
|
|
181
|
+
function onChanging(e) {
|
|
182
|
+
seeking.value = true
|
|
183
|
+
progress.value = e.detail.value
|
|
184
|
+
currentTime.value = duration.value ? (duration.value * e.detail.value) / 100 : 0
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// 拖动结束,真正 seek
|
|
188
|
+
function onChange(e) {
|
|
189
|
+
seeking.value = false
|
|
190
|
+
progress.value = e.detail.value
|
|
191
|
+
if (ctx && duration.value) {
|
|
192
|
+
try { ctx.seek((duration.value * e.detail.value) / 100) } catch (err) {}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function changeRate() {
|
|
197
|
+
const list = (props.rateList && props.rateList.length) ? props.rateList : [0.5, 0.75, 1, 1.25, 1.5, 2]
|
|
198
|
+
let i = list.indexOf(rate.value)
|
|
199
|
+
if (i === -1) i = list.indexOf(1)
|
|
200
|
+
rate.value = list[(i + 1) % list.length]
|
|
201
|
+
if (ctx) {
|
|
202
|
+
try { ctx.playbackRate = rate.value } catch (e) {}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// 加载失败:尝试下一个源;全部失败则抛 error
|
|
207
|
+
function handleError(err) {
|
|
208
|
+
if (srcIndex.value < srcList.value.length - 1) {
|
|
209
|
+
srcIndex.value += 1
|
|
210
|
+
currentTime.value = 0
|
|
211
|
+
progress.value = 0
|
|
212
|
+
if (ctx) {
|
|
213
|
+
ctx.src = currentSrc.value
|
|
214
|
+
try { ctx.play() } catch (e) {}
|
|
215
|
+
}
|
|
216
|
+
return
|
|
217
|
+
}
|
|
218
|
+
playing.value = false
|
|
219
|
+
errorMsg.value = (err && err.errMsg) || '音频加载失败'
|
|
220
|
+
emit('error', {
|
|
221
|
+
source: 'audio',
|
|
222
|
+
errMsg: errorMsg.value
|
|
223
|
+
})
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function formatTime(sec) {
|
|
227
|
+
sec = Math.floor(sec || 0)
|
|
228
|
+
const m = Math.floor(sec / 60)
|
|
229
|
+
const s = sec % 60
|
|
230
|
+
return m + ':' + (s < 10 ? '0' + s : s)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
defineExpose({
|
|
234
|
+
toggle,
|
|
235
|
+
reset,
|
|
236
|
+
changeRate
|
|
237
|
+
})
|
|
238
|
+
</script>
|
|
239
|
+
|
|
240
|
+
<style
|
|
241
|
+
lang="scss"
|
|
242
|
+
scoped
|
|
243
|
+
>
|
|
244
|
+
.up-audio {
|
|
245
|
+
display: flex;
|
|
246
|
+
align-items: center;
|
|
247
|
+
padding: 12rpx 16rpx;
|
|
248
|
+
margin: 8rpx 0;
|
|
249
|
+
border-radius: 8rpx;
|
|
250
|
+
background-color: #f5f7fa;
|
|
251
|
+
|
|
252
|
+
&__btn {
|
|
253
|
+
flex-shrink: 0;
|
|
254
|
+
width: 56rpx;
|
|
255
|
+
height: 56rpx;
|
|
256
|
+
border-radius: 50%;
|
|
257
|
+
display: flex;
|
|
258
|
+
align-items: center;
|
|
259
|
+
justify-content: center;
|
|
260
|
+
margin-right: 16rpx;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
&__btn-icon {
|
|
264
|
+
color: #ffffff;
|
|
265
|
+
font-size: 22rpx;
|
|
266
|
+
line-height: 1;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
&__main {
|
|
270
|
+
flex: 1;
|
|
271
|
+
min-width: 0;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
&__row {
|
|
275
|
+
display: flex;
|
|
276
|
+
align-items: center;
|
|
277
|
+
justify-content: space-between;
|
|
278
|
+
margin-bottom: 2rpx;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
&__title {
|
|
282
|
+
flex: 1;
|
|
283
|
+
font-size: 26rpx;
|
|
284
|
+
color: #303133;
|
|
285
|
+
overflow: hidden;
|
|
286
|
+
text-overflow: ellipsis;
|
|
287
|
+
white-space: nowrap;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
&__time {
|
|
291
|
+
flex-shrink: 0;
|
|
292
|
+
font-size: 22rpx;
|
|
293
|
+
color: #909399;
|
|
294
|
+
margin-left: 12rpx;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
&__slider {
|
|
298
|
+
margin: 0;
|
|
299
|
+
padding: 0;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
&__rate {
|
|
303
|
+
flex-shrink: 0;
|
|
304
|
+
margin-left: 16rpx;
|
|
305
|
+
padding: 4rpx 12rpx;
|
|
306
|
+
border-radius: 20rpx;
|
|
307
|
+
border: 1px solid #dcdfe6;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
&__rate-text {
|
|
311
|
+
font-size: 22rpx;
|
|
312
|
+
color: #606266;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
</style>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2019 wechat-miniprogram
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2015 Bodymovin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""生成 uvue 专用的 lottie-miniprogram 副本。
|
|
3
|
+
|
|
4
|
+
lottie-miniprogram 在**模块加载期**就会执行 `var b = wx.getSystemInfoSync()`,
|
|
5
|
+
而 uni-app x 没有 wx 全局,直接 import 会在启动时抛 ReferenceError。
|
|
6
|
+
这里在文件最前面补一个模块作用域的 wx 桩,后续压缩代码里的 `wx` 引用
|
|
7
|
+
会解析到这个局部变量(比挂 globalThis 更可靠,不依赖运行时全局对象)。
|
|
8
|
+
|
|
9
|
+
只补 getSystemInfoSync / request 两项:
|
|
10
|
+
- getSystemInfoSync:lottie 只用来取 pixelRatio(dpr 我们显式传 1)
|
|
11
|
+
- request:仅在按 path 加载远程 JSON 时使用,本组件只用 animationData
|
|
12
|
+
|
|
13
|
+
原始文件保持不动,供 .vue 侧(微信小程序/鸿蒙,有真实 wx)使用。
|
|
14
|
+
"""
|
|
15
|
+
import io
|
|
16
|
+
|
|
17
|
+
SRC = 'lottie-miniprogram.min.js'
|
|
18
|
+
DST = 'lottie-miniprogram.uvue.js'
|
|
19
|
+
|
|
20
|
+
SHIM = """/* ------------------------------------------------------------------
|
|
21
|
+
* uni-app x 适配层(本文件由 lottie-miniprogram.min.js 自动生成)
|
|
22
|
+
* lottie-miniprogram 在模块加载期即调用 wx.getSystemInfoSync(),
|
|
23
|
+
* 而 uni-app x 没有 wx 全局,故在此补一个模块作用域的 wx 桩。
|
|
24
|
+
* 下方代码与 lottie-miniprogram.min.js 完全一致,未做任何修改。
|
|
25
|
+
* ------------------------------------------------------------------ */
|
|
26
|
+
var wx = {
|
|
27
|
+
getSystemInfoSync: function () {
|
|
28
|
+
try {
|
|
29
|
+
var info = (typeof uni !== 'undefined' && uni.getSystemInfoSync) ? (uni.getSystemInfoSync() || {}) : {}
|
|
30
|
+
return { pixelRatio: info.pixelRatio || 1, platform: info.platform || '' }
|
|
31
|
+
} catch (e) {
|
|
32
|
+
return { pixelRatio: 1, platform: '' }
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
request: function () {}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
with io.open(SRC, 'rb') as f:
|
|
41
|
+
body = f.read()
|
|
42
|
+
|
|
43
|
+
with io.open(DST, 'wb') as f:
|
|
44
|
+
f.write(SHIM.encode('utf-8'))
|
|
45
|
+
f.write(body)
|
|
46
|
+
|
|
47
|
+
print('generated %s, size=%d' % (DST, len(SHIM.encode('utf-8')) + len(body)))
|