Voice diagnostics: track IDs before/after replaceTrack, WebRTC bytesSent, mic aria-label
Made-with: Cursor
This commit is contained in:
parent
5f7dc60376
commit
7c00e31cfa
2 changed files with 91 additions and 5 deletions
|
|
@ -94,21 +94,48 @@ export class AudioProcedure {
|
||||||
const ttsTrack = streamDest.stream.getAudioTracks()?.[0];
|
const ttsTrack = streamDest.stream.getAudioTracks()?.[0];
|
||||||
if (!ttsTrack) return { replaced: 0, pcs: pcs?.length || 0, reason: 'no-tts-track' };
|
if (!ttsTrack) return { replaced: 0, pcs: pcs?.length || 0, reason: 'no-tts-track' };
|
||||||
|
|
||||||
|
// #region agent log
|
||||||
|
const diag: Record<string, any> = {
|
||||||
|
ttsTrackId: ttsTrack.id,
|
||||||
|
ttsTrackEnabled: ttsTrack.enabled,
|
||||||
|
ttsTrackReadyState: ttsTrack.readyState,
|
||||||
|
ttsTrackMuted: ttsTrack.muted,
|
||||||
|
beforeSenderTrackIds: [] as string[],
|
||||||
|
afterSenderTrackIds: [] as string[],
|
||||||
|
};
|
||||||
|
// #endregion
|
||||||
|
|
||||||
let replaced = 0;
|
let replaced = 0;
|
||||||
for (const pc of pcs) {
|
for (const pc of pcs) {
|
||||||
try {
|
try {
|
||||||
const senders = pc.getSenders?.() || [];
|
const senders = pc.getSenders?.() || [];
|
||||||
for (const sender of senders) {
|
for (const sender of senders) {
|
||||||
if (sender?.track?.kind === 'audio') {
|
if (sender?.track?.kind === 'audio') {
|
||||||
|
// #region agent log
|
||||||
|
diag.beforeSenderTrackIds.push(sender.track.id);
|
||||||
|
// #endregion
|
||||||
await sender.replaceTrack(ttsTrack);
|
await sender.replaceTrack(ttsTrack);
|
||||||
replaced++;
|
replaced++;
|
||||||
|
// #region agent log
|
||||||
|
const afterTrack = sender.track;
|
||||||
|
diag.afterSenderTrackIds.push(afterTrack?.id || 'null');
|
||||||
|
diag.afterSenderTrackEnabled = afterTrack?.enabled;
|
||||||
|
diag.afterSenderTrackReadyState = afterTrack?.readyState;
|
||||||
|
// Force track enabled just in case
|
||||||
|
if (afterTrack && !afterTrack.enabled) {
|
||||||
|
afterTrack.enabled = true;
|
||||||
|
diag.forcedEnabled = true;
|
||||||
|
}
|
||||||
|
// #endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch (err: any) {
|
||||||
// ignore per peer connection
|
// #region agent log
|
||||||
|
diag.error = String(err?.message || err);
|
||||||
|
// #endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { replaced, pcs: pcs?.length || 0, reason: 'ok' };
|
return { replaced, pcs: pcs?.length || 0, reason: 'ok', diag };
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -241,9 +268,33 @@ export class AudioProcedure {
|
||||||
}
|
}
|
||||||
return { replaced: 0, pcs: 0, reason: 'force-function-missing' };
|
return { replaced: 0, pcs: 0, reason: 'force-function-missing' };
|
||||||
});
|
});
|
||||||
|
// #region agent log
|
||||||
|
const diag = senderInjectInfo?.diag || {};
|
||||||
this._logger.info(
|
this._logger.info(
|
||||||
`TTS sender injection: replaced=${senderInjectInfo?.replaced ?? 0}, pcs=${senderInjectInfo?.pcs ?? 0}, reason=${senderInjectInfo?.reason || 'n/a'}`,
|
`TTS sender injection: replaced=${senderInjectInfo?.replaced ?? 0}, pcs=${senderInjectInfo?.pcs ?? 0}, reason=${senderInjectInfo?.reason || 'n/a'} ` +
|
||||||
|
`ttsTrack=${diag.ttsTrackId || 'n/a'}(enabled=${diag.ttsTrackEnabled},state=${diag.ttsTrackReadyState},muted=${diag.ttsTrackMuted}) ` +
|
||||||
|
`beforeSenders=[${(diag.beforeSenderTrackIds || []).join(',')}] afterSenders=[${(diag.afterSenderTrackIds || []).join(',')}] ` +
|
||||||
|
`afterEnabled=${diag.afterSenderTrackEnabled} afterState=${diag.afterSenderTrackReadyState} forcedEnabled=${diag.forcedEnabled || false}`,
|
||||||
);
|
);
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
// Collect WebRTC stats BEFORE playback
|
||||||
|
// #region agent log
|
||||||
|
const statsBefore = await this._page.evaluate(async () => {
|
||||||
|
const pcs = ((window as any).__audioCapturePeerConnections || []) as RTCPeerConnection[];
|
||||||
|
let bytesSent = 0; let packetsSent = 0;
|
||||||
|
for (const pc of pcs) {
|
||||||
|
for (const s of (pc.getSenders?.() || [])) {
|
||||||
|
if (s?.track?.kind !== 'audio') continue;
|
||||||
|
try {
|
||||||
|
const stats = await s.getStats();
|
||||||
|
stats.forEach((r: any) => { if (r.type === 'outbound-rtp' && r.kind === 'audio') { bytesSent += Number(r.bytesSent || 0); packetsSent += Number(r.packetsSent || 0); } });
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { bytesSent, packetsSent };
|
||||||
|
});
|
||||||
|
// #endregion
|
||||||
|
|
||||||
await this._page.evaluate(async ({ audioData, format }) => {
|
await this._page.evaluate(async ({ audioData, format }) => {
|
||||||
const ctx = (window as any).__ttsAudioContext as AudioContext;
|
const ctx = (window as any).__ttsAudioContext as AudioContext;
|
||||||
|
|
@ -293,6 +344,38 @@ export class AudioProcedure {
|
||||||
});
|
});
|
||||||
}, { audioData, format });
|
}, { audioData, format });
|
||||||
|
|
||||||
|
// Collect WebRTC stats AFTER playback
|
||||||
|
// #region agent log
|
||||||
|
const statsAfter = await this._page.evaluate(async () => {
|
||||||
|
const pcs = ((window as any).__audioCapturePeerConnections || []) as RTCPeerConnection[];
|
||||||
|
let bytesSent = 0; let packetsSent = 0;
|
||||||
|
for (const pc of pcs) {
|
||||||
|
for (const s of (pc.getSenders?.() || [])) {
|
||||||
|
if (s?.track?.kind !== 'audio') continue;
|
||||||
|
try {
|
||||||
|
const stats = await s.getStats();
|
||||||
|
stats.forEach((r: any) => { if (r.type === 'outbound-rtp' && r.kind === 'audio') { bytesSent += Number(r.bytesSent || 0); packetsSent += Number(r.packetsSent || 0); } });
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Also check current sender track state
|
||||||
|
const senderInfo: any[] = [];
|
||||||
|
for (const pc of pcs) {
|
||||||
|
for (const s of (pc.getSenders?.() || [])) {
|
||||||
|
if (s?.track?.kind !== 'audio') continue;
|
||||||
|
senderInfo.push({ id: s.track.id, enabled: s.track.enabled, readyState: s.track.readyState, muted: s.track.muted });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { bytesSent, packetsSent, senderInfo };
|
||||||
|
});
|
||||||
|
this._logger.info(
|
||||||
|
`[Voice] WebRTC stats: before(bytes=${statsBefore.bytesSent},pkts=${statsBefore.packetsSent}) ` +
|
||||||
|
`after(bytes=${statsAfter.bytesSent},pkts=${statsAfter.packetsSent}) ` +
|
||||||
|
`delta(bytes=${statsAfter.bytesSent - statsBefore.bytesSent},pkts=${statsAfter.packetsSent - statsBefore.packetsSent}) ` +
|
||||||
|
`senders=${JSON.stringify(statsAfter.senderInfo)}`,
|
||||||
|
);
|
||||||
|
// #endregion
|
||||||
|
|
||||||
this._logger.info('Audio playback completed');
|
this._logger.info('Audio playback completed');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this._logger.error('Error playing audio:', error);
|
this._logger.error('Error playing audio:', error);
|
||||||
|
|
|
||||||
|
|
@ -520,11 +520,14 @@ export class BotOrchestrator {
|
||||||
checked,
|
checked,
|
||||||
dataCid: el.getAttribute('data-cid') || '',
|
dataCid: el.getAttribute('data-cid') || '',
|
||||||
title: el.getAttribute('title') || '',
|
title: el.getAttribute('title') || '',
|
||||||
|
ariaLabel: el.getAttribute('aria-label') || '',
|
||||||
tagName: el.tagName,
|
tagName: el.tagName,
|
||||||
looksMuted,
|
looksMuted,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
this._logger.info(`Mic state: checked=${state.checked}, data-cid="${state.dataCid}", tag=${state.tagName}, looksMuted=${state.looksMuted}`);
|
// #region agent log
|
||||||
|
this._logger.info(`Mic state: checked=${state.checked}, data-cid="${state.dataCid}", tag=${state.tagName}, looksMuted=${state.looksMuted}, ariaLabel="${state.ariaLabel}", title="${state.title}"`);
|
||||||
|
// #endregion
|
||||||
|
|
||||||
const needsClick = state.tagName === 'INPUT'
|
const needsClick = state.tagName === 'INPUT'
|
||||||
? !state.checked
|
? !state.checked
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue