Skip to content

feat(2d): add playbackRate signal to Video component - #831

Merged
aarthificial merged 1 commit into
motion-canvas:mainfrom
rmhartog:video-playback-rate
Nov 14, 2023
Merged

feat(2d): add playbackRate signal to Video component#831
aarthificial merged 1 commit into
motion-canvas:mainfrom
rmhartog:video-playback-rate

Conversation

@rmhartog

Copy link
Copy Markdown
Contributor

Added a playbackRate signal to the Video component that mirrors HTMLMediaElement.playbackRate.

Solves #711.

@aarthificial aarthificial left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't have a way to test it rn but shouldn't fastSeekedVideo set the playbackRate attribute on the actual video element too?
Seems like the current implementation would cause the video to constantly get out of sync with the playback.

Comment thread packages/2d/src/components/Video.ts Outdated
@rmhartog
rmhartog force-pushed the video-playback-rate branch from 1712e05 to 7f0b42d Compare October 25, 2023 06:16
@rmhartog

Copy link
Copy Markdown
Contributor Author

Don't have a way to test it rn but shouldn't fastSeekedVideo set the playbackRate attribute on the actual video element too? Seems like the current implementation would cause the video to constantly get out of sync with the playback.

Whenever the playbackRate is updated, it is also set on the video element. I've tested based on your comment, but did not find a scenario in which they'd get out of sync. Happy to dive into it further, but could you help me understand this scenario a little bit better?

@aarthificial

Copy link
Copy Markdown
Contributor

Sorry, didn't notice you were using the HTML attribute to store the value.
In that case though, wouldn't the value reset whenever the source changes?
For example:

import videoA from './videoA.mp4';
import videoB from './videoB.mp4';

// ...

const video = createRef<Video>();
view.add(<Video ref={video} src={videoA} playbackRate={2} />);

// this will reset the playback rate back to 1:
video().src(videoB);

@rmhartog

Copy link
Copy Markdown
Contributor Author

You're right, the current approach won't work in that scenario. I'll try to rewrite it by removing the getter and setter and reactively set the value on the video instead.

@aarthificial

aarthificial commented Oct 25, 2023

Copy link
Copy Markdown
Contributor

@rmhartog
If it helps, you can have a custom setter and getter while still storing the value in the signal like so:

class Example {
  @initial(1)
  @signal()
  public declare readonly playbackRate: SimpleSignal<number, this>;

  protected getPlaybackRate() {
    // invoke the native getter:
    return this.playbackRate.context.getter();
  }

  protected setPlaybackRate(playbackRate: number) {
    // invoke the native setter:
    this.playbackRate.context.setter(playbackRate);
  
    // do some other things:
    const video = this.video();
    video.playbackRate = playbackRate;

    if (playbackRate === 0) {
      this.pause();
    } else {
      const time = useThread().time;
      const start = time();
      const offset = this.time();
      this.time(() => this.clampTime(offset + (time() - start) * playbackRate));
    }
  }
}

@rmhartog
rmhartog force-pushed the video-playback-rate branch from 7f0b42d to 1a9efd5 Compare October 26, 2023 06:55
@rmhartog

Copy link
Copy Markdown
Contributor Author

Thanks @aarthificial, that was the missing piece! I've updated the code to imperatively update the time signal if the playbackRate changes and the video is playing, but the HTMLVideoElement.playbackRate is set reactively in the seekedVideo and fastSeekedVideo respectively.

Comment thread packages/2d/src/components/Video.ts Outdated
@rmhartog
rmhartog force-pushed the video-playback-rate branch from 1a9efd5 to b9253e3 Compare October 27, 2023 08:50
@rmhartog

Copy link
Copy Markdown
Contributor Author

Removed the superfluous getter method, think I've covered all comments @aarthificial!

@aarthificial aarthificial left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One more thing, setPlaybackRate pauses the video when the value is 0 but this behavior is not reflected in fastSeekVideo so there's a possibility that the video won't get paused after the source is changed. It should be easy to fix by expanding this condition at line 208:

const playing = this.playing() && time < video.duration;

Comment thread packages/2d/src/components/Video.ts Outdated
Comment thread packages/2d/src/components/Video.ts
@rmhartog
rmhartog force-pushed the video-playback-rate branch from b9253e3 to 9141acd Compare October 31, 2023 08:36
@rmhartog

Copy link
Copy Markdown
Contributor Author

Updated based on your feedback, last main question for me is the remarks of the log message. Not sure if it is necessary and/or how detailed it should be.

@aarthificial

Copy link
Copy Markdown
Contributor

I'd go for something like this:

The `playbackRate` of a `Video` cannot be reactive.

Make sure to use a concrete value and not a function:

```ts
// wrong ✗
video.playbackRate(() => 7);
// correct ✓
video.playbackRate(7);
```

If you're using a signal, extract its value before passing it to the property:

```ts
// wrong ✗
video.playbackRate(mySignal);
// correct ✓
video.playbackRate(mySignal());
```

Comment thread packages/2d/src/components/Video.ts
@aarthificial
aarthificial merged commit 5902b82 into motion-canvas:main Nov 14, 2023
@rmhartog
rmhartog deleted the video-playback-rate branch November 15, 2023 07:09
@screaminc

Copy link
Copy Markdown

@rmhartog @aarthificial Is there any limits on playbackrate values, can we go more than 16x?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants