vhall-mini-sdk 0.4.4 → 0.4.6

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,4 +1,4 @@
1
- import { isEmpty, safeJsonParse, sleep, genRandom } from '../../shared/utils.js';
1
+ import { isEmpty, safeJsonParse, sleep, genRandom, safeCustomLog } from '../../shared/utils.js';
2
2
  import createLogger from '../../shared/logger.js';
3
3
 
4
4
  const log = createLogger('mall-reward');
@@ -70,7 +70,7 @@ Component({
70
70
  if (!this.webinarType != 1) {
71
71
  return;
72
72
  }
73
- this.sdkInstance.BRSAgent?.customLog('checkAndFetchMallRewardInfo from=push');
73
+ safeCustomLog(this.sdkInstance, 'checkAndFetchMallRewardInfo from=push');
74
74
  this.checkAndFetchMallRewardInfo('push', { reward_id: msg.data?.id });
75
75
  // 消息推送
76
76
  // const data = {
@@ -121,7 +121,7 @@ Component({
121
121
  if (data?.online) {
122
122
  // 上线(连接建立成功):刷新商城奖励数据(与 FOREGROUND_REFRESH 共用防抖,避免重复请求)
123
123
  if (this._shouldRefreshByEvent()) {
124
- this.sdkInstance.BRSAgent?.customLog('checkAndFetchMallRewardInfo from=refresh, online_state_change');
124
+ safeCustomLog(this.sdkInstance, 'checkAndFetchMallRewardInfo from=refresh, online_state_change');
125
125
  this.checkAndFetchMallRewardInfo('refresh');
126
126
  }
127
127
  } else {
@@ -135,7 +135,7 @@ Component({
135
135
  obj[this.sdkInstance.EVENT.FOREGROUND_REFRESH] = (data) => {
136
136
  log.info('mallReward ------handleForegroundRefresh data:', data);
137
137
  if (!this.sdkInstance) return;
138
- this.sdkInstance.BRSAgent?.customLog('checkAndFetchMallRewardInfo from=refresh, foreground_refresh');
138
+ safeCustomLog(this.sdkInstance, 'checkAndFetchMallRewardInfo from=refresh, foreground_refresh');
139
139
  if (this._shouldRefreshByEvent()) {
140
140
  this.checkAndFetchMallRewardInfo('refresh');
141
141
  }
@@ -146,7 +146,7 @@ Component({
146
146
  onSdkReady() {
147
147
  log.info('mall-reward onSdkReady');
148
148
  this.setData({ initTime: Date.now() });
149
- this.sdkInstance.BRSAgent?.customLog('checkAndFetchMallRewardInfo from=refresh, onSdkReady');
149
+ safeCustomLog(this.sdkInstance, 'checkAndFetchMallRewardInfo from=refresh, onSdkReady');
150
150
  this.checkAndFetchMallRewardInfo('refresh');
151
151
  },
152
152
  // 事件触发的刷新防抖:JOIN_MSG / FOREGROUND_REFRESH 等共用冷却,避免接口重复触发
@@ -10,11 +10,7 @@
10
10
  </view>
11
11
  <view wx:if="{{playStatus=='paused'}}" class='center'>
12
12
  <!-- <mp-icon icon='play' color='#fff' size='{{60}}' bind:tap='handlePlayPause'></mp-icon> -->
13
- <image class="center-image play" mode="aspectFit" src="../../assets/image/play.png" bind:tap='handlePlayPause' />
14
- </view>
15
- <view wx:if="{{playStatus=='playing'&&!hiddenFlag}}" class='center'>
16
- <!-- <mp-icon icon='play' color='#fff' size='{{60}}' bind:tap='handlePlayPause'></mp-icon> -->
17
- <image class="center-image pause" mode="aspectFit" src="../../assets/image/pause_1.png" bind:tap='handlePlayPause' />
13
+ <image class="center-image" mode="aspectFit" src="../../assets/image/play.png" bind:tap='handlePlayPause' />
18
14
  </view>
19
15
  <view class="control {{fullscreen?'fullscreen':''}}" wx:if='{{!verticalMode}}' hidden="{{hiddenFlag}}">
20
16
  <view class="control-left">
@@ -33,18 +33,28 @@ export function createMethods() {
33
33
  };
34
34
  // console.log(`live-player statechange: ${code}, ${STATE_CODE[code.toString()]}`)
35
35
  switch (code) {
36
- // case 2001:
37
- // case 2002:
38
- // this.triggerEvent('loadStart', e.detail);
39
- // break;
36
+ // 2001/2002:已连接服务器 / 开始拉流。
37
+ // 网络差时最容易卡在这一段(连接成功但没有数据),必须给出"加载中"反馈,
38
+ // 否则用户点播放后只能看到封面,没有任何响应
39
+ case 2001:
40
+ case 2002:
41
+ this.triggerEvent('loadStart', e.detail);
42
+ break;
40
43
  case 2004:
41
44
  this.triggerEvent('play', e.detail);
42
45
  break;
46
+ // 2005:播放进度。部分场景(如 2007 后恢复)不会再收到 2004,
47
+ // 用播放进度兜底,确保 loading/封面能被正确收起
48
+ case 2005:
49
+ this.triggerEvent('playing', e.detail);
50
+ break;
43
51
  case 2007:
44
52
  this.triggerEvent('waiting', e.detail);
45
53
  break;
46
54
  case 2103:
55
+ // 网络断连自动重连:同时上报 loadStart,让上层显示加载态而不是停在旧画面
47
56
  this.triggerEvent('reconnecting', e.detail);
57
+ this.triggerEvent('loadStart', e.detail);
48
58
  break;
49
59
  case -2301:
50
60
  case -2302:
@@ -25,7 +25,13 @@ export function createExport() {
25
25
  component.setData({
26
26
  playStatus: "playing",
27
27
  pausedFlag: false,
28
+ // 点击播放立刻给出"加载中"反馈:网络差时 live-player 可能长时间停在
29
+ // 2001/2002 收不到任何状态码,只等 2007 会让用户以为点了没反应
30
+ showLoading: component.data.type === 'live',
31
+ showError: false,
28
32
  })
33
+ // 开启首帧兜底:超时仍未出画面则提示错误,避免一直封面/一直转圈
34
+ component.startFirstFrameWatchdog && component.startFirstFrameWatchdog();
29
35
  }
30
36
  catch (error) {
31
37
  reject(error);
@@ -111,6 +117,11 @@ export function createExport() {
111
117
  * 重新播放
112
118
  */
113
119
  replay() {
120
+ // 重试同样要有加载反馈和首帧兜底
121
+ if (component.data.type === 'live') {
122
+ component.setData({ showLoading: true });
123
+ component.startFirstFrameWatchdog && component.startFirstFrameWatchdog();
124
+ }
114
125
  return component.playerMain.replay();
115
126
  },
116
127
  /**
@@ -1,6 +1,11 @@
1
1
  import { getUnixTimeMs, getCurrentTimeZoneOffset, convertTimeZone } from '../../../shared/dateUtil'
2
2
  import { safeJsonParse } from '../../../shared/utils'
3
3
 
4
+ // 直播首帧兜底时长(ms):从开始拉流/用户点播放起,超过该时长仍未出画面则判定为加载失败。
5
+ // 直播在网络差时可能长时间停在 2001/2002(连接中)或 2007(loading)而没有后续状态码,
6
+ // 没有兜底就会出现"一直显示封面"或"一直转圈"两种假死状态。
7
+ const LIVE_FIRST_FRAME_TIMEOUT = 15000;
8
+
4
9
  export function createMethods() {
5
10
  return {
6
11
  async initVhallPlayer(option) {
@@ -174,6 +179,7 @@ export function createMethods() {
174
179
  onplay(e) {
175
180
  console.log(`播放play事件:`, e);
176
181
  this.triggerEvent('play', e.detail);
182
+ this.clearFirstFrameWatchdog();
177
183
  this.setData({
178
184
  playStatus: 'playing',
179
185
  pausedFlag: false,
@@ -190,10 +196,18 @@ export function createMethods() {
190
196
  onpause(e) {
191
197
  console.log(`暂停pause事件:`, e.detail);
192
198
  this.triggerEvent('pause', e.detail);
199
+ this.clearFirstFrameWatchdog();
193
200
  const data = {
194
201
  playStatus: 'paused',
195
202
  pausedFlag: true,
196
203
  };
204
+ // 直播:加载过程中被暂停时收掉 loading 并恢复封面,避免一直转圈
205
+ if (this.data.type === 'live' && this.data.showLoading) {
206
+ data.showLoading = false;
207
+ if (this.data.posterUrl) {
208
+ data.showPoster = true;
209
+ }
210
+ }
197
211
  // 暖场视频暂停时,只显示大播放按钮(不显示封面)
198
212
  if (this.data.warmVideoIds.length > 0) {
199
213
  data.showBigPlay = true;
@@ -283,6 +297,7 @@ export function createMethods() {
283
297
  onerror(e) {
284
298
  console.log(`播放error事件:`, e.detail);
285
299
  this.triggerEvent('error', e.detail);
300
+ this.clearFirstFrameWatchdog();
286
301
  this.setData({
287
302
  showError: true,
288
303
  showLoading: false,
@@ -298,15 +313,56 @@ export function createMethods() {
298
313
  this.setData({
299
314
  showLoading: true,
300
315
  });
316
+ this.startFirstFrameWatchdog();
301
317
  },
302
318
  /**
303
- * 开始连接事件(仅直播)
319
+ * 开始连接事件(仅直播):2001/2002 已连接/开始拉流,2103 自动重连也会触发
304
320
  * @param {*} e
305
321
  */
306
322
  onloadStart(e) {
307
323
  this.setData({
308
324
  showLoading: true,
309
325
  });
326
+ this.startFirstFrameWatchdog();
327
+ },
328
+ /**
329
+ * 播放进度(直播 2005):画面确实在推进。
330
+ * 2007 恢复播放时不一定再收到 2004,这里兜底收掉 loading/封面
331
+ */
332
+ onplaying() {
333
+ this.clearFirstFrameWatchdog();
334
+ const { showLoading, showPoster, showBigPlay } = this.data;
335
+ if (showLoading || showPoster || showBigPlay) {
336
+ this.setData({
337
+ showLoading: false,
338
+ showPoster: false,
339
+ showBigPlay: false,
340
+ });
341
+ }
342
+ },
343
+ /**
344
+ * 直播首帧兜底计时:开始拉流/等待/点击播放时启动,超时仍未出画面则切到错误态,
345
+ * 避免网络差时一直停在封面或一直转圈
346
+ */
347
+ startFirstFrameWatchdog() {
348
+ if (this.data.type !== 'live') return;
349
+ this.clearFirstFrameWatchdog();
350
+ this._firstFrameTimer = setTimeout(() => {
351
+ this._firstFrameTimer = null;
352
+ // 已经起播(loading 和封面都已被收掉)则不需要兜底
353
+ if (!this.data.showLoading && !this.data.showPoster) return;
354
+ console.warn('[player] 直播首帧超时,切换错误态');
355
+ this.setData({
356
+ showLoading: false,
357
+ showError: true,
358
+ });
359
+ }, LIVE_FIRST_FRAME_TIMEOUT);
360
+ },
361
+ clearFirstFrameWatchdog() {
362
+ if (this._firstFrameTimer) {
363
+ clearTimeout(this._firstFrameTimer);
364
+ this._firstFrameTimer = null;
365
+ }
310
366
  },
311
367
  /**
312
368
  * 自动重连事件(仅直播)
@@ -501,6 +557,7 @@ export function createMethods() {
501
557
  }
502
558
  },
503
559
  destroy() {
560
+ this.clearFirstFrameWatchdog();
504
561
  this.vhallPlayerCore?.destroy();
505
562
  }
506
563
  };
@@ -58,6 +58,7 @@ VeComponent({
58
58
  },
59
59
  detached() {
60
60
  logger.debug('lifetimes detached');
61
+ this.clearFirstFrameWatchdog && this.clearFirstFrameWatchdog();
61
62
  this.vhallPlayerCore?.destroy();
62
63
  this.playerMain = null;
63
64
  this.setData({
@@ -73,6 +74,8 @@ VeComponent({
73
74
  pageLifetimes: {
74
75
  hide() {
75
76
  logger.debug('pageLifetimes hide');
77
+ // 切后台时清掉首帧兜底,避免后台期间被误判为加载超时
78
+ this.clearFirstFrameWatchdog && this.clearFirstFrameWatchdog();
76
79
  this.vhallPlayerCore?.destroy();
77
80
  },
78
81
  show() {
@@ -8,9 +8,6 @@
8
8
  "vhall-player-error": {
9
9
  "default": "./components/error/error"
10
10
  },
11
- "vhall-player-loading": {
12
- "default": "./components/loading/loading"
13
- },
14
11
  "vhall-player-poster": {
15
12
  "default": "./components/poster/poster"
16
13
  },
@@ -7,7 +7,7 @@
7
7
  }
8
8
  </wxs>
9
9
  <view class='vhall-layout-root' style='{{_style}}'>
10
- <player-main catchtap="onToggleControls" wx:if="{{webinarStatus!=3}}" id='{{id}}' class='vhall-player-root' src='{{videoSrc}}' type='{{type}}' autoplay='{{isWarmVideo ? false : autoplay}}' objectFit='{{objectFit}}' muted='{{mutedFlag}}' mode='{{liveDelayMode}}' pictureInPictureMode='{{pictureInPictureMode}}' poster='{{type=="vod" ? "" : posterUrl}}' controls='{{controls}}' show-progress="{{isWarmVideo ? false : showProgress}}" show-bottom-progress="{{isWarmVideo ? false : showBottomProgress}}" show-center-play-btn="{{!isWarmVideo}}" loop="{{loop}}" initialSeekTime='{{initialSeekTime}}' bindfullscreenchange='onfullscreenchange' bindpictureinpicturechange='onpictureinpicturechange' bindtimeupdate='ontimeupdate' bindplay='onplay' bindpause='onpause' bindended='onended' bindloadedmetadata='onloadedmetadata' binderror='onerror' bindwaiting='onwaiting' bindloadStart='onloadStart' bindreconnecting='onreconnecting'>
10
+ <player-main catchtap="onToggleControls" wx:if="{{webinarStatus!=3}}" id='{{id}}' class='vhall-player-root' src='{{videoSrc}}' type='{{type}}' autoplay='{{isWarmVideo ? false : autoplay}}' objectFit='{{objectFit}}' muted='{{mutedFlag}}' mode='{{liveDelayMode}}' pictureInPictureMode='{{pictureInPictureMode}}' poster='{{type=="vod" ? "" : posterUrl}}' controls='{{controls}}' show-progress="{{isWarmVideo ? false : showProgress}}" show-bottom-progress="{{isWarmVideo ? false : showBottomProgress}}" show-center-play-btn="{{!isWarmVideo}}" loop="{{loop}}" initialSeekTime='{{initialSeekTime}}' bindfullscreenchange='onfullscreenchange' bindpictureinpicturechange='onpictureinpicturechange' bindtimeupdate='ontimeupdate' bindplay='onplay' bindpause='onpause' bindended='onended' bindloadedmetadata='onloadedmetadata' binderror='onerror' bindwaiting='onwaiting' bindloadStart='onloadStart' bindreconnecting='onreconnecting' bindplaying='onplaying'>
11
11
  <view wx:if="{{type=='live'}}" class="cover-view" catchtap="onToggleControls">
12
12
  <block>
13
13
  <!-- 纯音频播放蒙层图片 -->
@@ -28,8 +28,11 @@
28
28
  <vhall-player-controls hiddenFlag="{{controlsHiddenFlag}}" wx:if="{{type=='live' && controls && tools.canShow(ignores, 'vhall-player-controls')}}" verticalMode='{{verticalMode}}' playStatus='{{playStatus}}' paused='{{pausedFlag}}' muted='{{mutedFlag}}' fullscreen='{{fullscreenFlag}}' qualityAlias='{{qualityAlias}}' qualitys='{{qualitys}}' currentQuality='{{currentQuality}}' bindcontrolsPlayPause='handleControlsPlayPause' bindcontrolsReplay='handleControlsReplay' bindcontrolsMute='handleControlsMute' bindcontrolsFullScreen='handleControlsFullScreen' bindcontrolsSetQuality='handleControlsSetQuality' />
29
29
  <!-- 错误提示组件 -->
30
30
  <vhall-player-error wx:if="{{showError && tools.canShow(ignores, 'vhall-player-error')}}" error='{{error}}' binderrorandretry='handleErrorAndRetry' />
31
- <!-- loading组件 -->
32
- <vhall-player-loading wx:elif="{{type=='live' && showLoading && tools.canShow(ignores, 'vhall-player-loading')}}" />
31
+ <!-- loading:转圈 + "视频加载中..." 文案 -->
32
+ <view wx:elif="{{type=='live' && showLoading && tools.canShow(ignores, 'vhall-player-loading')}}" class="vhall-player-loading">
33
+ <view class="vhall-player-loading__spinner"></view>
34
+ <view class="vhall-player-loading__text">加载中...</view>
35
+ </view>
33
36
  <!-- 直播:在线人数和热度 -->
34
37
  <online-hot wx:if="{{!controlsHiddenFlag}}" fullscreen="{{fullscreenFlag}}" showPerson="{{showPerson}}" personValue="{{personValue}}" showHot="{{showHot}}" hotValue="{{hotValue}}"></online-hot>
35
38
  </block>
@@ -1 +1 @@
1
- .vhall-layout-root{transition:width .3s ease-in-out,height .3s ease-in-out}.cover-view,.vhall-layout-root{height:100%;width:100%}.webinar_not_live{align-items:center;background-color:rgba(0,0,0,.9);bottom:0;color:#fff;display:flex;font-size:14px;height:100%;justify-content:center;left:0;position:relative;right:0;top:0}.webinar_not_live .cover_img{height:100%;position:absolute;width:100%;z-index:1}.webinar_not_live .webinar_status{align-items:center;background-color:rgba(0,0,0,.6);bottom:0;display:flex;flex-direction:column;justify-content:center;left:0;position:absolute;right:0;text-align:center;top:0;z-index:2}.webinar_not_live .webinar_status .status-img{display:block;margin-left:-10px;width:140rpx}.webinar_not_live .webinar_status .status-text{display:block;font-size:1rem;padding-top:10px}.vhall-player-audio-cover{height:100%;left:0;position:absolute;top:0;width:100%}.vod-poster-play{align-items:center;background:rgba(0,0,0,.45);border-radius:50%;display:flex;height:140rpx;justify-content:center;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);width:140rpx;z-index:3}.vod-poster-play .play-triangle{border-color:transparent transparent transparent #fff;border-style:solid;border-width:30rpx 0 30rpx 48rpx;height:0;margin-left:12rpx;width:0}
1
+ .vhall-layout-root{transition:width .3s ease-in-out,height .3s ease-in-out}.cover-view,.vhall-layout-root{height:100%;width:100%}.webinar_not_live{align-items:center;background-color:rgba(0,0,0,.9);bottom:0;color:#fff;display:flex;font-size:14px;height:100%;justify-content:center;left:0;position:relative;right:0;top:0}.webinar_not_live .cover_img{height:100%;position:absolute;width:100%;z-index:1}.webinar_not_live .webinar_status{align-items:center;background-color:rgba(0,0,0,.6);bottom:0;display:flex;flex-direction:column;justify-content:center;left:0;position:absolute;right:0;text-align:center;top:0;z-index:2}.webinar_not_live .webinar_status .status-img{display:block;margin-left:-10px;width:140rpx}.webinar_not_live .webinar_status .status-text{display:block;font-size:1rem;padding-top:10px}.vhall-player-audio-cover{height:100%;left:0;position:absolute;top:0;width:100%}.vod-poster-play{align-items:center;background:rgba(0,0,0,.45);border-radius:50%;display:flex;height:140rpx;justify-content:center;left:50%;position:absolute;top:50%;transform:translate(-50%,-50%);width:140rpx;z-index:3}.vod-poster-play .play-triangle{border-color:transparent transparent transparent #fff;border-style:solid;border-width:30rpx 0 30rpx 48rpx;height:0;margin-left:12rpx;width:0}.vhall-player-loading{align-items:center;display:flex;flex-direction:column;height:100%;justify-content:center;left:0;position:absolute;top:0;width:100%;z-index:5}.vhall-player-loading .vhall-player-loading__spinner{animation:vhall-player-rotate .8s linear infinite;border:1px solid transparent;border-radius:100%;border-top-color:hsla(0,0%,100%,.9);box-sizing:border-box;height:30px;width:30px}.vhall-player-loading .vhall-player-loading__text{color:hsla(0,0%,100%,.9);line-height:20px;margin-top:8px;text-shadow:0 1px 2px rgba(0,0,0,.5)}@keyframes vhall-player-rotate{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}
@@ -1,4 +1,4 @@
1
- import { debounce } from '../../shared/utils';
1
+ import { debounce, safeCustomLog } from '../../shared/utils';
2
2
  const DEFAULT_SIGIN_TITLE = '主持人发起了签到';
3
3
  const DEFAULT_SIGIN_SUCCESS_TIP = '签到成功';
4
4
  const app = getApp();
@@ -126,15 +126,15 @@ Component({
126
126
  switch (msgDataType) {
127
127
  // 发起签到
128
128
  case 'sign_in_push':
129
- this.sdkInstance.BRSAgent?.customLog('sign_in_push');
130
- this.sdkInstance.get('logReportServer').report('116999', { stage: "sign_in_push" });
129
+ safeCustomLog(this.sdkInstance, 'sign_in_push');
130
+ this.sdkInstance.get('logReportServer').report('116998', { stage: "sign_in_push" });
131
131
  this.handleSigninPush(msg.data);
132
132
  break;
133
133
  // 结束签到
134
134
  case 'sign_end':
135
135
  // 直播结束
136
136
  case 'live_over':
137
- this.sdkInstance.BRSAgent?.customLog(`${msgDataType}`);
137
+ safeCustomLog(this.sdkInstance, `${msgDataType}`);
138
138
  this.handleSigninEnd(msg.data);
139
139
  break;
140
140
  }
@@ -262,7 +262,7 @@ Component({
262
262
  phone: watchInitData.join_info.phone,
263
263
  };
264
264
  try {
265
- this.sdkInstance.BRSAgent?.customLog('click signin');
265
+ safeCustomLog(this.sdkInstance, 'click signin');
266
266
  this.signed = true; // 标识签到过
267
267
  const res = await this.sdkInstance.wxApi.sendPost('/v3/interacts/sign/user-sign', params);
268
268
  // 不管成功失败都显示成功(原观看端逻辑如此)
@@ -272,10 +272,10 @@ Component({
272
272
  });
273
273
  this.closeCountdown();
274
274
  if (res?.code != 200) {
275
- this.sdkInstance.BRSAgent?.customLog('click signin failed:', JSON.stringify(res));
275
+ safeCustomLog(this.sdkInstance, 'click signin failed:', JSON.stringify(res));
276
276
  }
277
277
  } catch (error) {
278
- this.sdkInstance.BRSAgent?.customLog('click signin error:', ex?.message);
278
+ safeCustomLog(this.sdkInstance, 'click signin error:', ex?.message);
279
279
  wx.showToast({
280
280
  title: '网络异常!',
281
281
  icon: 'none',
@@ -298,7 +298,7 @@ Component({
298
298
  });
299
299
  // 错误处理
300
300
  audio.onError((err) => {
301
- this.sdkInstance.BRSAgent?.customLog('playSound error:', err.errMsg);
301
+ safeCustomLog(this.sdkInstance, 'playSound error:', err.errMsg);
302
302
  console.error('播放签到提示音失败:', err.errMsg);
303
303
  });
304
304
  },