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

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long