| /** |
| * @module modules/play-sk |
| * @description A playback controller for looping over a sequence. |
| * |
| * @evt mode-changed-manually - After the user clicks the play/pause button |
| * (but not if you set the mode property from code) |
| * detail: {mode: PlayMode} // 'play' or 'pause' |
| * @evt moveto - Indicating that the playback module wants to move the application to a new item |
| * The application should handle the event by updating itself to show the new item, then |
| * call movedTo(item) when finished to indicate playback may resume |
| * detail: {item: number} // the index the play module wants the app |
| * to move to. |
| */ |
| import { define } from 'elements-sk/define'; |
| import { html, TemplateResult } from 'lit-html'; |
| import { ElementSk } from '../../../infra-sk/modules/ElementSk'; |
| import 'elements-sk/icon/skip-previous-icon-sk'; |
| import 'elements-sk/icon/keyboard-arrow-left-icon-sk'; |
| import 'elements-sk/icon/play-arrow-icon-sk'; |
| import 'elements-sk/icon/pause-icon-sk'; |
| import 'elements-sk/icon/keyboard-arrow-right-icon-sk'; |
| import 'elements-sk/icon/skip-next-icon-sk'; |
| import 'elements-sk/icon/video-library-icon-sk'; |
| import { |
| ModeChangedManuallyEvent, |
| MoveToEvent, |
| PlayMode, |
| ModeChangedManuallyEventDetail, |
| MoveToEventDetail, |
| } from '../events'; |
| |
| export class PlaySk extends ElementSk { |
| private static template = (ele: PlaySk) => { |
| if (ele.visual === 'simple') { |
| return PlaySk.simpleTemplate(ele); |
| } |
| return PlaySk.fullTemplate(ele); |
| } |
| |
| private static fullTemplate = (ele: PlaySk) => html` |
| <div class="horizontal-flex"> |
| <div class='filler'></div> |
| <skip-previous-icon-sk title="Go to first" @click=${ele.begin} |
| ></skip-previous-icon-sk> |
| <keyboard-arrow-left-icon-sk title="Step back one (,)" @click=${ele.prev} |
| ></keyboard-arrow-left-icon-sk> |
| ${ele._playPauseIcon(ele)} |
| <keyboard-arrow-right-icon-sk title="Step forward one (.)" @click=${ele.next} |
| ></keyboard-arrow-right-icon-sk> |
| <skip-next-icon-sk title="Go to last" @click=${ele.end}></skip-next-icon-sk> |
| <div class='filler'></div> |
| <label>Delay in ms</label> |
| <input value="${ele._playbackDelay}" class=delay-input @change=${ele._delayChanged}> |
| </div>`; |
| |
| private static simpleTemplate = (ele: PlaySk) => html`<video-library-icon-sk title="Play/Pause" @click=${ele.togglePlay} |
| id='play-button-v'></video-library-icon-sk>`; |
| |
| private _mode: PlayMode = 'pause'; |
| |
| // current position in sequence |
| private _item: number = 0; |
| |
| // length of sequence |
| private _size: number = 2; |
| |
| // target number of milliseconds to wait between playback steps |
| private _playbackDelay: number = 0; |
| |
| // time at which the last moveto event was emitted |
| private _lastMoveTime: number = 0; |
| |
| // reference to a timeout we set so we can cancel it if necessary |
| private _timeout: number = 0; |
| |
| /** |
| * Specifies the visual style of the playback element. |
| * Possible values include: |
| * 'full': shows all five buttons and a textbox for controlling delay. |
| * 'simple': shows only a play button, using a distinct icon. |
| */ |
| public visual = 'full'; |
| |
| constructor() { |
| super(PlaySk.template); |
| } |
| |
| connectedCallback(): void { |
| super.connectedCallback(); |
| this._render(); |
| } |
| |
| disconnectedCallback(): void { |
| if (this._timeout) window.clearTimeout(this._timeout); |
| } |
| |
| get mode(): PlayMode { |
| return this._mode; |
| } |
| |
| // Valid values: 'play' or 'pause' |
| set mode(m: PlayMode) { |
| this._mode = m; |
| if (m === 'play') { |
| this.next(); |
| } else if (this._timeout) { |
| console.log(`paused on ${this._item}`); |
| window.clearTimeout(this._timeout); |
| } |
| this._render(); |
| } |
| |
| get size(): number { |
| return this._size; |
| } |
| |
| set size(s: number) { |
| this._size = s; |
| this._item = 0; |
| } |
| |
| set playbackDelay(ms: number) { |
| this._playbackDelay = ms; |
| this._render(); |
| } |
| |
| private _delayChanged(e: Event): void { |
| this._playbackDelay = parseInt((e.target as HTMLInputElement).value, 10); |
| } |
| |
| // Call this after handling the moveto event to indicate playback may proceed. |
| // The application may also call this at any time to indicate it has skipped directly |
| // to an item. |
| movedTo(item: number): void { |
| this._item = item; |
| if (this._mode === 'play') { |
| // wait out the remainder of the minimum playback delay |
| const elapsed = Date.now() - this._lastMoveTime; |
| const remainingMs = Math.max(0, this._playbackDelay - elapsed); |
| // Must be done with timeout, even if it's zero, or we exceed call stack size |
| this._timeout = window.setTimeout(() => { this.next(); }, remainingMs); |
| } |
| } |
| |
| // template helper deciding which icon to show in the play button spot |
| private _playPauseIcon(ele: PlaySk): TemplateResult { |
| if (this._mode === 'pause') { |
| return html`<play-arrow-icon-sk title="Play/Pause" @click=${ele.togglePlay} |
| id='play-button'></play-arrow-icon-sk>`; |
| } |
| return html`<pause-icon-sk title="Play/Pause" @click=${ele.togglePlay} |
| ></pause-icon-sk>`; |
| } |
| |
| togglePlay(): void { |
| this.mode = (this._mode === 'play') ? 'pause' : 'play'; |
| this.dispatchEvent( |
| new CustomEvent<ModeChangedManuallyEventDetail>( |
| ModeChangedManuallyEvent, { |
| detail: { mode: this._mode }, |
| bubbles: true, |
| }, |
| ), |
| ); |
| } |
| |
| // sends the moveto event |
| private _triggerEvent(): void { |
| if (this._timeout) { |
| window.clearTimeout(this._timeout); |
| } |
| this._lastMoveTime = Date.now(); |
| this.dispatchEvent( |
| new CustomEvent<MoveToEventDetail>( |
| MoveToEvent, { |
| detail: { item: this._item }, |
| bubbles: true, |
| }, |
| ), |
| ); |
| } |
| |
| begin(): void { |
| this._item = 0; |
| this._triggerEvent(); |
| } |
| |
| end(): void { |
| this._item = this._size - 1; |
| this._triggerEvent(); |
| } |
| |
| prev(): void { |
| this._item -= 1; |
| if (this._item < 0) { |
| this._item += this._size; |
| } |
| this._triggerEvent(); |
| } |
| |
| next(): void { |
| this._item = (this._item + 1) % this._size; |
| this._triggerEvent(); |
| } |
| } |
| |
| define('play-sk', PlaySk); |