// Uses an existing, authorized media element; does not export a changed file. async function observeTeachingPlaybackRate(media, rate, milliseconds = 450) { if (![1, 1.25].includes(rate)) throw new Error('this exercise allows only 1 or 1.25'); if (!(Number.isFinite(media.duration) && media.duration > 1.5)) throw new Error('loaded teaching media longer than 1.5 seconds required'); media.pause(); media.currentTime = 0; if (media.seeking) await new Promise(resolve => media.addEventListener('seeked', resolve, {once:true})); media.playbackRate = rate; const pitchPropertyExists = 'preservesPitch' in media; if (pitchPropertyExists) media.preservesPitch = true; await media.play(); const start = media.currentTime, wallStart = performance.now(); await new Promise(resolve => setTimeout(resolve, milliseconds)); media.pause(); return {requestedRate:rate, acceptedRate:media.playbackRate, duration:media.duration, mediaSecondsAdvanced:media.currentTime-start, wallSecondsObserved:(performance.now()-wallStart)/1000, pitchPropertyExists, preservesPitch: pitchPropertyExists ? media.preservesPitch : null, pitchQualityListened:false, TTSGenerated:false, changedFileExported:false}; }