Files
fluxer/fluxer_desktop/src/main/StreamingPriority.ts
T

653 lines
23 KiB
TypeScript

// SPDX-License-Identifier: AGPL-3.0-or-later
import {createRequire} from 'node:module';
import os from 'node:os';
import {stopWindowsScreenCaptureGuard} from '@electron/main/WindowsScreenCaptureGuard';
import {reportWindowsCaptureItemReuse} from '@electron/main/WindowsWindowCaptureItemReuse';
import {app, powerSaveBlocker} from 'electron';
import log from 'electron-log';
const STREAMING_PRIORITY = os.constants?.priority?.PRIORITY_ABOVE_NORMAL ?? -7;
const CAN_ELEVATE_PROCESS_PRIORITY = process.platform === 'win32';
const GPU_PRIORITY_REFRESH_INTERVAL_MS = 20000;
const GPU_SCHEDULING_PRIORITY_ENV = 'FLUXER_STREAMING_GPU_SCHEDULING_PRIORITY';
const MAX_GPU_PRIORITY_DIAGNOSTIC_TARGETS = 32;
const requireModule = createRequire(import.meta.url);
type WindowsGpuSchedulingPriority = 'high' | 'realtime';
type WindowsGpuPriorityTargetReason =
| 'browser-main'
| 'renderer'
| 'tracked-renderer'
| 'chromium-gpu'
| 'chromium-video-capture'
| 'chromium-video-encode'
| 'chromium-media-utility';
const PROCESS_PRIORITY_TARGET_REASONS: ReadonlySet<WindowsGpuPriorityTargetReason> = new Set([
'browser-main',
'renderer',
'tracked-renderer',
]);
type GpuPriorityModuleStatus = 'disabled' | 'unsupported-platform' | 'not-loaded' | 'loaded' | 'unavailable';
type GpuPriorityAttemptStatus =
| 'disabled'
| 'unsupported-platform'
| 'native-module-unavailable'
| 'no-targets'
| 'succeeded'
| 'partial'
| 'failed'
| 'no-active-priority';
type WindowsGpuPriorityModule = {
elevateGpuSchedulingPriority?: (processId?: number, priorityClass?: WindowsGpuSchedulingPriority) => boolean;
restoreGpuSchedulingPriority?: (processId?: number) => boolean;
loadError?: Error | null;
};
interface GpuPriorityTarget {
processId: number;
reasons: Array<WindowsGpuPriorityTargetReason>;
}
interface GpuPriorityFailure {
processId: number;
reason: string;
}
interface GpuPriorityAcquireDiagnostics {
status: GpuPriorityAttemptStatus;
priorityClass: WindowsGpuSchedulingPriority | null;
targets: Array<GpuPriorityTarget>;
elevatedProcessIds: Array<number>;
skippedProcessIds: Array<number>;
failedProcessIds: Array<GpuPriorityFailure>;
detail?: string;
}
interface GpuPriorityRestoreDiagnostics {
status: GpuPriorityAttemptStatus;
processIds: Array<number>;
restoredProcessIds: Array<number>;
failedProcessIds: Array<GpuPriorityFailure>;
detail?: string;
}
export interface StreamingPriorityDiagnostics {
refCount: number;
powerSaveBlocker: {
active: boolean;
id: number | null;
};
processPriority: {
supported: boolean;
streamingPriority: number;
savedPriority: number | null;
elevated: boolean;
elevatedProcesses: Array<{processId: number; savedPriority: number}>;
};
gpuScheduling: {
supported: boolean;
priorityClass: WindowsGpuSchedulingPriority | null;
env: string;
nativeModuleStatus: GpuPriorityModuleStatus;
nativeModuleLoadErrorDetail: string | null;
refreshActive: boolean;
refreshIntervalMs: number;
trackedWebContents: number;
elevatedProcesses: Array<{processId: number; priorityClass: WindowsGpuSchedulingPriority}>;
lastAcquire: GpuPriorityAcquireDiagnostics | null;
lastRestore: GpuPriorityRestoreDiagnostics | null;
};
}
let refCount = 0;
let powerBlockerId: number | null = null;
const savedProcessPriorities = new Map<number, number>();
let gpuPriorityModule: WindowsGpuPriorityModule | null | undefined;
let gpuPriorityModuleLoadErrorDetail: string | null = null;
const gpuPriorityElevatedProcessIds = new Map<number, WindowsGpuSchedulingPriority>();
const streamingPriorityWebContents = new Set<Electron.WebContents>();
const streamingPriorityWebContentsCleanup = new Map<Electron.WebContents, () => void>();
const backgroundThrottlingBypassedWebContents = new Set<Electron.WebContents>();
let gpuPriorityRefreshTimer: NodeJS.Timeout | null = null;
let lastGpuPriorityAcquire: GpuPriorityAcquireDiagnostics | null = null;
let lastGpuPriorityRestore: GpuPriorityRestoreDiagnostics | null = null;
function formatErrorDetail(error: unknown): string {
if (error instanceof Error) return error.message;
if (
typeof error === 'object' &&
error !== null &&
'message' in error &&
typeof (error as {message?: unknown}).message === 'string'
) {
return (error as {message: string}).message;
}
return String(error);
}
const GPU_PRIORITY_MODULE_UNAVAILABLE_DETAIL = 'Windows GPU priority native API unavailable';
function gpuPriorityModuleUnavailableReason(): string {
if (gpuPriorityModuleLoadErrorDetail === null) return GPU_PRIORITY_MODULE_UNAVAILABLE_DETAIL;
const lineBreakIndex = gpuPriorityModuleLoadErrorDetail.indexOf('\n');
if (lineBreakIndex === -1) return gpuPriorityModuleLoadErrorDetail;
return gpuPriorityModuleLoadErrorDetail.slice(0, lineBreakIndex).trimEnd();
}
function resolveGpuSchedulingPriority(): WindowsGpuSchedulingPriority | null {
const raw = process.env[GPU_SCHEDULING_PRIORITY_ENV];
if (raw == null || raw.trim() === '') return 'high';
const normalized = raw.trim().toLowerCase();
if (normalized === 'realtime' || normalized === 'real-time') return 'realtime';
if (normalized === 'high') return 'high';
if (normalized === 'off' || normalized === 'disabled' || normalized === 'none') return null;
log.warn('[StreamingPriority] Ignoring invalid GPU scheduling priority override', {
env: GPU_SCHEDULING_PRIORITY_ENV,
value: raw,
});
return 'high';
}
const GPU_SCHEDULING_PRIORITY = resolveGpuSchedulingPriority();
function startPowerSaveBlocker(): void {
try {
powerBlockerId = powerSaveBlocker.start('prevent-display-sleep');
} catch (error) {
log.warn('[StreamingPriority] Failed to start power save blocker', {error});
}
}
function stopPowerSaveBlocker(): void {
if (powerBlockerId === null) return;
try {
powerSaveBlocker.stop(powerBlockerId);
} catch (error) {
log.warn('[StreamingPriority] Failed to stop power save blocker', {error});
}
powerBlockerId = null;
}
function elevateProcessPriority(processId: number): void {
if (!CAN_ELEVATE_PROCESS_PRIORITY || savedProcessPriorities.has(processId)) return;
try {
const currentPriority = os.getPriority(processId);
if (currentPriority <= STREAMING_PRIORITY) return;
os.setPriority(processId, STREAMING_PRIORITY);
savedProcessPriorities.set(processId, currentPriority);
} catch (error) {
log.debug('[StreamingPriority] Failed to elevate process priority', {processId, error});
}
}
function elevateProcessPriorityTargets(targets: Array<GpuPriorityTarget>): void {
if (!CAN_ELEVATE_PROCESS_PRIORITY) return;
for (const target of targets) {
if (!target.reasons.some((reason) => PROCESS_PRIORITY_TARGET_REASONS.has(reason))) continue;
elevateProcessPriority(target.processId);
}
}
function restoreProcessPriority(): void {
if (savedProcessPriorities.size === 0) return;
const entries = [...savedProcessPriorities.entries()];
savedProcessPriorities.clear();
for (const [processId, priority] of entries) {
try {
os.setPriority(processId, priority);
} catch (error) {
log.debug('[StreamingPriority] Failed to restore process priority', {processId, error});
}
}
}
function loadWindowsGpuPriorityModule(): WindowsGpuPriorityModule | null {
if (process.platform !== 'win32') return null;
if (gpuPriorityModule !== undefined) return gpuPriorityModule;
try {
const addon = requireModule('@fluxer/win-game-capture') as WindowsGpuPriorityModule;
if (addon.loadError) {
gpuPriorityModuleLoadErrorDetail = formatErrorDetail(addon.loadError);
gpuPriorityModule = null;
log.debug('[StreamingPriority] Windows GPU priority module reported load error', {
detail: gpuPriorityModuleLoadErrorDetail,
});
} else {
gpuPriorityModuleLoadErrorDetail = null;
gpuPriorityModule = addon;
}
return gpuPriorityModule;
} catch (error) {
gpuPriorityModuleLoadErrorDetail = formatErrorDetail(error);
gpuPriorityModule = null;
log.debug('[StreamingPriority] Failed to load Windows GPU priority module', {
detail: gpuPriorityModuleLoadErrorDetail,
});
return null;
}
}
function addGpuPriorityTarget(
targets: Map<number, Set<WindowsGpuPriorityTargetReason>>,
processId: number | undefined,
reason: WindowsGpuPriorityTargetReason,
): void {
if (typeof processId !== 'number' || !Number.isInteger(processId) || processId <= 0) return;
const reasons = targets.get(processId);
if (reasons) {
reasons.add(reason);
} else {
targets.set(processId, new Set([reason]));
}
}
function getRendererProcessId(webContents?: Electron.WebContents): number | undefined {
if (!webContents || webContents.isDestroyed()) return undefined;
try {
return webContents.getOSProcessId();
} catch {
return undefined;
}
}
function setBackgroundThrottlingBypass(webContents: Electron.WebContents, bypass: boolean): void {
if (webContents.isDestroyed()) return;
try {
webContents.setBackgroundThrottling(!bypass);
if (bypass) {
backgroundThrottlingBypassedWebContents.add(webContents);
} else {
backgroundThrottlingBypassedWebContents.delete(webContents);
}
} catch (error) {
log.debug('[StreamingPriority] Failed to update renderer background throttling', {bypass, error});
}
}
function restoreBackgroundThrottling(): void {
for (const webContents of backgroundThrottlingBypassedWebContents) {
setBackgroundThrottlingBypass(webContents, false);
}
backgroundThrottlingBypassedWebContents.clear();
}
function rememberStreamingPriorityWebContents(webContents?: Electron.WebContents): void {
if (!webContents || webContents.isDestroyed() || streamingPriorityWebContents.has(webContents)) return;
streamingPriorityWebContents.add(webContents);
const processId = getRendererProcessId(webContents);
const forgetProcessPriority = (): void => {
if (processId === undefined) return;
savedProcessPriorities.delete(processId);
gpuPriorityElevatedProcessIds.delete(processId);
};
const cleanup = (): void => {
streamingPriorityWebContents.delete(webContents);
streamingPriorityWebContentsCleanup.delete(webContents);
backgroundThrottlingBypassedWebContents.delete(webContents);
forgetProcessPriority();
};
streamingPriorityWebContentsCleanup.set(webContents, () => {
webContents.removeListener('destroyed', cleanup);
webContents.removeListener('render-process-gone', forgetProcessPriority);
});
webContents.once('destroyed', cleanup);
webContents.once('render-process-gone', forgetProcessPriority);
}
function clearStreamingPriorityWebContents(): void {
for (const [webContents, detachListeners] of streamingPriorityWebContentsCleanup) {
if (webContents && !webContents.isDestroyed()) {
detachListeners();
}
}
streamingPriorityWebContents.clear();
streamingPriorityWebContentsCleanup.clear();
}
function getChromiumProcessTargetReasons(metric: Electron.ProcessMetric): Array<WindowsGpuPriorityTargetReason> {
const type = metric.type.toLowerCase();
if (type === 'gpu') return ['chromium-gpu'];
const processName = `${metric.name ?? ''} ${metric.serviceName ?? ''}`.toLowerCase();
const reasons: Array<WindowsGpuPriorityTargetReason> = [];
if (processName.includes('video capture') || processName.includes('video_capture')) {
reasons.push('chromium-video-capture');
}
if (processName.includes('video encode') || processName.includes('video_encode')) {
reasons.push('chromium-video-encode');
}
if (type !== 'utility' || reasons.length > 0) return reasons;
if (
processName.includes('capture') ||
processName.includes('encoder') ||
processName.includes('media') ||
processName.includes('video') ||
processName.includes('webrtc')
) {
reasons.push('chromium-media-utility');
}
return reasons;
}
function collectGpuSchedulingPriorityTargets(webContents?: Electron.WebContents): Array<GpuPriorityTarget> {
const targets = new Map<number, Set<WindowsGpuPriorityTargetReason>>();
addGpuPriorityTarget(targets, process.pid, 'browser-main');
const rendererProcessId = getRendererProcessId(webContents);
addGpuPriorityTarget(targets, rendererProcessId, 'renderer');
for (const trackedWebContents of streamingPriorityWebContents) {
const trackedRendererProcessId = getRendererProcessId(trackedWebContents);
if (trackedRendererProcessId !== rendererProcessId) {
addGpuPriorityTarget(targets, trackedRendererProcessId, 'tracked-renderer');
}
}
try {
for (const metric of app.getAppMetrics()) {
for (const reason of getChromiumProcessTargetReasons(metric)) {
addGpuPriorityTarget(targets, metric.pid, reason);
}
}
} catch (error) {
log.debug('[StreamingPriority] Failed to collect Chromium process metrics', {error});
}
return [...targets.entries()].map(([processId, reasons]) => ({processId, reasons: [...reasons]}));
}
function limitGpuPriorityTargets(targets: Array<GpuPriorityTarget>): Array<GpuPriorityTarget> {
return targets
.slice(0, MAX_GPU_PRIORITY_DIAGNOSTIC_TARGETS)
.map((target) => ({processId: target.processId, reasons: [...target.reasons]}));
}
function getGpuPriorityModuleStatus(): GpuPriorityModuleStatus {
if (GPU_SCHEDULING_PRIORITY === null) return 'disabled';
if (process.platform !== 'win32') return 'unsupported-platform';
if (gpuPriorityModule === undefined) return 'not-loaded';
return gpuPriorityModule === null ? 'unavailable' : 'loaded';
}
function cloneGpuPriorityAcquireDiagnostics(
diagnostics: GpuPriorityAcquireDiagnostics | null,
): GpuPriorityAcquireDiagnostics | null {
if (!diagnostics) return null;
return {
...diagnostics,
targets: diagnostics.targets.map((target) => ({processId: target.processId, reasons: [...target.reasons]})),
elevatedProcessIds: [...diagnostics.elevatedProcessIds],
skippedProcessIds: [...diagnostics.skippedProcessIds],
failedProcessIds: diagnostics.failedProcessIds.map((failure) => ({...failure})),
};
}
function cloneGpuPriorityRestoreDiagnostics(
diagnostics: GpuPriorityRestoreDiagnostics | null,
): GpuPriorityRestoreDiagnostics | null {
if (!diagnostics) return null;
return {
...diagnostics,
processIds: [...diagnostics.processIds],
restoredProcessIds: [...diagnostics.restoredProcessIds],
failedProcessIds: diagnostics.failedProcessIds.map((failure) => ({...failure})),
};
}
function elevateGpuSchedulingPriority(targets: Array<GpuPriorityTarget>): void {
if (GPU_SCHEDULING_PRIORITY === null) {
lastGpuPriorityAcquire = {
status: 'disabled',
priorityClass: null,
targets: [],
elevatedProcessIds: [],
skippedProcessIds: [],
failedProcessIds: [],
detail: `${GPU_SCHEDULING_PRIORITY_ENV} disabled GPU scheduling priority`,
};
return;
}
if (process.platform !== 'win32') {
lastGpuPriorityAcquire = {
status: 'unsupported-platform',
priorityClass: GPU_SCHEDULING_PRIORITY,
targets: limitGpuPriorityTargets(targets),
elevatedProcessIds: [],
skippedProcessIds: [],
failedProcessIds: [],
detail: `GPU scheduling priority is Windows-only; current platform is ${process.platform}`,
};
return;
}
const addon = loadWindowsGpuPriorityModule();
if (!addon?.elevateGpuSchedulingPriority) {
lastGpuPriorityAcquire = {
status: 'native-module-unavailable',
priorityClass: GPU_SCHEDULING_PRIORITY,
targets: limitGpuPriorityTargets(targets),
elevatedProcessIds: [],
skippedProcessIds: [],
failedProcessIds: targets.slice(0, MAX_GPU_PRIORITY_DIAGNOSTIC_TARGETS).map((target) => ({
processId: target.processId,
reason: gpuPriorityModuleUnavailableReason(),
})),
detail: gpuPriorityModuleLoadErrorDetail ?? GPU_PRIORITY_MODULE_UNAVAILABLE_DETAIL,
};
log.debug('[StreamingPriority] Cannot elevate GPU scheduling priority; native module unavailable', {
priorityClass: GPU_SCHEDULING_PRIORITY,
targets: lastGpuPriorityAcquire.targets,
detail: lastGpuPriorityAcquire.detail,
});
return;
}
if (targets.length === 0) {
lastGpuPriorityAcquire = {
status: 'no-targets',
priorityClass: GPU_SCHEDULING_PRIORITY,
targets: [],
elevatedProcessIds: [],
skippedProcessIds: [],
failedProcessIds: [],
};
return;
}
const elevatedProcessIds: Array<number> = [];
const skippedProcessIds: Array<number> = [];
const failedProcessIds: Array<GpuPriorityFailure> = [];
for (const {processId} of targets) {
if (gpuPriorityElevatedProcessIds.get(processId) === GPU_SCHEDULING_PRIORITY) {
skippedProcessIds.push(processId);
continue;
}
try {
if (addon.elevateGpuSchedulingPriority(processId, GPU_SCHEDULING_PRIORITY)) {
gpuPriorityElevatedProcessIds.set(processId, GPU_SCHEDULING_PRIORITY);
elevatedProcessIds.push(processId);
} else {
failedProcessIds.push({processId, reason: 'native API returned false'});
log.debug('[StreamingPriority] Failed to elevate GPU scheduling priority', {processId});
}
} catch (error) {
failedProcessIds.push({processId, reason: formatErrorDetail(error)});
log.debug('[StreamingPriority] Failed to elevate GPU scheduling priority', {processId, error});
}
}
lastGpuPriorityAcquire = {
status: failedProcessIds.length === 0 ? 'succeeded' : elevatedProcessIds.length > 0 ? 'partial' : 'failed',
priorityClass: GPU_SCHEDULING_PRIORITY,
targets: limitGpuPriorityTargets(targets),
elevatedProcessIds: elevatedProcessIds.slice(0, MAX_GPU_PRIORITY_DIAGNOSTIC_TARGETS),
skippedProcessIds: skippedProcessIds.slice(0, MAX_GPU_PRIORITY_DIAGNOSTIC_TARGETS),
failedProcessIds: failedProcessIds.slice(0, MAX_GPU_PRIORITY_DIAGNOSTIC_TARGETS),
};
if (elevatedProcessIds.length > 0 || failedProcessIds.length > 0) {
log.info('[StreamingPriority] Elevated GPU scheduling priority', {
priorityClass: GPU_SCHEDULING_PRIORITY,
targets: lastGpuPriorityAcquire.targets,
elevatedProcessIds: lastGpuPriorityAcquire.elevatedProcessIds,
skippedProcessIds: lastGpuPriorityAcquire.skippedProcessIds,
failedProcessIds: lastGpuPriorityAcquire.failedProcessIds,
});
}
}
function refreshGpuSchedulingPriority(): void {
if (refCount <= 0) return;
const targets = collectGpuSchedulingPriorityTargets();
elevateProcessPriorityTargets(targets);
elevateGpuSchedulingPriority(targets);
}
function startGpuPriorityRefresh(): void {
if (process.platform !== 'win32' || GPU_SCHEDULING_PRIORITY === null || gpuPriorityRefreshTimer !== null) return;
gpuPriorityRefreshTimer = setInterval(refreshGpuSchedulingPriority, GPU_PRIORITY_REFRESH_INTERVAL_MS);
gpuPriorityRefreshTimer.unref?.();
}
function stopGpuPriorityRefresh(): void {
if (gpuPriorityRefreshTimer === null) return;
clearInterval(gpuPriorityRefreshTimer);
gpuPriorityRefreshTimer = null;
}
function restoreGpuSchedulingPriority(): void {
if (gpuPriorityElevatedProcessIds.size === 0) {
lastGpuPriorityRestore = {
status: 'no-active-priority',
processIds: [],
restoredProcessIds: [],
failedProcessIds: [],
};
return;
}
const processIds = [...gpuPriorityElevatedProcessIds.keys()];
gpuPriorityElevatedProcessIds.clear();
const addon = loadWindowsGpuPriorityModule();
if (!addon?.restoreGpuSchedulingPriority) {
lastGpuPriorityRestore = {
status: 'native-module-unavailable',
processIds,
restoredProcessIds: [],
failedProcessIds: processIds.map((processId) => ({
processId,
reason: gpuPriorityModuleUnavailableReason(),
})),
detail: gpuPriorityModuleLoadErrorDetail ?? GPU_PRIORITY_MODULE_UNAVAILABLE_DETAIL,
};
log.debug('[StreamingPriority] Cannot restore GPU scheduling priority; native module unavailable', {
processIds,
detail: lastGpuPriorityRestore.detail,
});
return;
}
const restoredProcessIds: Array<number> = [];
const failedProcessIds: Array<GpuPriorityFailure> = [];
for (const processId of processIds) {
try {
if (addon.restoreGpuSchedulingPriority(processId)) {
restoredProcessIds.push(processId);
} else {
failedProcessIds.push({processId, reason: 'native API returned false'});
}
} catch (error) {
failedProcessIds.push({processId, reason: formatErrorDetail(error)});
log.debug('[StreamingPriority] Failed to restore GPU scheduling priority', {processId, error});
}
}
lastGpuPriorityRestore = {
status: failedProcessIds.length === 0 ? 'succeeded' : restoredProcessIds.length > 0 ? 'partial' : 'failed',
processIds,
restoredProcessIds,
failedProcessIds,
};
if (failedProcessIds.length > 0) {
log.debug('[StreamingPriority] Failed to restore GPU scheduling priority for some processes', {
processIds: failedProcessIds,
});
} else {
log.info('[StreamingPriority] Restored GPU scheduling priority', {processIds: restoredProcessIds});
}
}
export function getStreamingPriorityDiagnostics(): StreamingPriorityDiagnostics {
return {
refCount,
powerSaveBlocker: {
active: powerBlockerId !== null,
id: powerBlockerId,
},
processPriority: {
supported: CAN_ELEVATE_PROCESS_PRIORITY,
streamingPriority: STREAMING_PRIORITY,
savedPriority: savedProcessPriorities.get(process.pid) ?? null,
elevated: savedProcessPriorities.size > 0,
elevatedProcesses: [...savedProcessPriorities.entries()].map(([processId, savedPriority]) => ({
processId,
savedPriority,
})),
},
gpuScheduling: {
supported: process.platform === 'win32' && GPU_SCHEDULING_PRIORITY !== null,
priorityClass: GPU_SCHEDULING_PRIORITY,
env: GPU_SCHEDULING_PRIORITY_ENV,
nativeModuleStatus: getGpuPriorityModuleStatus(),
nativeModuleLoadErrorDetail: gpuPriorityModuleLoadErrorDetail,
refreshActive: gpuPriorityRefreshTimer !== null,
refreshIntervalMs: GPU_PRIORITY_REFRESH_INTERVAL_MS,
trackedWebContents: streamingPriorityWebContents.size,
elevatedProcesses: [...gpuPriorityElevatedProcessIds.entries()].map(([processId, priorityClass]) => ({
processId,
priorityClass,
})),
lastAcquire: cloneGpuPriorityAcquireDiagnostics(lastGpuPriorityAcquire),
lastRestore: cloneGpuPriorityRestoreDiagnostics(lastGpuPriorityRestore),
},
};
}
export function acquireStreamingPriority(webContents?: Electron.WebContents): void {
rememberStreamingPriorityWebContents(webContents);
if (webContents && !webContents.isDestroyed()) {
setBackgroundThrottlingBypass(webContents, true);
}
refCount++;
if (refCount === 1) {
startPowerSaveBlocker();
startGpuPriorityRefresh();
log.info('[StreamingPriority] Acquired', {refCount});
}
const targets = collectGpuSchedulingPriorityTargets(webContents);
elevateProcessPriorityTargets(targets);
elevateGpuSchedulingPriority(targets);
}
export function releaseStreamingPriority(): void {
if (refCount <= 0) return;
refCount--;
if (refCount === 0) {
stopGpuPriorityRefresh();
restoreBackgroundThrottling();
clearStreamingPriorityWebContents();
stopPowerSaveBlocker();
restoreProcessPriority();
restoreGpuSchedulingPriority();
stopWindowsScreenCaptureGuard('streaming-priority-release');
reportWindowsCaptureItemReuse('streaming-priority-release');
log.info('[StreamingPriority] Released');
}
}
export function resetStreamingPriority(): void {
if (refCount === 0) return;
refCount = 0;
stopGpuPriorityRefresh();
restoreBackgroundThrottling();
clearStreamingPriorityWebContents();
stopPowerSaveBlocker();
restoreProcessPriority();
restoreGpuSchedulingPriority();
stopWindowsScreenCaptureGuard('streaming-priority-reset');
reportWindowsCaptureItemReuse('streaming-priority-reset');
log.info('[StreamingPriority] Reset');
}