Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 14, 2023

Conversation

rmhartog
Copy link
Contributor

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

Solves #711.

Copy link
Contributor

@aarthificial aarthificial left a comment

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.

@rmhartog rmhartog force-pushed the video-playback-rate branch from 1712e05 to 7f0b42d Compare October 25, 2023 06:16
@rmhartog
Copy link
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
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
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
Copy link
Contributor

aarthificial commented Oct 25, 2023

@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
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.

@rmhartog rmhartog force-pushed the video-playback-rate branch from 1a9efd5 to b9253e3 Compare October 27, 2023 08:50
@rmhartog
Copy link
Contributor Author

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

Copy link
Contributor

@aarthificial aarthificial left a comment

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;

@rmhartog rmhartog force-pushed the video-playback-rate branch from b9253e3 to 9141acd Compare October 31, 2023 08:36
@rmhartog
Copy link
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
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());
```

@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

@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