解决离开页面后录音终端的问题

This commit is contained in:
zhonghua.li
2026-04-18 23:45:24 +08:00
parent 87afece84f
commit f05a85bc1c
4 changed files with 123 additions and 29 deletions

View File

@@ -233,6 +233,20 @@ function firstNonEmptyString(...parts) {
return "";
}
// 跨页面录音会话缓存(模块级):页面销毁后仍保留录音状态与分段上传结果
const recordingSessionCache = {
recordingServiceId: null,
recordingContext: null,
recordingElapsedSeconds: 0,
recordingEstimatedSizeBytes: 0,
recordingManualStopRequested: false,
recordingCurrentSegmentNo: 0,
recordingCurrentSegmentStartAt: 0,
recordingCurrentSegmentElapsedSeconds: 0,
uploadingCountMap: {},
uploadResultMap: {},
};
export default {
props: {
/** 卡片右侧状态文案默认与接待页「服务中」Tab 一致 */
@@ -283,22 +297,22 @@ export default {
},
recorderSupported: false,
recorderManager: null,
recordingServiceId: null,
recordingContext: null,
recordingElapsedSeconds: 0,
recordingEstimatedSizeBytes: 0,
recordingServiceId: recordingSessionCache.recordingServiceId,
recordingContext: recordingSessionCache.recordingContext,
recordingElapsedSeconds: recordingSessionCache.recordingElapsedSeconds,
recordingEstimatedSizeBytes: recordingSessionCache.recordingEstimatedSizeBytes,
recordingTicker: null,
keepScreenOnEnabled: false,
// 上传前压缩策略:从录音源头降低采样率/码率,减少上传文件体积
recordingSampleRate: 16000,
recordingEncodeBitRate: 32000,
recordingSegmentDurationMs: 5 * 60 * 1000,
recordingManualStopRequested: false,
recordingCurrentSegmentNo: 0,
recordingCurrentSegmentStartAt: 0,
recordingCurrentSegmentElapsedSeconds: 0,
uploadingCountMap: {},
uploadResultMap: {}
recordingManualStopRequested: recordingSessionCache.recordingManualStopRequested,
recordingCurrentSegmentNo: recordingSessionCache.recordingCurrentSegmentNo,
recordingCurrentSegmentStartAt: recordingSessionCache.recordingCurrentSegmentStartAt,
recordingCurrentSegmentElapsedSeconds: recordingSessionCache.recordingCurrentSegmentElapsedSeconds,
uploadingCountMap: { ...recordingSessionCache.uploadingCountMap },
uploadResultMap: { ...recordingSessionCache.uploadResultMap }
}
},
mounted() {
@@ -307,26 +321,71 @@ export default {
// 获取当前用户手机号
this.loadCurrentUserPhone();
this.initServiceRecorder();
this.restoreRecordingSessionFromCache();
if (this.recordingServiceId && this.recordingContext?.id) {
this.startRecordingTicker({ reset: false });
}
this.fetchServiceStatusList();
},
beforeUnmount() {
this.stopRecordingTicker();
this.setKeepScreenOn(false);
if (this.recorderManager && this.recordingServiceId) {
try {
this.recorderManager.stop();
} catch (e) {
/* ignore */
}
}
// 跨页面持续录音离开页面仅暂停UI定时器不清空录音显示状态
this.pauseRecordingTicker();
// 跨页面持续录音:离开页面时不主动 stop
this.persistRecordingSessionToCache();
},
methods: {
persistRecordingSessionToCache() {
recordingSessionCache.recordingServiceId = this.recordingServiceId || null;
recordingSessionCache.recordingContext = this.recordingContext ? { ...this.recordingContext } : null;
recordingSessionCache.recordingElapsedSeconds = Number(this.recordingElapsedSeconds) || 0;
recordingSessionCache.recordingEstimatedSizeBytes = Number(this.recordingEstimatedSizeBytes) || 0;
recordingSessionCache.recordingManualStopRequested = !!this.recordingManualStopRequested;
recordingSessionCache.recordingCurrentSegmentNo = Number(this.recordingCurrentSegmentNo) || 0;
recordingSessionCache.recordingCurrentSegmentStartAt = Number(this.recordingCurrentSegmentStartAt) || 0;
recordingSessionCache.recordingCurrentSegmentElapsedSeconds = Number(this.recordingCurrentSegmentElapsedSeconds) || 0;
recordingSessionCache.uploadingCountMap = { ...this.uploadingCountMap };
recordingSessionCache.uploadResultMap = { ...this.uploadResultMap };
},
restoreRecordingSessionFromCache() {
this.recordingServiceId = recordingSessionCache.recordingServiceId || null;
this.recordingContext = recordingSessionCache.recordingContext ? { ...recordingSessionCache.recordingContext } : null;
this.recordingElapsedSeconds = Number(recordingSessionCache.recordingElapsedSeconds) || 0;
this.recordingEstimatedSizeBytes = Number(recordingSessionCache.recordingEstimatedSizeBytes) || 0;
this.recordingManualStopRequested = !!recordingSessionCache.recordingManualStopRequested;
this.recordingCurrentSegmentNo = Number(recordingSessionCache.recordingCurrentSegmentNo) || 0;
this.recordingCurrentSegmentStartAt = Number(recordingSessionCache.recordingCurrentSegmentStartAt) || 0;
this.recordingCurrentSegmentElapsedSeconds = Number(recordingSessionCache.recordingCurrentSegmentElapsedSeconds) || 0;
this.uploadingCountMap = { ...recordingSessionCache.uploadingCountMap };
this.uploadResultMap = { ...recordingSessionCache.uploadResultMap };
},
initServiceRecorder() {
this.recorderSupported = typeof uni.getRecorderManager === 'function';
if (!this.recorderSupported) {
return;
}
this.recorderManager = uni.getRecorderManager();
this.ensureRecorderManager();
this.bindRecorderEvents();
},
ensureRecorderManager() {
if (!this.recorderSupported) {
return false;
}
if (!this.recorderManager) {
this.recorderManager = uni.getRecorderManager();
}
return !!this.recorderManager;
},
bindRecorderEvents() {
if (!this.ensureRecorderManager()) {
return;
}
// 先解绑,避免重复绑定或被其它页面覆盖后残留旧回调
if (typeof this.recorderManager.offStop === 'function') {
this.recorderManager.offStop();
}
if (typeof this.recorderManager.offError === 'function') {
this.recorderManager.offError();
}
// 录音每段停止后回调:静默上传,若非手动停止则继续下一段
this.recorderManager.onStop((res) => {
const ctx = this.recordingContext;
@@ -384,6 +443,7 @@ export default {
this.recordingCurrentSegmentStartAt = 0;
this.recordingCurrentSegmentElapsedSeconds = 0;
this.setKeepScreenOn(false);
this.persistRecordingSessionToCache();
});
this.recorderManager.onError(() => {
this.recordingServiceId = null;
@@ -394,6 +454,7 @@ export default {
this.recordingCurrentSegmentElapsedSeconds = 0;
this.stopRecordingTicker();
this.setKeepScreenOn(false);
this.persistRecordingSessionToCache();
uni.showToast({ title: '录音出错', icon: 'none' });
});
},
@@ -411,25 +472,48 @@ export default {
},
});
},
startRecordingTicker() {
this.stopRecordingTicker();
this.recordingElapsedSeconds = 0;
this.recordingEstimatedSizeBytes = 0;
startRecordingTicker(options = {}) {
const { reset = true } = options;
this.pauseRecordingTicker();
if (reset) {
this.recordingElapsedSeconds = 0;
this.recordingEstimatedSizeBytes = 0;
} else if (this.recordingCurrentSegmentStartAt) {
const resumedElapsed = Math.max(
0,
Math.floor((Date.now() - Number(this.recordingCurrentSegmentStartAt)) / 1000)
);
this.recordingElapsedSeconds = resumedElapsed;
const bytesPerSecond = (Number(this.recordingEncodeBitRate) || 32000) / 8;
this.recordingEstimatedSizeBytes = Math.floor(resumedElapsed * bytesPerSecond);
}
const bytesPerSecond = (Number(this.recordingEncodeBitRate) || 32000) / 8;
this.recordingTicker = setInterval(() => {
this.recordingElapsedSeconds += 1;
if (this.recordingCurrentSegmentStartAt) {
this.recordingElapsedSeconds = Math.max(
0,
Math.floor((Date.now() - Number(this.recordingCurrentSegmentStartAt)) / 1000)
);
} else {
this.recordingElapsedSeconds += 1;
}
this.recordingCurrentSegmentElapsedSeconds = this.recordingElapsedSeconds;
this.recordingEstimatedSizeBytes = Math.floor(this.recordingElapsedSeconds * bytesPerSecond);
this.persistRecordingSessionToCache();
}, 1000);
},
stopRecordingTicker() {
pauseRecordingTicker() {
if (this.recordingTicker) {
clearInterval(this.recordingTicker);
}
this.recordingTicker = null;
},
stopRecordingTicker() {
this.pauseRecordingTicker();
this.recordingCurrentSegmentElapsedSeconds = this.recordingElapsedSeconds;
this.recordingElapsedSeconds = 0;
this.recordingEstimatedSizeBytes = 0;
this.persistRecordingSessionToCache();
},
startNewSegmentRecording() {
if (!this.recorderManager || !this.recordingServiceId || !this.recordingContext?.id) {
@@ -447,6 +531,7 @@ export default {
format: 'mp3'
});
this.startRecordingTicker();
this.persistRecordingSessionToCache();
} catch (e) {
this.recordingServiceId = null;
this.recordingContext = null;
@@ -456,6 +541,7 @@ export default {
this.recordingCurrentSegmentElapsedSeconds = 0;
this.stopRecordingTicker();
this.setKeepScreenOn(false);
this.persistRecordingSessionToCache();
uni.showToast({ title: '无法开始录音', icon: 'none' });
}
},
@@ -482,6 +568,9 @@ export default {
return String(recordId) === this.recordingServiceId;
},
startPhoneRecord(item) {
// 返回页面后可能被其它页面覆盖录音回调,这里每次录音前重新绑定一次
this.ensureRecorderManager();
this.bindRecorderEvents();
if (!this.recorderSupported || !this.recorderManager) {
uni.showToast({
title: '当前环境不支持录音,请使用微信小程序或 App',
@@ -512,6 +601,7 @@ export default {
this.recordingContext = { ...item };
this.recordingServiceId = String(item.id);
this.setKeepScreenOn(true);
this.persistRecordingSessionToCache();
this.startNewSegmentRecording();
},
/** 结束录音onStop 回调中会调用 uploadFileForRecord 上传至后端 */
@@ -523,6 +613,7 @@ export default {
return;
}
this.recordingManualStopRequested = true;
this.persistRecordingSessionToCache();
this.recorderManager.stop();
},
/**
@@ -788,12 +879,14 @@ export default {
const key = String(recordId);
const next = Number(this.uploadingCountMap[key] || 0) + 1;
this.uploadingCountMap = { ...this.uploadingCountMap, [key]: next };
this.persistRecordingSessionToCache();
},
decreaseUploadingCount(recordId) {
const key = String(recordId);
const current = Number(this.uploadingCountMap[key] || 0);
const next = Math.max(0, current - 1);
this.uploadingCountMap = { ...this.uploadingCountMap, [key]: next };
this.persistRecordingSessionToCache();
},
getUploadResultList(recordId) {
const key = String(recordId || '');
@@ -803,6 +896,7 @@ export default {
setUploadResultList(recordId, list) {
const key = String(recordId || '');
this.uploadResultMap = { ...this.uploadResultMap, [key]: Array.isArray(list) ? list : [] };
this.persistRecordingSessionToCache();
},
pushUploadResult(recordId, segment) {
const list = this.getUploadResultList(recordId);