diff --git a/packages/2d/src/lib/components/Media.ts b/packages/2d/src/lib/components/Media.ts index 784436aa..334a1d69 100644 --- a/packages/2d/src/lib/components/Media.ts +++ b/packages/2d/src/lib/components/Media.ts @@ -1,160 +1,159 @@ import { - DependencyContext, - PlaybackState, - SignalValue, - SimpleSignal, - clamp, - isReactive, - useLogger, - useThread, - } from '@motion-canvas/core'; - import { initial, signal, computed } from '../decorators'; - import { Rect, RectProps } from './Rect'; - import reactivePlaybackRate from './__logs__/reactive-playback-rate.md'; - - export interface MediaProps extends RectProps { - src?: SignalValue; - loop?: SignalValue; - playbackRate?: number; - time?: SignalValue; - play?: boolean; - } - - export abstract class Media extends Rect { - @signal() - public declare readonly src: SimpleSignal; - - @initial(false) - @signal() - public declare readonly loop: SimpleSignal; - - @initial(1) - @signal() - public declare readonly playbackRate: SimpleSignal; - - @initial(0) - @signal() - protected declare readonly time: SimpleSignal; - - @initial(false) - @signal() - protected declare readonly playing: SimpleSignal; - - protected lastTime = -1; - - public constructor(props: MediaProps) { - super(props); - if (props.play) { - this.play(); - } - } - - public isPlaying(): boolean { - return this.playing(); - } - - public getCurrentTime(): number { - return this.clampTime(this.time()); - } - - public getDuration(): number { - return this.mediaElement().duration; - } - - @computed() - public override completion(): number { - return this.clampTime(this.time()) / this.getDuration(); - } - - protected abstract mediaElement(): HTMLMediaElement; - - protected abstract seekedMedia(): HTMLMediaElement; - - protected abstract fastSeekedMedia(): HTMLMediaElement; - - protected abstract override draw(context: CanvasRenderingContext2D): void; - - protected setCurrentTime(value: number) { - const media = this.mediaElement(); - if (media.readyState < 2) return; - - media.currentTime = value; - this.lastTime = value; - if (media.seeking) { - DependencyContext.collectPromise( - new Promise(resolve => { - const listener = () => { - resolve(); - media.removeEventListener('seeked', listener); - }; - media.addEventListener('seeked', listener); - }), - ); - } - } - - protected setPlaybackRate(playbackRate: number) { - let value: number; - if (isReactive(playbackRate)) { - value = playbackRate(); - useLogger().warn({ - message: 'Invalid value set as the playback rate', - remarks: reactivePlaybackRate, - inspect: this.key, - stack: new Error().stack, - }); - } else { - value = playbackRate; - } - this.playbackRate.context.setter(value); - - if (this.playing()) { - if (value === 0) { - this.pause(); - } else { - const time = useThread().time; - const start = time(); - const offset = this.time(); - this.time(() => this.clampTime(offset + (time() - start) * value)); - } - } + DependencyContext, + SignalValue, + SimpleSignal, + clamp, + isReactive, + useLogger, + useThread, +} from '@motion-canvas/core'; +import {computed, initial, signal} from '../decorators'; +import {Rect, RectProps} from './Rect'; +import reactivePlaybackRate from './__logs__/reactive-playback-rate.md'; + +export interface MediaProps extends RectProps { + src?: SignalValue; + loop?: SignalValue; + playbackRate?: number; + time?: SignalValue; + play?: boolean; +} + +export abstract class Media extends Rect { + @signal() + public declare readonly src: SimpleSignal; + + @initial(false) + @signal() + public declare readonly loop: SimpleSignal; + + @initial(1) + @signal() + public declare readonly playbackRate: SimpleSignal; + + @initial(0) + @signal() + protected declare readonly time: SimpleSignal; + + @initial(false) + @signal() + protected declare readonly playing: SimpleSignal; + + protected lastTime = -1; + + public constructor(props: MediaProps) { + super(props); + if (props.play) { + this.play(); } - - public play() { - const time = useThread().time; - const start = time(); - const offset = this.time(); - const playbackRate = this.playbackRate(); - this.playing(true); - this.time(() => this.clampTime(offset + (time() - start) * playbackRate)); + } + + public isPlaying(): boolean { + return this.playing(); + } + + public getCurrentTime(): number { + return this.clampTime(this.time()); + } + + public getDuration(): number { + return this.mediaElement().duration; + } + + @computed() + public override completion(): number { + return this.clampTime(this.time()) / this.getDuration(); + } + + protected abstract mediaElement(): HTMLMediaElement; + + protected abstract seekedMedia(): HTMLMediaElement; + + protected abstract fastSeekedMedia(): HTMLMediaElement; + + protected abstract override draw(context: CanvasRenderingContext2D): void; + + protected setCurrentTime(value: number) { + const media = this.mediaElement(); + if (media.readyState < 2) return; + + media.currentTime = value; + this.lastTime = value; + if (media.seeking) { + DependencyContext.collectPromise( + new Promise(resolve => { + const listener = () => { + resolve(); + media.removeEventListener('seeked', listener); + }; + media.addEventListener('seeked', listener); + }), + ); } - - public pause() { - this.playing(false); - this.time.save(); - this.mediaElement().pause(); + } + + protected setPlaybackRate(playbackRate: number) { + let value: number; + if (isReactive(playbackRate)) { + value = playbackRate(); + useLogger().warn({ + message: 'Invalid value set as the playback rate', + remarks: reactivePlaybackRate, + inspect: this.key, + stack: new Error().stack, + }); + } else { + value = playbackRate; } - - public seek(time: number) { - const playing = this.playing(); - this.time(this.clampTime(time)); - if (playing) { - this.play(); - } else { + this.playbackRate.context.setter(value); + + if (this.playing()) { + if (value === 0) { this.pause(); + } else { + const time = useThread().time; + const start = time(); + const offset = this.time(); + this.time(() => this.clampTime(offset + (time() - start) * value)); } } - - public clampTime(time: number): number { - const duration = this.getDuration(); - if (this.loop()) { - time %= duration; - } - return clamp(0, duration, time); + } + + public play() { + const time = useThread().time; + const start = time(); + const offset = this.time(); + const playbackRate = this.playbackRate(); + this.playing(true); + this.time(() => this.clampTime(offset + (time() - start) * playbackRate)); + } + + public pause() { + this.playing(false); + this.time.save(); + this.mediaElement().pause(); + } + + public seek(time: number) { + const playing = this.playing(); + this.time(this.clampTime(time)); + if (playing) { + this.play(); + } else { + this.pause(); } - - protected override collectAsyncResources() { - super.collectAsyncResources(); - this.seekedMedia(); + } + + public clampTime(time: number): number { + const duration = this.getDuration(); + if (this.loop()) { + time %= duration; } - } \ No newline at end of file + return clamp(0, duration, time); + } + + protected override collectAsyncResources() { + super.collectAsyncResources(); + this.seekedMedia(); + } +} diff --git a/packages/2d/src/lib/components/Video.ts b/packages/2d/src/lib/components/Video.ts index 11ea731b..249298c1 100644 --- a/packages/2d/src/lib/components/Video.ts +++ b/packages/2d/src/lib/components/Video.ts @@ -5,17 +5,11 @@ import { SerializedVector2, SignalValue, SimpleSignal, - clamp, - isReactive, - useLogger, - useThread, } from '@motion-canvas/core'; import {computed, initial, nodeName, signal} from '../decorators'; import {DesiredLength} from '../partials'; import {drawImage} from '../utils'; -import {Rect, RectProps} from './Rect'; -import reactivePlaybackRate from './__logs__/reactive-playback-rate.md'; -import { Media, MediaProps } from './Media'; +import {Media, MediaProps} from './Media'; export interface VideoProps extends MediaProps { /**