File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -34,6 +34,7 @@ import type { Signal as SignalModel } from 'brn/schemas/signal';
3434import Intl from 'ember-intl/services/intl' ;
3535import { PolySynth , Synth , SynthOptions } from 'tone' ;
3636import UserDataService from './user-data' ;
37+ import StudyingTimerService from './studying-timer' ;
3738import type { Exercise } from 'brn/schemas/exercise' ;
3839
3940type ISourceCollection = ( ISource | IToneSource | null ) [ ] ;
@@ -52,6 +53,7 @@ export default class AudioService extends Service {
5253 @service ( 'stats' ) declare stats : StatsService ;
5354 @service ( 'intl' ) declare intl : Intl ;
5455 @service ( 'user-data' ) declare userData : UserDataService ;
56+ @service ( 'studying-timer' ) declare studyingTimer : StudyingTimerService ;
5557 context ! : AudioContext ;
5658
5759 willDestroy ( ) : void {
@@ -218,6 +220,7 @@ export default class AudioService extends Service {
218220
219221 @action
220222 async playAudio ( ) {
223+ this . studyingTimer . resetIdle ( ) ;
221224 try {
222225 if ( ! isTesting ( ) ) {
223226 await this . playTask . perform ( ) ;
Original file line number Diff line number Diff line change @@ -66,14 +66,28 @@ export default class StudyingTimerService extends Service {
6666 }
6767 @action
6868 maybeIdlePause ( ) {
69- // Pause cascades into audio.stop() via task-player.onPauseStateChanged,
70- // which would interrupt exercises whenever the user stops moving the mouse.
69+ // resetIdle() (invoked on every playAudio) is the primary mechanism that
70+ // keeps the watcher from firing mid-sequence. This guard is a defensive
71+ // backstop in case a single clip ever outruns the idle window: pause
72+ // cascades into audio.stop() via task-player.onPauseStateChanged, which
73+ // would interrupt exercises whenever the user stops moving the mouse.
7174 if ( this . audio . isPlaying ) {
7275 return ;
7376 }
7477 this . pause ( ) ;
7578 }
7679 @action
80+ resetIdle ( ) {
81+ if ( this . idleWatcher ) {
82+ try {
83+ this . idleWatcher . stop ( ) ;
84+ this . idleWatcher . start ( ) ;
85+ } catch ( _e ) {
86+ // idle-js may not support stop/start cycle in some edge cases
87+ }
88+ }
89+ }
90+ @action
7791 async startIdleWatcher ( ) {
7892 if ( isTesting ( ) ) {
7993 return ;
Original file line number Diff line number Diff line change @@ -36,4 +36,43 @@ module('Unit | Service | studying-timer', function (hooks) {
3636 assert . true ( timer . isPaused ) ;
3737 } ) ;
3838 } ) ;
39+
40+ module ( 'resetIdle' , function ( ) {
41+ test ( 'rearms the idle watcher via stop/start' , function ( assert ) {
42+ const timer = this . owner . lookup ( 'service:studying-timer' ) ;
43+ let stopCount = 0 ;
44+ let startCount = 0 ;
45+ timer . idleWatcher = {
46+ stop ( ) {
47+ stopCount ++ ;
48+ } ,
49+ start ( ) {
50+ startCount ++ ;
51+ } ,
52+ } ;
53+ timer . resetIdle ( ) ;
54+ assert . strictEqual ( stopCount , 1 , 'idleWatcher.stop called once' ) ;
55+ assert . strictEqual ( startCount , 1 , 'idleWatcher.start called once' ) ;
56+ } ) ;
57+
58+ test ( 'does not toggle isPaused' , function ( assert ) {
59+ // Regression guard: resetIdle must not clear a user-initiated pause.
60+ const timer = this . owner . lookup ( 'service:studying-timer' ) ;
61+ timer . idleWatcher = { stop ( ) { } , start ( ) { } } ;
62+ timer . pause ( ) ;
63+ timer . resetIdle ( ) ;
64+ assert . true ( timer . isPaused , 'user pause remains sticky after resetIdle' ) ;
65+ } ) ;
66+
67+ test ( 'is a no-op when idleWatcher is null' , function ( assert ) {
68+ const timer = this . owner . lookup ( 'service:studying-timer' ) ;
69+ timer . idleWatcher = null ;
70+ try {
71+ timer . resetIdle ( ) ;
72+ assert . ok ( true , 'did not throw' ) ;
73+ } catch ( _e ) {
74+ assert . ok ( false , 'resetIdle threw with null idleWatcher' ) ;
75+ }
76+ } ) ;
77+ } ) ;
3978} ) ;
You can’t perform that action at this time.
0 commit comments