feat/optimization-and-audio #1
@@ -24,6 +24,7 @@ import {
|
||||
type ExamListItem,
|
||||
} from "~/service/wk";
|
||||
import { setUnauthorizedHandler } from "~/service/http";
|
||||
import { startSilentAudio, stopSilentAudio } from "~/service/silentAudio";
|
||||
import { accountStore, type AccountItem } from "~/store/account";
|
||||
import { getMergedHosts, settingsStore } from "~/store/settings";
|
||||
import type { CourseType } from "~/types/Course";
|
||||
@@ -722,6 +723,7 @@ const Account = () => {
|
||||
try {
|
||||
accountStore.getState().setAccountRunningStudy(account.id, true);
|
||||
touchStudyHeartbeat(account.id);
|
||||
startSilentAudio();
|
||||
appendStudyLog(`开始刷课:${course.name}`, account.id);
|
||||
await runStudyQueue({
|
||||
accountId: account.id,
|
||||
@@ -734,6 +736,7 @@ const Account = () => {
|
||||
setIsRunningStudy: () => {
|
||||
accountStore.getState().setAccountRunningStudy(account.id, false);
|
||||
clearStudyHeartbeat(account.id);
|
||||
stopSilentAudio();
|
||||
},
|
||||
onLog: (message: string, accoundID: string) => {
|
||||
touchStudyHeartbeat(accoundID);
|
||||
@@ -751,6 +754,7 @@ const Account = () => {
|
||||
} finally {
|
||||
accountStore.getState().setAccountRunningStudy(account.id, false);
|
||||
clearStudyHeartbeat(account.id);
|
||||
stopSilentAudio();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -762,6 +766,7 @@ const Account = () => {
|
||||
|
||||
accountStore.getState().setAccountRunningStudy(account.id, false);
|
||||
clearStudyHeartbeat(account.id);
|
||||
stopSilentAudio();
|
||||
appendStudyLog(`已发送停止刷课指令:${account.user.name}`, account.id);
|
||||
};
|
||||
|
||||
|
||||
57
src/service/silentAudio.ts
Normal file
57
src/service/silentAudio.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Silent audio playback to prevent browser tab throttling during long-running tasks.
|
||||
*
|
||||
* Uses the Web Audio API to produce a nearly inaudible signal that keeps the
|
||||
* browser from suspending the tab's timers and network requests.
|
||||
*/
|
||||
|
||||
let audioContext: AudioContext | null = null;
|
||||
let oscillatorNode: OscillatorNode | null = null;
|
||||
let gainNode: GainNode | null = null;
|
||||
|
||||
/**
|
||||
* Start playing silent audio. Safe to call multiple times — duplicate calls
|
||||
* are ignored if audio is already playing.
|
||||
*/
|
||||
export const startSilentAudio = () => {
|
||||
if (oscillatorNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
audioContext = new AudioContext();
|
||||
gainNode = audioContext.createGain();
|
||||
gainNode.gain.value = 0.001; // Nearly silent
|
||||
|
||||
oscillatorNode = audioContext.createOscillator();
|
||||
oscillatorNode.type = "sine";
|
||||
oscillatorNode.frequency.value = 1; // Sub-bass, inaudible
|
||||
oscillatorNode.connect(gainNode);
|
||||
gainNode.connect(audioContext.destination);
|
||||
oscillatorNode.start();
|
||||
} catch {
|
||||
// AudioContext may be unavailable in some environments; degrade silently.
|
||||
oscillatorNode = null;
|
||||
gainNode = null;
|
||||
audioContext = null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Stop playing silent audio and release resources.
|
||||
*/
|
||||
export const stopSilentAudio = () => {
|
||||
try {
|
||||
oscillatorNode?.stop();
|
||||
} catch {
|
||||
// Already stopped or never started
|
||||
}
|
||||
|
||||
oscillatorNode?.disconnect();
|
||||
gainNode?.disconnect();
|
||||
audioContext?.close();
|
||||
|
||||
oscillatorNode = null;
|
||||
gainNode = null;
|
||||
audioContext = null;
|
||||
};
|
||||
Reference in New Issue
Block a user