@aochuang/common 1.0.138 → 1.0.141

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.
@@ -81,25 +81,14 @@ export default {
81
81
  }
82
82
  },
83
83
  created () {
84
- const resolvePendingStart = () => {
85
- if (this._pendingStartResolve) {
86
- this._pendingStartResolve()
87
- this._pendingStartResolve = null
88
- }
89
- }
90
- const releasePlayEndPromise = () => {
91
- if (this.playEndPromise && this.playEndPromise.resolve) {
92
- this.playEndPromise.resolve()
93
- }
94
- }
95
84
  const newFactory = (Factory, options) => {
96
85
  return new Factory(Object.assign({
97
86
  playRate: this.playRate,
98
87
  onError: (e) => {
99
88
  this.starting = false
100
89
  this.playStatus = 'stop'
101
- resolvePendingStart()
102
- releasePlayEndPromise()
90
+ this._resolvePendingStart()
91
+ this._releasePlayEndPromise()
103
92
  this.$set(this.errorMsgMap, this.playId, e.message || '未知错误')
104
93
  this.$emit('error', Object.assign({
105
94
  playId: this.playId
@@ -178,9 +167,15 @@ export default {
178
167
  start (id, val, total, cb) {
179
168
  return new Promise((resolve) => {
180
169
  this.stop()
170
+ const startToken = (this._startToken || 0) + 1
171
+ this._startToken = startToken
181
172
  this._pendingStartResolve = resolve
182
173
  const playEndPromise = this.playEndPromise || Promise.resolve()
183
174
  playEndPromise.finally(() => {
175
+ if (this._startToken !== startToken) {
176
+ resolve()
177
+ return
178
+ }
184
179
  this.playEndPromise = createPromise()
185
180
  val = formatOssPath(val)
186
181
  this.starting = true
@@ -189,12 +184,16 @@ export default {
189
184
  this.$delete(this.errorMsgMap, id)
190
185
  this.$nextTick(() => {
191
186
  this.player.setUrl(val, (ctx) => {
187
+ if (this._startToken !== startToken) {
188
+ resolve()
189
+ return
190
+ }
192
191
  cb && cb(ctx)
193
192
  if (!ctx || ctx.status !== 'success') {
194
193
  this.starting = false
195
194
  this.playStatus = 'stop'
196
- resolvePendingStart()
197
- releasePlayEndPromise()
195
+ this._resolvePendingStart()
196
+ this._releasePlayEndPromise()
198
197
  resolve()
199
198
  return
200
199
  }
@@ -203,7 +202,11 @@ export default {
203
202
  Promise.all([
204
203
  this.player.start()
205
204
  ]).finally(() => {
206
- resolvePendingStart()
205
+ if (this._startToken !== startToken) {
206
+ resolve()
207
+ return
208
+ }
209
+ this._resolvePendingStart()
207
210
  resolve()
208
211
  })
209
212
  })
@@ -225,14 +228,12 @@ export default {
225
228
  this.player.resume()
226
229
  },
227
230
  stop () {
231
+ this._startToken = (this._startToken || 0) + 1
228
232
  this.starting = false
229
233
  this.playId = null
230
234
  this.playStatus = 'stop'
231
- if (this._pendingStartResolve) {
232
- this._pendingStartResolve()
233
- this._pendingStartResolve = null
234
- }
235
- this.player.stop()
235
+ this._resolvePendingStart()
236
+ this.player && this.player.stop()
236
237
  },
237
238
  isPlaying (id) {
238
239
  return this.playStatus === 'play' && this.playId === id
@@ -288,6 +289,17 @@ export default {
288
289
  this.stop()
289
290
  }
290
291
  this.$delete(this.errorMsgMap, id)
292
+ },
293
+ _resolvePendingStart () {
294
+ if (this._pendingStartResolve) {
295
+ this._pendingStartResolve()
296
+ this._pendingStartResolve = null
297
+ }
298
+ },
299
+ _releasePlayEndPromise () {
300
+ if (this.playEndPromise && this.playEndPromise.resolve) {
301
+ this.playEndPromise.resolve()
302
+ }
291
303
  }
292
304
  },
293
305
  render () {
@@ -287,6 +287,7 @@ export default {
287
287
  },
288
288
  playCurrPosition (val) {
289
289
  this.controlPlayPosition = val
290
+ this.$emit('progress-change', val)
290
291
  },
291
292
  url () {
292
293
  this.innerUrl = this.url
@@ -396,6 +397,69 @@ export default {
396
397
  }
397
398
  window.open(formatOssPath(this.voiceSrc), '_blank')
398
399
  },
400
+ setPlayPosition (position) {
401
+ let targetValue = parseFloat(position)
402
+ if (!Number.isFinite(targetValue)) {
403
+ return
404
+ }
405
+ const rawTargetValue = targetValue
406
+ if (this.voiceSeconds) {
407
+ targetValue = Math.max(0, Math.min(targetValue, Number(this.voiceSeconds)))
408
+ }
409
+ console.log('[vtt-sync] ac voice setPlayPosition', {
410
+ position,
411
+ rawTargetValue,
412
+ targetValue,
413
+ voiceSeconds: this.voiceSeconds,
414
+ controlPlayPosition: this.controlPlayPosition,
415
+ isPlaying: this.isPlaying,
416
+ isPause: this.isPause,
417
+ playUrl: this.AcVoice && this.AcVoice.playUrl,
418
+ innerUrl: this.innerUrl
419
+ })
420
+ clearTimeout(this._controlChangeTimer)
421
+ this.controlPlayPosition = targetValue
422
+ if (this.AcVoice.playUrl !== this.innerUrl) {
423
+ return Promise.all([
424
+ this.play()
425
+ ]).then(() => {
426
+ if (!this.AcVoice.isSuperChangeProgress(this.innerUrl)) {
427
+ !this._notTips && this.$warn('AAC格式音频不支持更改播放进度,可以下载本地播放', () => {
428
+ this._notTips = false
429
+ })
430
+ this._notTips = true
431
+ return
432
+ }
433
+ this._controlChangeTimer = setTimeout(() => {
434
+ this.AcVoice.setPosition(targetValue)
435
+ }, 100)
436
+ })
437
+ } else {
438
+ if (!this.isPlaying) {
439
+ return this.play().then(() => {
440
+ if (!this.AcVoice.isSuperChangeProgress(this.innerUrl)) {
441
+ !this._notTips && this.$warn('AAC格式音频不支持更改播放进度,可以下载本地播放', () => {
442
+ this._notTips = false
443
+ })
444
+ this._notTips = true
445
+ return
446
+ }
447
+ this._controlChangeTimer = setTimeout(() => {
448
+ this.AcVoice.setPosition(targetValue)
449
+ }, 100)
450
+ })
451
+ } else {
452
+ if (!this.AcVoice.isSuperChangeProgress(this.innerUrl)) {
453
+ !this._notTips && this.$warn('AAC格式音频不支持更改播放进度,可以下载本地播放', () => {
454
+ this._notTips = false
455
+ })
456
+ this._notTips = true
457
+ return
458
+ }
459
+ this.AcVoice.setPosition(targetValue)
460
+ }
461
+ }
462
+ },
399
463
  handleControlChange (evt) {
400
464
  const targetValue = evt.target.value
401
465
  clearTimeout(this._controlChangeTimer)
@@ -1,23 +1,76 @@
1
1
  <template>
2
- <video
3
- class="ui-video"
4
- :autoplay="autoplay"
5
- :width="width"
6
- :poster="poster"
7
- :preload="preload"
8
- :height="height"
9
- :controls="controls"
10
- @error="handleError"
2
+ <div
3
+ class="ui-video"
4
+ :class="{'is-poster-play-mode': posterPlayMode}"
5
+ :style="wrapperStyle"
11
6
  >
12
- <source :src="innerSrc">
13
- 您的浏览器不支持 video 标签。
14
- </video>
7
+ <template v-if="posterPlayMode">
8
+ <div
9
+ v-if="innerSrc"
10
+ class="ui-video__cover"
11
+ :class="{'is-empty': !innerPoster}"
12
+ :style="coverStyle"
13
+ @click="handlePlayClick"
14
+ >
15
+ <div class="ui-video__cover-mask"></div>
16
+ <div class="ui-video__cover-play">
17
+ <ui-icon name="play-line1" :size="24"></ui-icon>
18
+ </div>
19
+ </div>
20
+ <div v-else class="ui-video__poster-only" :style="coverStyle"></div>
21
+ <div v-if="showFloatingPlayer" ref="floatingLayer" class="ui-video__floating-layer" @click.self="handleCloseClick">
22
+ <div class="ui-video__floating-panel">
23
+ <div class="ui-video__floating-head">
24
+ <ui-icon class="ui-video__floating-close" name="close-circle-line" :size="28" @click="handleCloseClick"></ui-icon>
25
+ </div>
26
+ <div class="ui-video__floating-body">
27
+ <video
28
+ ref="floatingVideo"
29
+ class="ui-video__native"
30
+ :autoplay="autoplay"
31
+ :poster="poster"
32
+ :preload="preload"
33
+ :controls="controls"
34
+ playsinline
35
+ webkit-playsinline
36
+ @error="handleError"
37
+ >
38
+ <source :src="innerSrc">
39
+ 您的浏览器不支持 video 标签。
40
+ </video>
41
+ </div>
42
+ </div>
43
+ </div>
44
+ </template>
45
+ <template v-else>
46
+ <video
47
+ ref="nativeVideo"
48
+ class="ui-video__native"
49
+ :autoplay="autoplay"
50
+ :width="width"
51
+ :poster="poster"
52
+ :preload="preload"
53
+ :height="height"
54
+ :controls="controls"
55
+ playsinline
56
+ webkit-playsinline
57
+ @error="handleError"
58
+ >
59
+ <source :src="innerSrc">
60
+ 您的浏览器不支持 video 标签。
61
+ </video>
62
+ </template>
63
+ </div>
15
64
  </template>
16
65
  <script>
66
+ import UiIcon from '../icon'
17
67
  import { formatOssPath } from '../../utils/oss'
18
68
 
19
69
  export default {
20
70
  name: 'UiVideo',
71
+ components: {
72
+ UiIcon
73
+ },
21
74
  props: {
22
75
  width: {
23
76
  type: String
@@ -39,6 +92,10 @@ export default {
39
92
  type: Boolean,
40
93
  default: false
41
94
  },
95
+ posterPlayMode: {
96
+ type: Boolean,
97
+ default: false
98
+ },
42
99
  src: {
43
100
  type: String
44
101
  }
@@ -46,36 +103,92 @@ export default {
46
103
  computed: {
47
104
  style () {
48
105
  return {
49
- width: this.width,
50
- height: this.height
106
+ width: this.formatSize(this.width),
107
+ height: this.formatSize(this.height)
51
108
  }
52
109
  },
110
+ wrapperStyle () {
111
+ if (!this.posterPlayMode) {
112
+ return null
113
+ }
114
+ return {
115
+ width: this.formatSize(this.width),
116
+ height: this.formatSize(this.height)
117
+ }
118
+ },
119
+ innerPoster () {
120
+ return formatOssPath(this.poster)
121
+ },
122
+ coverStyle () {
123
+ const style = {
124
+ width: this.formatSize(this.width),
125
+ height: this.formatSize(this.height)
126
+ }
127
+ if (this.innerPoster) {
128
+ style.backgroundImage = `url(${this.innerPoster})`
129
+ }
130
+ return style
131
+ },
53
132
  innerSrc () {
54
133
  return formatOssPath(this.src)
55
134
  }
56
135
  },
57
136
  data () {
58
137
  return {
59
- isError: false
138
+ isError: false,
139
+ showFloatingPlayer: false
60
140
  }
61
141
  },
62
142
  watch: {
63
143
  src () {
144
+ clearTimeout(this._checkErrorTimer)
145
+ this._checkErrorTimer = null
64
146
  this.isError = false
147
+ this.showFloatingPlayer = false
148
+ if (!this.posterPlayMode) {
149
+ this.$nextTick(() => {
150
+ this.loopCheckError()
151
+ })
152
+ }
65
153
  }
66
154
  },
67
155
  mounted () {
68
- this.loopCheckError()
156
+ if (!this.posterPlayMode) {
157
+ this.loopCheckError()
158
+ }
69
159
  },
70
160
  methods: {
161
+ formatSize (value) {
162
+ if (!value && value !== 0) {
163
+ return null
164
+ }
165
+ if (typeof value === 'number') {
166
+ return `${value}px`
167
+ }
168
+ if (/^\d+$/.test(String(value))) {
169
+ return `${value}px`
170
+ }
171
+ return value
172
+ },
173
+ getVideoElement () {
174
+ if (this.posterPlayMode) {
175
+ return this.$refs.floatingVideo
176
+ }
177
+ return this.$refs.nativeVideo
178
+ },
71
179
  loopCheckError () {
72
180
  if (this.isError) {
73
181
  return
74
182
  }
75
183
  this._checkErrorTimer = setTimeout(() => {
184
+ const videoEl = this.getVideoElement()
185
+ if (!videoEl) {
186
+ this.loopCheckError()
187
+ return
188
+ }
76
189
  // 0:没有关于媒体资源的可用信息,或者媒体资源尚未加载。
77
190
  // 3: 媒体元素尝试加载资源,但没有找到有效的媒体源(可能是资源 URL 错误、403/404 等网络错误,或所有<source>标签指定的资源都无法加载)。
78
- if (this.$el.readyState === 0 && this.$el.networkState === 3) {
191
+ if (videoEl.readyState === 0 && videoEl.networkState === 3) {
79
192
  this.isError = true
80
193
  this.$emit('error')
81
194
  } else {
@@ -86,10 +199,48 @@ export default {
86
199
  handleError () {
87
200
  this.isError = true
88
201
  this.$emit('error')
202
+ },
203
+ appendFloatingLayer () {
204
+ const layerEl = this.$refs.floatingLayer
205
+ if (!layerEl || !document.body || layerEl.parentNode === document.body) {
206
+ return
207
+ }
208
+ document.body.appendChild(layerEl)
209
+ },
210
+ removeFloatingLayer () {
211
+ const layerEl = this.$refs.floatingLayer
212
+ if (!layerEl || layerEl.parentNode !== document.body) {
213
+ return
214
+ }
215
+ document.body.removeChild(layerEl)
216
+ },
217
+ handlePlayClick () {
218
+ this.showFloatingPlayer = true
219
+ this.$nextTick(() => {
220
+ this.appendFloatingLayer()
221
+ const videoEl = this.$refs.floatingVideo
222
+ if (!videoEl) {
223
+ return
224
+ }
225
+ if (!this._checkErrorTimer && !this.isError) {
226
+ this.loopCheckError()
227
+ }
228
+ if (videoEl.play) {
229
+ videoEl.play().catch(() => {})
230
+ }
231
+ })
232
+ },
233
+ handleCloseClick () {
234
+ clearTimeout(this._checkErrorTimer)
235
+ this._checkErrorTimer = null
236
+ this.removeFloatingLayer()
237
+ this.showFloatingPlayer = false
89
238
  }
90
239
  },
91
240
  beforeDestroy () {
92
241
  clearTimeout(this._checkErrorTimer)
242
+ this._checkErrorTimer = null
243
+ this.removeFloatingLayer()
93
244
  }
94
245
  }
95
246
  </script>
@@ -97,5 +248,95 @@ export default {
97
248
  .ui-video {
98
249
  background-color: #000;
99
250
  object-fit: contain;
251
+ position: relative;
252
+ z-index: 0;
253
+ display: block;
254
+ -webkit-transform: translateZ(0);
255
+ transform: translateZ(0);
256
+ }
257
+ .ui-video__native{
258
+ display: block;
259
+ max-width: 100%;
260
+ max-height: 100%;
261
+ background-color: #000;
262
+ object-fit: contain;
263
+ }
264
+ .ui-video.is-poster-play-mode{
265
+ overflow: hidden;
266
+ display: block;
267
+ }
268
+ .ui-video__cover{
269
+ position: relative;
270
+ background-color: #000;
271
+ background-position: center;
272
+ background-repeat: no-repeat;
273
+ background-size: cover;
274
+ cursor: pointer;
275
+ display: block;
276
+ }
277
+ .ui-video__cover.is-empty{
278
+ background-image: none;
279
+ }
280
+ .ui-video__poster-only{
281
+ background-color: #000;
282
+ background-position: center;
283
+ background-repeat: no-repeat;
284
+ background-size: cover;
285
+ display: block;
286
+ }
287
+ .ui-video__floating-layer{
288
+ position: fixed;
289
+ inset: 0;
290
+ z-index: 9999;
291
+ background-color: rgba(0,0,0,.72);
292
+ display: flex;
293
+ align-items: center;
294
+ justify-content: center;
295
+ padding: 24px 0;
296
+ box-sizing: border-box;
297
+ }
298
+ .ui-video__floating-panel{
299
+ width: 100%;
300
+ max-width: 960px;
301
+ border-radius: 4px;
302
+ overflow: hidden;
303
+ }
304
+ .ui-video__floating-head{
305
+ display: flex;
306
+ justify-content: flex-end;
307
+ padding: 8px 8px 0;
308
+ color: #fff;
309
+ }
310
+ .ui-video__floating-close{
311
+ cursor: pointer;
312
+ }
313
+ .ui-video__floating-body{
314
+ padding: 8px 0 12px;
315
+ }
316
+ .ui-video__floating-body .ui-video__native{
317
+ width: 100%;
318
+ height: auto;
319
+ max-width: 100%;
320
+ max-height: calc(100vh - 120px);
321
+ }
322
+ .ui-video__cover-mask{
323
+ position: absolute;
324
+ inset: 0;
325
+ background-color: rgba(0,0,0,.2);
326
+ }
327
+ .ui-video__cover-play{
328
+ position: absolute;
329
+ left: 50%;
330
+ top: 50%;
331
+ width: 44px;
332
+ height: 44px;
333
+ margin-left: -22px;
334
+ margin-top: -22px;
335
+ border-radius: 50%;
336
+ background-color: rgba(0,0,0,.55);
337
+ color: #fff;
338
+ display: flex;
339
+ align-items: center;
340
+ justify-content: center;
100
341
  }
101
342
  </style>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aochuang/common",
3
- "version": "1.0.138",
3
+ "version": "1.0.141",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "publishConfig": {