stellar-ui-v2 1.40.20 → 1.40.21

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.
@@ -1,756 +1,756 @@
1
- <template>
2
- <view class="ste-video-root" :class="cmpRootClass" :style="[cmpRootStyleVar]">
3
- <!-- #ifdef H5 | MP-WEIXIN -->
4
- <video
5
- class="ste-video"
6
- :id="id"
7
- :src="videoSrc"
8
- :controls="false"
9
- :show-center-play-btn="false"
10
- :poster="poster"
11
- :autoplay="autoplay"
12
- :loop="loop"
13
- :muted="isMuted"
14
- :initialTime="initialTime"
15
- :duration="duration"
16
- :enable-play-gesture="enablePlayGesture"
17
- :pageGesture="pageGesture"
18
- :direction="direction"
19
- :enableProgressGesture="enableProgressGesture"
20
- :objectFit="objectFit"
21
- :mobileNetHintType="mobileNetHintType"
22
- :autoPauseIfNavigate="autoPauseIfNavigate"
23
- :autoPauseIfOpenNative="autoPauseIfOpenNative"
24
- :vslideGesture="vslideGesture"
25
- :vslideGestureInFullscreen="vslideGestureInFullscreen"
26
- :adUnitId="adUnitId"
27
- :posterForCrawler="posterForCrawler"
28
- :codec="codec"
29
- :httpCache="httpCache"
30
- :playStrategy="playStrategy"
31
- :header="header"
32
- :isLive="isLive"
33
- @click="handleClick"
34
- @play="play"
35
- @pause="pause"
36
- @ended="ended"
37
- @timeupdate="timeupdate"
38
- @fullscreenchange="fullscreenchange"
39
- @waiting="waiting"
40
- @error="error"
41
- @progress="progress"
42
- @loadeddata="loadeddata"
43
- @loadstart="loadstart"
44
- @seeked="seeked"
45
- @seeking="seeking"
46
- @loadedmetadata="loadedMetaData"
47
- @fullscreenclick="fullscreenclick"
48
- >
49
- <!-- <view class="ste-video-custom-content"> -->
50
- <!-- 图片操作提示 -->
51
- <view class="overlay-box" v-if="isFull && !firstFullDone">
52
- <ste-image src="https://image.whzb.com/chain/StellarUI/video/overlay.png" mode="widthFix" width="100%" />
53
- </view>
54
- <!-- 顶部操作栏 -->
55
- <view
56
- class="cover top"
57
- :style="{
58
- transform: showControl ? 'translateY(0)' : 'translateY(-100%)',
59
- display: isFull ? 'flex' : 'none',
60
- }"
61
- @click="isClickControl = true"
62
- >
63
- <view @click="exitFullScreen">
64
- <ste-icon code="&#xe673;" size="40" color="#ffffff"></ste-icon>
65
- </view>
66
- <text class="title">{{ title }}</text>
67
- </view>
68
- <!-- 底部操作栏 -->
69
- <view class="cover bottom" :style="{ transform: showControl ? 'translateY(0)' : 'translateY(100%)' }" @click="isClickControl = true">
70
- <!-- 播放/暂停按钮 -->
71
- <view v-if="showPlayBtn">
72
- <ste-icon code="&#xe6a8;" size="36" color="#ffffff" v-if="!playState" @click="handlePlay(true)"></ste-icon>
73
- <ste-icon code="&#xe6ab;" size="36" color="#ffffff" v-else @click="handlePlay(false)"></ste-icon>
74
- </view>
75
- <!-- 静音按钮 -->
76
- <view class="muted-box">
77
- <!-- 非静音 -->
78
- <ste-icon code="&#xe6c2;" size="38" color="#ffffff" v-if="!isMuted" @click="triggerMuted"></ste-icon>
79
- <!-- 静音 -->
80
- <ste-icon code="&#xe6c3;" size="38" color="#ffffff" v-else @click="triggerMuted"></ste-icon>
81
- </view>
82
- <!-- 时间进度 -->
83
- <view class="time-box" v-if="isFull">
84
- <view class="time left">{{ formatTime(videoCurrent) }}</view>
85
- <view>/</view>
86
- <view class="time right">{{ formatTime(videoDuration) }}</view>
87
- </view>
88
- <!-- 进度条 -->
89
- <view class="progress-box" v-if="reRenderFlag">
90
- <ste-slider :value="playProgress" @change="handleProgressChange" barHeight="4" buttonSize="26">
91
- <view class="progress-bar" slot="button" />
92
- </ste-slider>
93
- </view>
94
- <!-- 时间进度 -->
95
- <view class="time-box" v-if="!isFull">
96
- <view class="time left">{{ formatTime(videoCurrent) }}</view>
97
- <view>/</view>
98
- <view class="time right">{{ formatTime(videoDuration) }}</view>
99
- </view>
100
- <template v-if="isFull">
101
- <view class="text-box resolution" @click="handleResolutionClick" v-if="resolution && resolution.length > 0">
102
- {{ resolution[resolutionIndex].text }}
103
- </view>
104
- <view class="text-box speed" @click="handleSpeedClick">
105
- {{ speedConfigArr[speedIndex] + 'X' }}
106
- </view>
107
- </template>
108
- <!-- 全屏按钮 -->
109
- <view v-if="showFullscreenBtn">
110
- <ste-icon code="&#xe6a9;" size="36" color="#ffffff" v-if="!isFull" @click="handleFull(true)"></ste-icon>
111
- <ste-icon code="&#xe6aa;" size="36" color="#ffffff" v-else @click="handleFull(false)"></ste-icon>
112
- </view>
113
- </view>
114
- <!-- 倍速、清晰度弹窗 -->
115
- <view class="popup-box" :style="{ transform: showPopup ? 'translateX(0)' : 'translateX(100%)' }" @click="handlePopupClick">
116
- <view class="item-box" :style="{ display: popupState == 1 ? 'flex' : 'none' }">
117
- <view class="choose-item" v-for="(e, index) in speedConfigArr" :key="index" @click="handleChooseItem(e, index)">
118
- <text class="text">{{ e + 'X' }}</text>
119
- <view v-if="speedIndex == index" class="check-icon">
120
- <ste-icon code="&#xe67a;" color="#fff" size="36"></ste-icon>
121
- </view>
122
- </view>
123
- </view>
124
- <view class="item-box" :style="{ display: popupState == 2 ? 'flex' : 'none' }">
125
- <view class="choose-item" v-for="(e, index) in resolution" :key="index" @click="handleChooseItem(e, index)">
126
- <text class="text">{{ e.text }}</text>
127
- <view v-if="resolutionIndex == index" class="check-icon">
128
- <ste-icon code="&#xe67a;" color="#fff" size="36"></ste-icon>
129
- </view>
130
- </view>
131
- </view>
132
- </view>
133
- <!-- 操作提示 -->
134
- <view class="tip-toast" :class="[{ show: showTip }]">
135
- <view class="tip-text">{{ msg }}</view>
136
- </view>
137
- <!-- </view> -->
138
- </video>
139
- <!-- #endif -->
140
- <!-- #ifdef MP-ALIPAY -->
141
- <video
142
- class="ste-video"
143
- :id="id"
144
- :src="src"
145
- controls
146
- :enable-play-gesture="true"
147
- :show-center-play-btn="true"
148
- :show-play-btn="true"
149
- object-fit="contain"
150
- :autoplay="false"
151
- :http-cache="true"
152
- :play-btn-position="'center'"
153
- />
154
- <!-- #endif -->
155
- <!-- #ifdef APP -->
156
- <view
157
- class="ste-video ste-video-renderjs-container"
158
- :id="id"
159
- :data-src="src"
160
- :data-poster="poster || ''"
161
- :data-autoplay="autoplay ? 'true' : 'false'"
162
- :data-loop="loop ? 'true' : 'false'"
163
- :data-muted="isMuted ? 'true' : 'false'"
164
- :data-object-fit="objectFit || 'contain'"
165
- ></view>
166
- <!-- #endif -->
167
- </view>
168
- </template>
169
-
170
- <script>
171
- /**
172
- * ste-video 视频
173
- * @description 视频组件
174
- * @tutorial https://stellar-ui.intecloud.com.cn/?projectName=stellar-ui&menu=%E7%BB%84%E4%BB%B6&active=ste-video
175
- */
176
- import utils from '../../utils/utils.js';
177
- import props from './props.js';
178
- export default {
179
- group: '展示组件',
180
- title: 'Video 视频',
181
- name: 'ste-video',
182
- mixins: [props],
183
- data() {
184
- return {
185
- reRenderFlag: true,
186
- id: 'video-' + utils.guid(),
187
- videoSrc: '',
188
- video: null, // video 上下文
189
- playState: false, // 标识播放状态
190
- isFull: false, // 标识是否全屏状态
191
- showControl: true,
192
- isClickControl: false, // 标识点击的时候是否点的操作栏
193
- playProgress: 0,
194
- videoDuration: 0, // 视频时长,单位s
195
- videoCurrent: 0, // 当前视频播放时长,单位s
196
- firstFullDone: false, // 标识是否全屏过,此时不再显示图片提示
197
- isClickImgTip: false, // 标识点击的时候是否点的图片提示
198
- showPopup: false,
199
- popupState: 1, // 标识当前是倍速还是清晰度 1 倍速,2 清晰度
200
- speedIndex: 2,
201
- resolutionIndex: 0,
202
- speedConfigArr: [0.5, 0.8, 1.0, 1.25, 1.5],
203
- showTip: false,
204
- msg: '',
205
- isMuted: this.muted,
206
- };
207
- },
208
- created() {
209
- if (this.src) {
210
- this.videoSrc = this.src;
211
- } else {
212
- this.videoSrc = this.resolution[this.resolutionIndex] ? this.resolution[this.resolutionIndex].url : '';
213
- }
214
- },
215
- mounted() {
216
- // #ifndef APP
217
- this.video = uni.createVideoContext(this.id, this);
218
- // #endif
219
- },
220
- watch: {
221
- muted: {
222
- handler(val) {
223
- this.isMuted = val;
224
- },
225
- immediate: true,
226
- },
227
- },
228
- computed: {
229
- cmpRootClass() {
230
- let classArr = [];
231
- if (this.isFull) {
232
- classArr.push('full');
233
- }
234
- if (this.autoHeight) {
235
- classArr.push('auto-height');
236
- }
237
-
238
- return classArr.join(' ');
239
- },
240
- cmpRootStyleVar() {
241
- let style = {
242
- '--control-height': this.isFull ? utils.formatPx(128) : utils.formatPx(88),
243
- // #ifndef H5 || WEB
244
- '--control-bottom-bar': utils.System.getNavbarBottom(),
245
- // #endif
246
- '--text-box-width': utils.formatPx(80),
247
- '--progress-bar-width': utils.formatPx(28),
248
- '--choose-item-font-size': utils.formatPx(28),
249
- '--check-icon-left': utils.formatPx(100),
250
- '--choose-item-gap': utils.formatPx(80),
251
- '--cover-font-size': utils.formatPx(28),
252
- '--title-padding-left': utils.formatPx(194),
253
- '--rpx-to-px-20': utils.formatPx(20),
254
- '--rpx-to-px-24': utils.formatPx(24),
255
- '--rpx-to-px-40': utils.formatPx(40),
256
- '--rpx-to-px-32': utils.formatPx(32),
257
- '--rpx-to-px-46': utils.formatPx(46),
258
- '--rpx-to-px-16': utils.formatPx(16),
259
- '--rpx-to-px-40': utils.formatPx(40),
260
- '--rpx-to-px-36': utils.formatPx(36),
261
- };
262
- return style;
263
- },
264
- },
265
- methods: {
266
- thro: utils.thro,
267
-
268
- handlePlay(state) {
269
- if (state) {
270
- // 播放
271
- this.video.play();
272
- } else {
273
- this.video.pause();
274
- }
275
- },
276
- handleFull(state) {
277
- if (state) {
278
- this.video.requestFullScreen({
279
- direction: 90,
280
- });
281
- } else {
282
- this.firstFullDone = true;
283
- this.video.exitFullScreen();
284
- }
285
-
286
- this.reRenderFlag = false;
287
- setTimeout(() => {
288
- this.reRenderFlag = true;
289
- }, 200);
290
- },
291
- exitFullScreen() {
292
- this.showPopup = false;
293
- this.video.exitFullScreen();
294
-
295
- this.reRenderFlag = false;
296
- setTimeout(() => {
297
- this.reRenderFlag = true;
298
- }, 200);
299
- },
300
- handleProgressChange(v) {
301
- this.videoCurrent = this.videoDuration * (v / 100);
302
- this.video.seek(this.videoCurrent);
303
- },
304
- // 由于video的点击事件会导致其他点击事件也触发,所以通过一些标识符判断来触发不同逻辑
305
- handleClick() {
306
- // 此时全屏并点击了图片提示
307
- if (this.isFull && !this.firstFullDone) {
308
- this.firstFullDone = true;
309
- return;
310
- }
311
-
312
- if (!this.isClickControl && !this.isClickImgTip) {
313
- // 此时点击了视频空白区域
314
- if (this.showPopup) {
315
- this.showPopup = false;
316
- return;
317
- }
318
- this.showControl = !this.showControl;
319
- this.$emit('controlstoggle', this.showControl);
320
- } else {
321
- this.isClickControl = false;
322
- this.isClickImgTip = false;
323
- }
324
- },
325
- handleSpeedClick() {
326
- this.popupState = 1;
327
- this.showPopup = true;
328
- },
329
- handleResolutionClick() {
330
- this.popupState = 2;
331
- this.showPopup = true;
332
- },
333
- handlePopupClick() {
334
- this.isClickControl = true;
335
- },
336
- handleChooseItem(e, index) {
337
- let msg = '';
338
- if (this.popupState == 1) {
339
- if (this.speedIndex == index) return;
340
- this.speedIndex = index;
341
- this.video.playbackRate(this.speedConfigArr[this.speedIndex]);
342
- msg = e + 'X';
343
- } else {
344
- if (this.resolutionIndex == index) return;
345
- this.resolutionIndex = index;
346
- this.handlePlay(false);
347
- this.videoSrc = this.resolution[this.resolutionIndex].url;
348
- setTimeout(() => {
349
- this.video.seek(this.videoCurrent);
350
- this.handlePlay(true);
351
- }, 200);
352
- msg = `切换${e.text}成功`;
353
- }
354
-
355
- setTimeout(() => {
356
- this.showPopup = false;
357
- this.tip(msg);
358
- }, 20);
359
- },
360
- // 监听video事件
361
-
362
- play() {
363
- this.playState = true;
364
- },
365
- pause() {
366
- this.playState = false;
367
- },
368
- ended(e) {
369
- this.$emit('ended', e);
370
- },
371
- timeupdate(e) {
372
- this.videoDuration = e.detail.duration;
373
- this.videoCurrent = e.detail.currentTime;
374
- this.playProgress = (this.videoCurrent / this.videoDuration) * 100;
375
- },
376
- fullscreenchange(e) {
377
- this.isFull = e.detail.fullScreen;
378
- if (this.isFull) {
379
- this.video.hideStatusBar();
380
- } else {
381
- this.video.showStatusBar();
382
- }
383
- },
384
- waiting(e) {
385
- this.$emit('waiting', e);
386
- },
387
- error(e) {
388
- this.$emit('error', e);
389
- },
390
- progress(e) {
391
- this.$emit('progress', e);
392
- },
393
- loadeddata(e) {
394
- this.$emit('loadeddata', e);
395
- },
396
- loadstart(e) {
397
- this.$emit('loadstart', e);
398
- },
399
- seeked(e) {
400
- this.$emit('seeked', e);
401
- },
402
- seeking(e) {
403
- this.$emit('seeking', e);
404
- },
405
- loadedMetaData(e) {
406
- this.videoDuration = e.detail.duration;
407
- },
408
- fullscreenclick(e) {
409
- this.$emit('fullscreenclick', e);
410
- },
411
- // 监听video事件 end
412
- //
413
- // 格式化视频时间为时分秒
414
- formatTime(seconds) {
415
- let hours = Math.floor(seconds / 3600);
416
- let minutes = Math.floor((seconds % 3600) / 60);
417
- let remainingSeconds = Math.floor(seconds % 60);
418
-
419
- let formattedHours = hours > 0 ? (hours < 10 ? '0' + hours : hours) : '';
420
- let formattedMinutes = minutes < 10 ? '0' + minutes : minutes;
421
- let formattedSeconds = remainingSeconds < 10 ? '0' + remainingSeconds : remainingSeconds;
422
-
423
- if (formattedHours !== '') {
424
- return formattedHours + ':' + formattedMinutes + ':' + formattedSeconds;
425
- } else {
426
- return formattedMinutes + ':' + formattedSeconds;
427
- }
428
- },
429
- tip(msg) {
430
- if (!msg) return;
431
- this.msg = msg;
432
- this.showTip = true;
433
- setTimeout(() => {
434
- this.showTip = false;
435
- }, 1500);
436
- },
437
- triggerMuted() {
438
- this.isMuted = !this.isMuted;
439
- },
440
- },
441
- };
442
- </script>
443
-
444
- <!-- #ifdef H5 || APP-PLUS -->
445
- <script module="videoRender" lang="renderjs">
446
- export default {
447
- data() {
448
- return {
449
- rvVideo: null,
450
- isInitialized: false,
451
- isInitCapture: false,
452
- };
453
- },
454
- mounted() {
455
- this.initVideo();
456
- },
457
- methods: {
458
- initVideo() {
459
- if (this.isInitialized || !this.$el) return;
460
- // 只在 APP 端执行,H5 端无此容器
461
- const container = this.$el.querySelector('.ste-video-renderjs-container');
462
- if (!container) return;
463
-
464
- this.isInitialized = true;
465
-
466
- // 从 data 属性读取配置,避免跨层访问 $vm 的兼容性问题
467
- const ds = container.dataset;
468
-
469
- const canvas = document.createElement('canvas');
470
- canvas.width = 1;
471
- canvas.height = 1;
472
- const ctx = canvas.getContext('2d');
473
- ctx.fillStyle = '#000000';
474
- ctx.fillRect(0, 0, 1, 1);
475
- const blackPoster = canvas.toDataURL('image/png');
476
-
477
- const video = document.createElement('video');
478
- video.src = ds.src;
479
- video.controls = true;
480
- video.autoplay = ds.autoplay === 'true';
481
- video.loop = ds.loop === 'true';
482
- video.muted = ds.muted === 'true';
483
- video.poster = ds.poster || blackPoster;
484
- video.playsInline = true;
485
- video.style.width = '100%';
486
- video.style.height = '225px';
487
- video.style.maxWidth = '100%';
488
- video.style.display = 'block';
489
- video.style.background = '#000';
490
- video.style.objectFit = ds.objectFit || 'contain';
491
- // video.style.objectFit = 'cover';
492
- video.setAttribute('playsinline', '');
493
- video.setAttribute('webkit-playsinline', '');
494
- video.setAttribute('x5-playsinline', '');
495
- video.setAttribute('x5-video-player-type', 'h5');
496
-
497
- const ownerVm = this.$ownerInstance;
498
- video.addEventListener('play', () => {
499
- if (this.isInitCapture) return;
500
- ownerVm.callMethod('play');
501
- });
502
- video.addEventListener('pause', () => {
503
- if (this.isInitCapture) return;
504
- ownerVm.callMethod('pause');
505
- });
506
- video.addEventListener('ended', () => ownerVm.callMethod('ended', { detail: {} }));
507
- video.addEventListener('timeupdate', () => {
508
- ownerVm.callMethod('timeupdate', {
509
- detail: {
510
- currentTime: video.currentTime,
511
- duration: video.duration || 0,
512
- },
513
- });
514
- });
515
- video.addEventListener('error', () => ownerVm.callMethod('error', { detail: {} }));
516
- video.addEventListener('loadedmetadata', () => {
517
- ownerVm.callMethod('loadedMetaData', {
518
- detail: { duration: video.duration },
519
- });
520
- });
521
- video.addEventListener('waiting', () => ownerVm.callMethod('waiting', { detail: {} }));
522
- video.addEventListener('loadeddata', () => {
523
- ownerVm.callMethod('loadeddata', { detail: {} });
524
- if (!ds.autoplay || ds.autoplay !== 'true') {
525
- this.isInitCapture = true;
526
- const p = video.play();
527
- if (p !== undefined) {
528
- setTimeout(() => {
529
- p.then(() => {
530
- video.pause();
531
- video.currentTime = 0;
532
- this.isInitCapture = false;
533
- }).catch(() => {
534
- this.isInitCapture = false;
535
- });
536
- }, 100)
537
- } else {
538
- this.isInitCapture = false;
539
- }
540
- }
541
- });
542
- video.addEventListener('loadstart', () => ownerVm.callMethod('loadstart', { detail: {} }));
543
- video.addEventListener('seeked', () => ownerVm.callMethod('seeked', { detail: {} }));
544
- video.addEventListener('seeking', () => ownerVm.callMethod('seeking', { detail: {} }));
545
-
546
- container.appendChild(video);
547
- this.rvVideo = video;
548
- },
549
- },
550
- };
551
- </script>
552
- <!-- #endif -->
553
-
554
- <style lang="scss" scoped>
555
- .ste-video-root {
556
- position: relative;
557
- overflow: hidden;
558
- width: 100%;
559
-
560
- .ste-video {
561
- width: 100%;
562
- display: block;
563
- }
564
-
565
- .ste-video-custom-content {
566
- width: 100%;
567
- height: 100%;
568
- pointer-events: auto;
569
- }
570
-
571
- &.auto-height {
572
- height: 100%;
573
-
574
- .ste-video {
575
- height: 100%;
576
- }
577
- }
578
-
579
- &.full {
580
- .cover {
581
- position: fixed;
582
-
583
- padding: 0 var(--rpx-to-px-24);
584
-
585
- &.top {
586
- background: linear-gradient(0, rgba(0, 0, 0, 0) 0%, #000000 100%, #000000 100%);
587
- }
588
-
589
- &.bottom {
590
- background: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, #000000 100%);
591
-
592
- padding: 0 var(--rpx-to-px-40);
593
-
594
- .progress-box {
595
- padding-left: 0;
596
- padding-right: var(--rpx-to-px-32);
597
- }
598
-
599
- .text-box {
600
- padding-right: var(--rpx-to-px-32);
601
- }
602
-
603
- .time-box {
604
- padding-right: var(--rpx-to-px-46) !important;
605
- padding-left: var(--rpx-to-px-32);
606
- }
607
- }
608
- }
609
- }
610
-
611
- .overlay-box {
612
- width: 100%;
613
- position: fixed;
614
- left: 0;
615
- top: 50%;
616
- transform: translateY(-50%);
617
- z-index: 999;
618
- }
619
-
620
- .cover {
621
- position: absolute;
622
- line-height: 1;
623
- width: 100%;
624
- transition: transform 0.3s ease;
625
- display: flex;
626
- align-items: center;
627
- padding: 0 var(--rpx-to-px-16);
628
- height: var(--control-height);
629
- pointer-events: auto;
630
- color: #ffffff;
631
- font-size: var(--cover-font-size);
632
-
633
- &.top {
634
- top: 0;
635
- background: linear-gradient(180deg, rgba(0, 0, 0, 0.5) 5%, rgba(255, 255, 255, 0) 100%);
636
-
637
- padding-left: var(--title-padding-left);
638
-
639
- .title {
640
- margin-left: 16rpx;
641
- font-size: var(--rpx-to-px-36);
642
- }
643
- }
644
-
645
- &.bottom {
646
- bottom: 0;
647
- // background: linear-gradient(0, rgba(0, 0, 0, 0.5) 5%, rgba(255, 255, 255, 0) 100%);
648
- background-color: rgba(0, 0, 0, 0.6);
649
-
650
- display: flex;
651
- justify-content: space-between;
652
-
653
- .muted-box {
654
- padding-left: var(--rpx-to-px-16);
655
- }
656
-
657
- .progress-box {
658
- flex: 1;
659
- padding: 0 var(--rpx-to-px-32);
660
-
661
- .progress-bar {
662
- width: var(--progress-bar-width);
663
- height: var(--progress-bar-width);
664
- border-radius: 50%;
665
- background-color: #ffffff;
666
- }
667
- }
668
-
669
- .time-box {
670
- display: flex;
671
- align-items: center;
672
- overflow: hidden;
673
- padding-right: var(--rpx-to-px-20);
674
-
675
- .time {
676
- width: var(--text-box-width);
677
- overflow: hidden;
678
-
679
- &.right {
680
- text-align: right;
681
- }
682
- }
683
- }
684
- }
685
- }
686
-
687
- .popup-box {
688
- height: 100vh;
689
- width: 24.6vw;
690
- position: absolute;
691
- pointer-events: auto;
692
- right: 0;
693
- top: 0;
694
- transition: transform 0.3s ease;
695
- background-color: rgba(0, 0, 0, 0.8);
696
-
697
- line-height: 1;
698
-
699
- .item-box {
700
- line-height: 1;
701
- display: flex;
702
- flex-direction: column;
703
- justify-content: center;
704
- align-items: center;
705
- gap: var(--choose-item-gap);
706
-
707
- width: 100%;
708
- height: 100%;
709
- }
710
-
711
- .choose-item {
712
- color: #ffffff;
713
- position: relative;
714
- width: 100%;
715
- text-align: center;
716
-
717
- .text {
718
- font-size: var(--choose-item-font-size);
719
- }
720
-
721
- .check-icon {
722
- display: flex;
723
- position: absolute;
724
- right: var(--check-icon-left);
725
- top: -1px;
726
- }
727
- }
728
- }
729
-
730
- .tip-toast {
731
- background-color: rgba(0, 0, 0, 0.7);
732
- border-radius: 16rpx;
733
- padding: 10px;
734
-
735
- position: absolute;
736
- left: 50%;
737
- top: 50%;
738
- display: flex;
739
- align-items: center;
740
- justify-content: center;
741
- transform: translate(-50%, -50%);
742
- transition: all 0.2s;
743
- visibility: hidden;
744
-
745
- &.show {
746
- visibility: visible;
747
- }
748
-
749
- .tip-text {
750
- color: #ffffff;
751
- font-size: 14px;
752
- line-height: 1;
753
- }
754
- }
755
- }
756
- </style>
1
+ <template>
2
+ <view class="ste-video-root" :class="cmpRootClass" :style="[cmpRootStyleVar]">
3
+ <!-- #ifdef H5 | MP-WEIXIN -->
4
+ <video
5
+ class="ste-video"
6
+ :id="id"
7
+ :src="videoSrc"
8
+ :controls="false"
9
+ :show-center-play-btn="false"
10
+ :poster="poster"
11
+ :autoplay="autoplay"
12
+ :loop="loop"
13
+ :muted="isMuted"
14
+ :initialTime="initialTime"
15
+ :duration="duration"
16
+ :enable-play-gesture="enablePlayGesture"
17
+ :pageGesture="pageGesture"
18
+ :direction="direction"
19
+ :enableProgressGesture="enableProgressGesture"
20
+ :objectFit="objectFit"
21
+ :mobileNetHintType="mobileNetHintType"
22
+ :autoPauseIfNavigate="autoPauseIfNavigate"
23
+ :autoPauseIfOpenNative="autoPauseIfOpenNative"
24
+ :vslideGesture="vslideGesture"
25
+ :vslideGestureInFullscreen="vslideGestureInFullscreen"
26
+ :adUnitId="adUnitId"
27
+ :posterForCrawler="posterForCrawler"
28
+ :codec="codec"
29
+ :httpCache="httpCache"
30
+ :playStrategy="playStrategy"
31
+ :header="header"
32
+ :isLive="isLive"
33
+ @click="handleClick"
34
+ @play="play"
35
+ @pause="pause"
36
+ @ended="ended"
37
+ @timeupdate="timeupdate"
38
+ @fullscreenchange="fullscreenchange"
39
+ @waiting="waiting"
40
+ @error="error"
41
+ @progress="progress"
42
+ @loadeddata="loadeddata"
43
+ @loadstart="loadstart"
44
+ @seeked="seeked"
45
+ @seeking="seeking"
46
+ @loadedmetadata="loadedMetaData"
47
+ @fullscreenclick="fullscreenclick"
48
+ >
49
+ <!-- <view class="ste-video-custom-content"> -->
50
+ <!-- 图片操作提示 -->
51
+ <view class="overlay-box" v-if="isFull && !firstFullDone">
52
+ <ste-image src="https://image.whzb.com/chain/StellarUI/video/overlay.png" mode="widthFix" width="100%" />
53
+ </view>
54
+ <!-- 顶部操作栏 -->
55
+ <view
56
+ class="cover top"
57
+ :style="{
58
+ transform: showControl ? 'translateY(0)' : 'translateY(-100%)',
59
+ display: isFull ? 'flex' : 'none',
60
+ }"
61
+ @click="isClickControl = true"
62
+ >
63
+ <view @click="exitFullScreen">
64
+ <ste-icon code="&#xe673;" size="40" color="#ffffff"></ste-icon>
65
+ </view>
66
+ <text class="title">{{ title }}</text>
67
+ </view>
68
+ <!-- 底部操作栏 -->
69
+ <view class="cover bottom" :style="{ transform: showControl ? 'translateY(0)' : 'translateY(100%)' }" @click="isClickControl = true">
70
+ <!-- 播放/暂停按钮 -->
71
+ <view v-if="showPlayBtn">
72
+ <ste-icon code="&#xe6a8;" size="36" color="#ffffff" v-if="!playState" @click="handlePlay(true)"></ste-icon>
73
+ <ste-icon code="&#xe6ab;" size="36" color="#ffffff" v-else @click="handlePlay(false)"></ste-icon>
74
+ </view>
75
+ <!-- 静音按钮 -->
76
+ <view class="muted-box">
77
+ <!-- 非静音 -->
78
+ <ste-icon code="&#xe6c2;" size="38" color="#ffffff" v-if="!isMuted" @click="triggerMuted"></ste-icon>
79
+ <!-- 静音 -->
80
+ <ste-icon code="&#xe6c3;" size="38" color="#ffffff" v-else @click="triggerMuted"></ste-icon>
81
+ </view>
82
+ <!-- 时间进度 -->
83
+ <view class="time-box" v-if="isFull">
84
+ <view class="time left">{{ formatTime(videoCurrent) }}</view>
85
+ <view>/</view>
86
+ <view class="time right">{{ formatTime(videoDuration) }}</view>
87
+ </view>
88
+ <!-- 进度条 -->
89
+ <view class="progress-box" v-if="reRenderFlag">
90
+ <ste-slider :value="playProgress" @change="handleProgressChange" barHeight="4" buttonSize="26">
91
+ <view class="progress-bar" slot="button" />
92
+ </ste-slider>
93
+ </view>
94
+ <!-- 时间进度 -->
95
+ <view class="time-box" v-if="!isFull">
96
+ <view class="time left">{{ formatTime(videoCurrent) }}</view>
97
+ <view>/</view>
98
+ <view class="time right">{{ formatTime(videoDuration) }}</view>
99
+ </view>
100
+ <template v-if="isFull">
101
+ <view class="text-box resolution" @click="handleResolutionClick" v-if="resolution && resolution.length > 0">
102
+ {{ resolution[resolutionIndex].text }}
103
+ </view>
104
+ <view class="text-box speed" @click="handleSpeedClick">
105
+ {{ speedConfigArr[speedIndex] + 'X' }}
106
+ </view>
107
+ </template>
108
+ <!-- 全屏按钮 -->
109
+ <view v-if="showFullscreenBtn">
110
+ <ste-icon code="&#xe6a9;" size="36" color="#ffffff" v-if="!isFull" @click="handleFull(true)"></ste-icon>
111
+ <ste-icon code="&#xe6aa;" size="36" color="#ffffff" v-else @click="handleFull(false)"></ste-icon>
112
+ </view>
113
+ </view>
114
+ <!-- 倍速、清晰度弹窗 -->
115
+ <view class="popup-box" :style="{ transform: showPopup ? 'translateX(0)' : 'translateX(100%)' }" @click="handlePopupClick">
116
+ <view class="item-box" :style="{ display: popupState == 1 ? 'flex' : 'none' }">
117
+ <view class="choose-item" v-for="(e, index) in speedConfigArr" :key="index" @click="handleChooseItem(e, index)">
118
+ <text class="text">{{ e + 'X' }}</text>
119
+ <view v-if="speedIndex == index" class="check-icon">
120
+ <ste-icon code="&#xe67a;" color="#fff" size="36"></ste-icon>
121
+ </view>
122
+ </view>
123
+ </view>
124
+ <view class="item-box" :style="{ display: popupState == 2 ? 'flex' : 'none' }">
125
+ <view class="choose-item" v-for="(e, index) in resolution" :key="index" @click="handleChooseItem(e, index)">
126
+ <text class="text">{{ e.text }}</text>
127
+ <view v-if="resolutionIndex == index" class="check-icon">
128
+ <ste-icon code="&#xe67a;" color="#fff" size="36"></ste-icon>
129
+ </view>
130
+ </view>
131
+ </view>
132
+ </view>
133
+ <!-- 操作提示 -->
134
+ <view class="tip-toast" :class="[{ show: showTip }]">
135
+ <view class="tip-text">{{ msg }}</view>
136
+ </view>
137
+ <!-- </view> -->
138
+ </video>
139
+ <!-- #endif -->
140
+ <!-- #ifdef MP-ALIPAY -->
141
+ <video
142
+ class="ste-video"
143
+ :id="id"
144
+ :src="src"
145
+ controls
146
+ :enable-play-gesture="true"
147
+ :show-center-play-btn="true"
148
+ :show-play-btn="true"
149
+ object-fit="contain"
150
+ :autoplay="false"
151
+ :http-cache="true"
152
+ :play-btn-position="'center'"
153
+ />
154
+ <!-- #endif -->
155
+ <!-- #ifdef APP -->
156
+ <view
157
+ class="ste-video ste-video-renderjs-container"
158
+ :id="id"
159
+ :data-src="src"
160
+ :data-poster="poster || ''"
161
+ :data-autoplay="autoplay ? 'true' : 'false'"
162
+ :data-loop="loop ? 'true' : 'false'"
163
+ :data-muted="isMuted ? 'true' : 'false'"
164
+ :data-object-fit="objectFit || 'contain'"
165
+ ></view>
166
+ <!-- #endif -->
167
+ </view>
168
+ </template>
169
+
170
+ <script>
171
+ /**
172
+ * ste-video 视频
173
+ * @description 视频组件
174
+ * @tutorial https://stellar-ui.intecloud.com.cn/?projectName=stellar-ui&menu=%E7%BB%84%E4%BB%B6&active=ste-video
175
+ */
176
+ import utils from '../../utils/utils.js';
177
+ import props from './props.js';
178
+ export default {
179
+ group: '展示组件',
180
+ title: 'Video 视频',
181
+ name: 'ste-video',
182
+ mixins: [props],
183
+ data() {
184
+ return {
185
+ reRenderFlag: true,
186
+ id: 'video-' + utils.guid(),
187
+ videoSrc: '',
188
+ video: null, // video 上下文
189
+ playState: false, // 标识播放状态
190
+ isFull: false, // 标识是否全屏状态
191
+ showControl: true,
192
+ isClickControl: false, // 标识点击的时候是否点的操作栏
193
+ playProgress: 0,
194
+ videoDuration: 0, // 视频时长,单位s
195
+ videoCurrent: 0, // 当前视频播放时长,单位s
196
+ firstFullDone: false, // 标识是否全屏过,此时不再显示图片提示
197
+ isClickImgTip: false, // 标识点击的时候是否点的图片提示
198
+ showPopup: false,
199
+ popupState: 1, // 标识当前是倍速还是清晰度 1 倍速,2 清晰度
200
+ speedIndex: 2,
201
+ resolutionIndex: 0,
202
+ speedConfigArr: [0.5, 0.8, 1.0, 1.25, 1.5],
203
+ showTip: false,
204
+ msg: '',
205
+ isMuted: this.muted,
206
+ };
207
+ },
208
+ created() {
209
+ if (this.src) {
210
+ this.videoSrc = this.src;
211
+ } else {
212
+ this.videoSrc = this.resolution[this.resolutionIndex] ? this.resolution[this.resolutionIndex].url : '';
213
+ }
214
+ },
215
+ mounted() {
216
+ // #ifndef APP
217
+ this.video = uni.createVideoContext(this.id, this);
218
+ // #endif
219
+ },
220
+ watch: {
221
+ muted: {
222
+ handler(val) {
223
+ this.isMuted = val;
224
+ },
225
+ immediate: true,
226
+ },
227
+ },
228
+ computed: {
229
+ cmpRootClass() {
230
+ let classArr = [];
231
+ if (this.isFull) {
232
+ classArr.push('full');
233
+ }
234
+ if (this.autoHeight) {
235
+ classArr.push('auto-height');
236
+ }
237
+
238
+ return classArr.join(' ');
239
+ },
240
+ cmpRootStyleVar() {
241
+ let style = {
242
+ '--control-height': this.isFull ? utils.formatPx(128) : utils.formatPx(88),
243
+ // #ifndef H5 || WEB
244
+ '--control-bottom-bar': utils.System.getNavbarBottom(),
245
+ // #endif
246
+ '--text-box-width': utils.formatPx(80),
247
+ '--progress-bar-width': utils.formatPx(28),
248
+ '--choose-item-font-size': utils.formatPx(28),
249
+ '--check-icon-left': utils.formatPx(100),
250
+ '--choose-item-gap': utils.formatPx(80),
251
+ '--cover-font-size': utils.formatPx(28),
252
+ '--title-padding-left': utils.formatPx(194),
253
+ '--rpx-to-px-20': utils.formatPx(20),
254
+ '--rpx-to-px-24': utils.formatPx(24),
255
+ '--rpx-to-px-40': utils.formatPx(40),
256
+ '--rpx-to-px-32': utils.formatPx(32),
257
+ '--rpx-to-px-46': utils.formatPx(46),
258
+ '--rpx-to-px-16': utils.formatPx(16),
259
+ '--rpx-to-px-40': utils.formatPx(40),
260
+ '--rpx-to-px-36': utils.formatPx(36),
261
+ };
262
+ return style;
263
+ },
264
+ },
265
+ methods: {
266
+ thro: utils.thro,
267
+
268
+ handlePlay(state) {
269
+ if (state) {
270
+ // 播放
271
+ this.video.play();
272
+ } else {
273
+ this.video.pause();
274
+ }
275
+ },
276
+ handleFull(state) {
277
+ if (state) {
278
+ this.video.requestFullScreen({
279
+ direction: 90,
280
+ });
281
+ } else {
282
+ this.firstFullDone = true;
283
+ this.video.exitFullScreen();
284
+ }
285
+
286
+ this.reRenderFlag = false;
287
+ setTimeout(() => {
288
+ this.reRenderFlag = true;
289
+ }, 200);
290
+ },
291
+ exitFullScreen() {
292
+ this.showPopup = false;
293
+ this.video.exitFullScreen();
294
+
295
+ this.reRenderFlag = false;
296
+ setTimeout(() => {
297
+ this.reRenderFlag = true;
298
+ }, 200);
299
+ },
300
+ handleProgressChange(v) {
301
+ this.videoCurrent = this.videoDuration * (v / 100);
302
+ this.video.seek(this.videoCurrent);
303
+ },
304
+ // 由于video的点击事件会导致其他点击事件也触发,所以通过一些标识符判断来触发不同逻辑
305
+ handleClick() {
306
+ // 此时全屏并点击了图片提示
307
+ if (this.isFull && !this.firstFullDone) {
308
+ this.firstFullDone = true;
309
+ return;
310
+ }
311
+
312
+ if (!this.isClickControl && !this.isClickImgTip) {
313
+ // 此时点击了视频空白区域
314
+ if (this.showPopup) {
315
+ this.showPopup = false;
316
+ return;
317
+ }
318
+ this.showControl = !this.showControl;
319
+ this.$emit('controlstoggle', this.showControl);
320
+ } else {
321
+ this.isClickControl = false;
322
+ this.isClickImgTip = false;
323
+ }
324
+ },
325
+ handleSpeedClick() {
326
+ this.popupState = 1;
327
+ this.showPopup = true;
328
+ },
329
+ handleResolutionClick() {
330
+ this.popupState = 2;
331
+ this.showPopup = true;
332
+ },
333
+ handlePopupClick() {
334
+ this.isClickControl = true;
335
+ },
336
+ handleChooseItem(e, index) {
337
+ let msg = '';
338
+ if (this.popupState == 1) {
339
+ if (this.speedIndex == index) return;
340
+ this.speedIndex = index;
341
+ this.video.playbackRate(this.speedConfigArr[this.speedIndex]);
342
+ msg = e + 'X';
343
+ } else {
344
+ if (this.resolutionIndex == index) return;
345
+ this.resolutionIndex = index;
346
+ this.handlePlay(false);
347
+ this.videoSrc = this.resolution[this.resolutionIndex].url;
348
+ setTimeout(() => {
349
+ this.video.seek(this.videoCurrent);
350
+ this.handlePlay(true);
351
+ }, 200);
352
+ msg = `切换${e.text}成功`;
353
+ }
354
+
355
+ setTimeout(() => {
356
+ this.showPopup = false;
357
+ this.tip(msg);
358
+ }, 20);
359
+ },
360
+ // 监听video事件
361
+
362
+ play() {
363
+ this.playState = true;
364
+ },
365
+ pause() {
366
+ this.playState = false;
367
+ },
368
+ ended(e) {
369
+ this.$emit('ended', e);
370
+ },
371
+ timeupdate(e) {
372
+ this.videoDuration = e.detail.duration;
373
+ this.videoCurrent = e.detail.currentTime;
374
+ this.playProgress = (this.videoCurrent / this.videoDuration) * 100;
375
+ },
376
+ fullscreenchange(e) {
377
+ this.isFull = e.detail.fullScreen;
378
+ if (this.isFull) {
379
+ this.video.hideStatusBar();
380
+ } else {
381
+ this.video.showStatusBar();
382
+ }
383
+ },
384
+ waiting(e) {
385
+ this.$emit('waiting', e);
386
+ },
387
+ error(e) {
388
+ this.$emit('error', e);
389
+ },
390
+ progress(e) {
391
+ this.$emit('progress', e);
392
+ },
393
+ loadeddata(e) {
394
+ this.$emit('loadeddata', e);
395
+ },
396
+ loadstart(e) {
397
+ this.$emit('loadstart', e);
398
+ },
399
+ seeked(e) {
400
+ this.$emit('seeked', e);
401
+ },
402
+ seeking(e) {
403
+ this.$emit('seeking', e);
404
+ },
405
+ loadedMetaData(e) {
406
+ this.videoDuration = e.detail.duration;
407
+ },
408
+ fullscreenclick(e) {
409
+ this.$emit('fullscreenclick', e);
410
+ },
411
+ // 监听video事件 end
412
+ //
413
+ // 格式化视频时间为时分秒
414
+ formatTime(seconds) {
415
+ let hours = Math.floor(seconds / 3600);
416
+ let minutes = Math.floor((seconds % 3600) / 60);
417
+ let remainingSeconds = Math.floor(seconds % 60);
418
+
419
+ let formattedHours = hours > 0 ? (hours < 10 ? '0' + hours : hours) : '';
420
+ let formattedMinutes = minutes < 10 ? '0' + minutes : minutes;
421
+ let formattedSeconds = remainingSeconds < 10 ? '0' + remainingSeconds : remainingSeconds;
422
+
423
+ if (formattedHours !== '') {
424
+ return formattedHours + ':' + formattedMinutes + ':' + formattedSeconds;
425
+ } else {
426
+ return formattedMinutes + ':' + formattedSeconds;
427
+ }
428
+ },
429
+ tip(msg) {
430
+ if (!msg) return;
431
+ this.msg = msg;
432
+ this.showTip = true;
433
+ setTimeout(() => {
434
+ this.showTip = false;
435
+ }, 1500);
436
+ },
437
+ triggerMuted() {
438
+ this.isMuted = !this.isMuted;
439
+ },
440
+ },
441
+ };
442
+ </script>
443
+
444
+ <!-- #ifdef H5 || APP-PLUS -->
445
+ <script module="videoRender" lang="renderjs">
446
+ export default {
447
+ data() {
448
+ return {
449
+ rvVideo: null,
450
+ isInitialized: false,
451
+ isInitCapture: false,
452
+ };
453
+ },
454
+ mounted() {
455
+ this.initVideo();
456
+ },
457
+ methods: {
458
+ initVideo() {
459
+ if (this.isInitialized || !this.$el) return;
460
+ // 只在 APP 端执行,H5 端无此容器
461
+ const container = this.$el.querySelector('.ste-video-renderjs-container');
462
+ if (!container) return;
463
+
464
+ this.isInitialized = true;
465
+
466
+ // 从 data 属性读取配置,避免跨层访问 $vm 的兼容性问题
467
+ const ds = container.dataset;
468
+
469
+ const canvas = document.createElement('canvas');
470
+ canvas.width = 1;
471
+ canvas.height = 1;
472
+ const ctx = canvas.getContext('2d');
473
+ ctx.fillStyle = '#000000';
474
+ ctx.fillRect(0, 0, 1, 1);
475
+ const blackPoster = canvas.toDataURL('image/png');
476
+
477
+ const video = document.createElement('video');
478
+ video.src = ds.src;
479
+ video.controls = true;
480
+ video.autoplay = ds.autoplay === 'true';
481
+ video.loop = ds.loop === 'true';
482
+ video.muted = ds.muted === 'true';
483
+ video.poster = ds.poster || blackPoster;
484
+ video.playsInline = true;
485
+ video.style.width = '100%';
486
+ video.style.height = '225px';
487
+ video.style.maxWidth = '100%';
488
+ video.style.display = 'block';
489
+ video.style.background = '#000';
490
+ video.style.objectFit = ds.objectFit || 'contain';
491
+ // video.style.objectFit = 'cover';
492
+ video.setAttribute('playsinline', '');
493
+ video.setAttribute('webkit-playsinline', '');
494
+ video.setAttribute('x5-playsinline', '');
495
+ video.setAttribute('x5-video-player-type', 'h5');
496
+
497
+ const ownerVm = this.$ownerInstance;
498
+ video.addEventListener('play', () => {
499
+ if (this.isInitCapture) return;
500
+ ownerVm.callMethod('play');
501
+ });
502
+ video.addEventListener('pause', () => {
503
+ if (this.isInitCapture) return;
504
+ ownerVm.callMethod('pause');
505
+ });
506
+ video.addEventListener('ended', () => ownerVm.callMethod('ended', { detail: {} }));
507
+ video.addEventListener('timeupdate', () => {
508
+ ownerVm.callMethod('timeupdate', {
509
+ detail: {
510
+ currentTime: video.currentTime,
511
+ duration: video.duration || 0,
512
+ },
513
+ });
514
+ });
515
+ video.addEventListener('error', () => ownerVm.callMethod('error', { detail: {} }));
516
+ video.addEventListener('loadedmetadata', () => {
517
+ ownerVm.callMethod('loadedMetaData', {
518
+ detail: { duration: video.duration },
519
+ });
520
+ });
521
+ video.addEventListener('waiting', () => ownerVm.callMethod('waiting', { detail: {} }));
522
+ video.addEventListener('loadeddata', () => {
523
+ ownerVm.callMethod('loadeddata', { detail: {} });
524
+ if (!ds.autoplay || ds.autoplay !== 'true') {
525
+ this.isInitCapture = true;
526
+ const p = video.play();
527
+ if (p !== undefined) {
528
+ setTimeout(() => {
529
+ p.then(() => {
530
+ video.pause();
531
+ video.currentTime = 0;
532
+ this.isInitCapture = false;
533
+ }).catch(() => {
534
+ this.isInitCapture = false;
535
+ });
536
+ }, 100)
537
+ } else {
538
+ this.isInitCapture = false;
539
+ }
540
+ }
541
+ });
542
+ video.addEventListener('loadstart', () => ownerVm.callMethod('loadstart', { detail: {} }));
543
+ video.addEventListener('seeked', () => ownerVm.callMethod('seeked', { detail: {} }));
544
+ video.addEventListener('seeking', () => ownerVm.callMethod('seeking', { detail: {} }));
545
+
546
+ container.appendChild(video);
547
+ this.rvVideo = video;
548
+ },
549
+ },
550
+ };
551
+ </script>
552
+ <!-- #endif -->
553
+
554
+ <style lang="scss" scoped>
555
+ .ste-video-root {
556
+ position: relative;
557
+ overflow: hidden;
558
+ width: 100%;
559
+
560
+ .ste-video {
561
+ width: 100%;
562
+ display: block;
563
+ }
564
+
565
+ .ste-video-custom-content {
566
+ width: 100%;
567
+ height: 100%;
568
+ pointer-events: auto;
569
+ }
570
+
571
+ &.auto-height {
572
+ height: 100%;
573
+
574
+ .ste-video {
575
+ height: 100%;
576
+ }
577
+ }
578
+
579
+ &.full {
580
+ .cover {
581
+ position: fixed;
582
+
583
+ padding: 0 var(--rpx-to-px-24);
584
+
585
+ &.top {
586
+ background: linear-gradient(0, rgba(0, 0, 0, 0) 0%, #000000 100%, #000000 100%);
587
+ }
588
+
589
+ &.bottom {
590
+ background: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, #000000 100%);
591
+
592
+ padding: 0 var(--rpx-to-px-40);
593
+
594
+ .progress-box {
595
+ padding-left: 0;
596
+ padding-right: var(--rpx-to-px-32);
597
+ }
598
+
599
+ .text-box {
600
+ padding-right: var(--rpx-to-px-32);
601
+ }
602
+
603
+ .time-box {
604
+ padding-right: var(--rpx-to-px-46) !important;
605
+ padding-left: var(--rpx-to-px-32);
606
+ }
607
+ }
608
+ }
609
+ }
610
+
611
+ .overlay-box {
612
+ width: 100%;
613
+ position: fixed;
614
+ left: 0;
615
+ top: 50%;
616
+ transform: translateY(-50%);
617
+ z-index: 999;
618
+ }
619
+
620
+ .cover {
621
+ position: absolute;
622
+ line-height: 1;
623
+ width: 100%;
624
+ transition: transform 0.3s ease;
625
+ display: flex;
626
+ align-items: center;
627
+ padding: 0 var(--rpx-to-px-16);
628
+ height: var(--control-height);
629
+ pointer-events: auto;
630
+ color: #ffffff;
631
+ font-size: var(--cover-font-size);
632
+
633
+ &.top {
634
+ top: 0;
635
+ background: linear-gradient(180deg, rgba(0, 0, 0, 0.5) 5%, rgba(255, 255, 255, 0) 100%);
636
+
637
+ padding-left: var(--title-padding-left);
638
+
639
+ .title {
640
+ margin-left: 16rpx;
641
+ font-size: var(--rpx-to-px-36);
642
+ }
643
+ }
644
+
645
+ &.bottom {
646
+ bottom: 0;
647
+ // background: linear-gradient(0, rgba(0, 0, 0, 0.5) 5%, rgba(255, 255, 255, 0) 100%);
648
+ background-color: rgba(0, 0, 0, 0.6);
649
+
650
+ display: flex;
651
+ justify-content: space-between;
652
+
653
+ .muted-box {
654
+ padding-left: var(--rpx-to-px-16);
655
+ }
656
+
657
+ .progress-box {
658
+ flex: 1;
659
+ padding: 0 var(--rpx-to-px-32);
660
+
661
+ .progress-bar {
662
+ width: var(--progress-bar-width);
663
+ height: var(--progress-bar-width);
664
+ border-radius: 50%;
665
+ background-color: #ffffff;
666
+ }
667
+ }
668
+
669
+ .time-box {
670
+ display: flex;
671
+ align-items: center;
672
+ overflow: hidden;
673
+ padding-right: var(--rpx-to-px-20);
674
+
675
+ .time {
676
+ width: var(--text-box-width);
677
+ overflow: hidden;
678
+
679
+ &.right {
680
+ text-align: right;
681
+ }
682
+ }
683
+ }
684
+ }
685
+ }
686
+
687
+ .popup-box {
688
+ height: 100vh;
689
+ width: 24.6vw;
690
+ position: absolute;
691
+ pointer-events: auto;
692
+ right: 0;
693
+ top: 0;
694
+ transition: transform 0.3s ease;
695
+ background-color: rgba(0, 0, 0, 0.8);
696
+
697
+ line-height: 1;
698
+
699
+ .item-box {
700
+ line-height: 1;
701
+ display: flex;
702
+ flex-direction: column;
703
+ justify-content: center;
704
+ align-items: center;
705
+ gap: var(--choose-item-gap);
706
+
707
+ width: 100%;
708
+ height: 100%;
709
+ }
710
+
711
+ .choose-item {
712
+ color: #ffffff;
713
+ position: relative;
714
+ width: 100%;
715
+ text-align: center;
716
+
717
+ .text {
718
+ font-size: var(--choose-item-font-size);
719
+ }
720
+
721
+ .check-icon {
722
+ display: flex;
723
+ position: absolute;
724
+ right: var(--check-icon-left);
725
+ top: -1px;
726
+ }
727
+ }
728
+ }
729
+
730
+ .tip-toast {
731
+ background-color: rgba(0, 0, 0, 0.7);
732
+ border-radius: 16rpx;
733
+ padding: 10px;
734
+
735
+ position: absolute;
736
+ left: 50%;
737
+ top: 50%;
738
+ display: flex;
739
+ align-items: center;
740
+ justify-content: center;
741
+ transform: translate(-50%, -50%);
742
+ transition: all 0.2s;
743
+ visibility: hidden;
744
+
745
+ &.show {
746
+ visibility: visible;
747
+ }
748
+
749
+ .tip-text {
750
+ color: #ffffff;
751
+ font-size: 14px;
752
+ line-height: 1;
753
+ }
754
+ }
755
+ }
756
+ </style>