import type { IPlaybackDirection , IPlaybackRange , } from '@theatre/core/sequences/Sequence' ; import { defer } from '@theatre/shared/utils/defer' ; import noop from '@theatre/shared/utils/noop' ; import type { Pointer , Ticker } from '@theatre/dataverse' ; import { Atom } from '@theatre/dataverse' ; export interface IPlaybackState { position : number ; playing : boolean ; } export interface IPlaybackController { getCurrentPosition ( ) : number ; gotoPosition ( position ) : void ; readonly statePointer : Pointer < IPlaybackState > ; destroy ( ) : void ; play ( iterationCount , range , rate , direction ) : Promise < boolean > ; pause ( ) : void ; } export default class DefaultPlaybackController implements IPlaybackController { _stopPlayCallback : ( ) => void = noop ; private _state : Atom < IPlaybackState > = new Atom < IPlaybackState > ( { position : 0 , playing : false , } ) ; readonly statePointer : Pointer < IPlaybackState > ; constructor ( private readonly _ticker ) { this . statePointer = this . _state . pointer ; } destroy ( ) { } pause ( ) { this . _stopPlayCallback ( ) ; this . playing = false ; this . _stopPlayCallback = noop ; } gotoPosition ( time ) { this . _updatePositionInState ( time ) ; } private _updatePositionInState ( time ) { this . _state . reduceState ( [ 'position' ] , ( ) => time ) ; } getCurrentPosition ( ) { return this . _state . getState ( ) . position ; } get playing ( ) { return this . _state . getState ( ) . playing ; } set playing ( playing ) { this . _state . setIn ( [ 'playing' ] , playing ) ; } play ( iterationCount , range , rate , direction ) { if ( this . playing ) { this . pause ( ) ; } this . playing = true ; const ticker = this . _ticker ; const iterationLength = range [ 1 ] - range [ 0 ] ; { const startPos = this . getCurrentPosition ( ) ; if ( startPos < range [ 0 ] || startPos > range [ 1 ] ) { if ( direction === 'normal' || direction === 'alternate' ) { this . _updatePositionInState ( range [ 0 ] ) ; } else if ( direction === 'reverse' || direction === 'alternateReverse' ) { this . _updatePositionInState ( range [ 1 ] ) ; } } else if ( direction === 'normal' || direction === 'alternate' ) { if ( startPos === range [ 1 ] ) { this . _updatePositionInState ( range [ 0 ] ) ; } } else { if ( startPos === range [ 0 ] ) { this . _updatePositionInState ( range [ 1 ] ) ; } } } const deferred = defer < boolean > ( ) ; const initialTickerTime = ticker . time ; const totalPlaybackLength = iterationLength * iterationCount ; let initialElapsedPos = this . getCurrentPosition ( ) - range [ 0 ] ; if ( direction === 'reverse' || direction === 'alternateReverse' ) { initialElapsedPos = range [ 1 ] - this . getCurrentPosition ( ) ; } const tick = ( currentTickerTime ) => { const elapsedTickerTime = Math . max ( currentTickerTime - initialTickerTime , 0 ) ; const elapsedTickerTimeInSeconds = elapsedTickerTime / 1000 ; const elapsedPos = Math . min ( elapsedTickerTimeInSeconds * rate + initialElapsedPos , totalPlaybackLength ) ; if ( elapsedPos !== totalPlaybackLength ) { const iterationNumber = Math . floor ( elapsedPos / iterationLength ) ; let currentIterationPos = ( ( elapsedPos / iterationLength ) % 1 ) * iterationLength ; if ( direction !== 'normal' ) { if ( direction === 'reverse' ) { currentIterationPos = iterationLength - currentIterationPos ; } else { const isCurrentIterationNumberEven = iterationNumber % 2 === 0 ; if ( direction === 'alternate' ) { if ( ! isCurrentIterationNumberEven ) { currentIterationPos = iterationLength - currentIterationPos ; } } else { if ( isCurrentIterationNumberEven ) { currentIterationPos = iterationLength - currentIterationPos ; } } } } this . _updatePositionInState ( currentIterationPos + range [ 0 ] ) ; requestNextTick ( ) ; } else { if ( direction === 'normal' ) { this . _updatePositionInState ( range [ 1 ] ) ; } else if ( direction === 'reverse' ) { this . _updatePositionInState ( range [ 0 ] ) ; } else { const isLastIterationEven = ( iterationCount - 1 ) % 2 === 0 ; if ( direction === 'alternate' ) { if ( isLastIterationEven ) { this . _updatePositionInState ( range [ 1 ] ) ; } else { this . _updatePositionInState ( range [ 0 ] ) ; } } else { if ( isLastIterationEven ) { this . _updatePositionInState ( range [ 0 ] ) ; } else { this . _updatePositionInState ( range [ 1 ] ) ; } } } this . playing = false ; deferred . resolve ( true ) ; } } ; this . _stopPlayCallback = ( ) => { ticker . offThisOrNextTick ( tick ) ; ticker . offNextTick ( tick ) ; if ( this . playing ) deferred . resolve ( false ) ; } ; const requestNextTick = ( ) => ticker . onNextTick ( tick ) ; ticker . onThisOrNextTick ( tick ) ; return deferred . promise ; } }