| 232 | } |
| 233 | |
| 234 | export class MediaTimer implements ITimer { |
| 235 | private _ready: boolean = false |
| 236 | private _state: ITimerState = 'Paused' |
| 237 | private _onReadyCb: ITimerReadyCallback = NULL_FN |
| 238 | private _onTickCb: ITimerTickCallback = NULL_FN |
| 239 | private _onStateChangeCb: ITimerStateChangeCallback = NULL_FN |
| 240 | |
| 241 | private _disposes: IDisposable[] = [] |
| 242 | |
| 243 | constructor( |
| 244 | private _media: HTMLMediaElement, |
| 245 | private _ticker: ITicker = new AnimationFrameTicker() |
| 246 | ) { |
| 247 | this._disposes = [ |
| 248 | addDisposableDomListener(_media, 'error', () => { console.error('error') }), |
| 249 | addDisposableDomListener(_media, 'waiting', () => { console.log('waiting') }), |
| 250 | addDisposableDomListener(_media, 'durationchange', () => { console.log('durationchange') }), |
| 251 | addDisposableDomListener(_media, 'canplay', () => { this._ready = true; this._onReadyCb() }), |
| 252 | addDisposableDomListener(_media, 'play', () => { this._setState('Running') }), |
| 253 | addDisposableDomListener(_media, 'pause', () => { this._setState('Paused') }), |
| 254 | addDisposableDomListener(_media, 'ended', () => { |
| 255 | this.stop() |
| 256 | this._onTickCb(this.time) |
| 257 | }), |
| 258 | addDisposableDomListener(_media, 'seeking', () => { |
| 259 | if (this.isRunning()) { |
| 260 | this._ticker.stop() |
| 261 | } |
| 262 | }), |
| 263 | addDisposableDomListener(_media, 'seeked', () => { |
| 264 | if (this.isRunning()) { |
| 265 | this._ticker.start(this._tick.bind(this)) |
| 266 | } |
| 267 | }), |
| 268 | ] |
| 269 | } |
| 270 | |
| 271 | public get ready(): boolean { return this._ready } |
| 272 | public get progress(): number { return this._media.currentTime / this._media.duration } |
| 273 | public get duration(): number { return this._media.duration * 1000 } |
| 274 | public get state(): ITimerState { return this._state } |
| 275 | public get time(): number { return this._media.currentTime * 1000 } |
| 276 | public set time(t: number) { if (this._ready) { this._media.currentTime = t / 1000 } } |
| 277 | public get timescale(): number { return this._media.playbackRate } |
| 278 | public set timescale(s: number) { this._media.playbackRate = s } |
| 279 | |
| 280 | private _setState(state: ITimerState) { |
| 281 | if (this._state !== state) { |
| 282 | this._state = state |
| 283 | this._onStateChangeCb(this._state) |
| 284 | } |
| 285 | } |
| 286 | |
| 287 | private _tick() { |
| 288 | // In Safari, the audio currentTime may exceed it's duration, |
| 289 | // or a little bit less than the duration |
| 290 | if (this.isStopped() && this._ticker.running) { |
| 291 | this.stop() |
nothing calls this directly
no outgoing calls
no test coverage detected