Jump to content
Search Community

Control Video HTML Tag

anotheruser test
Moderator Tag

Recommended Posts

Hey anotheruser,

 

There must be some function telling the tween to pause, correct? So pause the video there as well. 

 

Often times for situations like these I'll write a function to not have to duplicate the tween.pause() and video.pause() every time I need to do it:

function pauseVideo() {
  tween.pause();
  video.pause();
}

// now you can just pauseVideo() when you need to

Same thing for playing them.

  • Like 3
Link to comment
Share on other sites

  • 3 years later...

@ZachSaucier

 

There are a couple of problems with this approach. The first one is that maybe the video is associated with a child's timeline, so when doing a play or pause on the parent timeline we cannot just play or pause the video as the child's timeline may start later in time. The second issue is that the approach suggested assumes that we have access to the video in the same place as the tween, that will not always be the case as maybe you are working with a timeline that is created at some top-level whereas the video is created deep inside a web component or something like that.

What would really be useful is if there were callbacks that fired every time a timeline started playing (note it is not the same as onStart as we could start playing from the middle of the timeline), as well as callbacks for seek and stop. With a set of callbacks like this, it would be super easy to integrate with stuff like the video tag or any other external animation system.

Link to comment
Share on other sites

Hi @manast. GSAP animations are far more dynamic than video playback - it's not as simplistic as "play and pause". The playhead can move in all sorts of ways: 

  • Obviously normal play()/pause()
  • Set the progress() or totalProgress()
  • Set time() or totalTime()
  • seek()
  • Adjust the timeScale() so it'll play much faster or slower
  • reverse()
  • Adjust any of the above on an animation's PARENT (or any ancestor) which would automatically trickle down to affect the descendants.

How would you accommodate all of those? For example, let's say we implemented exactly what you're suggesting so that some kind of callback got fired onPlay() or onPause() or onSeek()...

 

let tween = gsap.to("#id", {
  x: 1000,
  onPlay: myPlayFunc,
  onPause: myPauseFunc,
  onSeek: mySeekFunc
});

But...

let tl = gsap.timeline();
tl.add(tween, 1);
tl.seek(1.5); // should onSeek() be called on the child even though this happens on the parent? 
tl.time(1.8); // should onSeek() get called here too?
tl.timeScale(3); // uh oh...now the playhead of the video will be totally out of sync
tl.pause(); // should the child's onPause() get called even though it's not technically paused but it acts like it only because the parent's playhead is paused?
tl.reverse(); // playback is resumed, but it's going backwards so would the child's onPlay() get called? If you're playing the video as a result, obviously the playhead will be going the opposite direction

Or even more confusingly, what if someone does this?: 

tween.pause(); // obviously fires the onPause()
// now tween the playhead with another tween such that functionally it appears to be playing even though it is paused
gsap.to(tween, {
  time: tween.duration(),
  duration: tween.duration(),
  ease: "none"
});

As a convenience, I just whipped up this helper function for you: 

function mediaTimeline(media, config) {
	typeof(media) === "string" && (media = document.querySelector(media));
	let duration = media.duration,
			onUpdate = config && config.onUpdate,
			tl = gsap.timeline({
				paused: true,
				onUpdate() {
					if (tl.paused() || Math.abs(tl.time() * duration - media.currentTime) > 0.5) {
						media.currentTime = tl.time() * duration;
					}
					onUpdate && onUpdate.call(tl);
				}
			}),
			updateDuration = () => {
				duration = media.duration;
				tl.timeScale(1 / duration);
			},
			pause = tl.pause,
			play = tl.play;
	tl.set({}, {}, 1);
	media.addEventListener("durationchange", updateDuration);
	updateDuration();
	media.onplay = () => tl.play();
	media.onpause = () => tl.pause();
	media.ontimeupdate = () => {
		tl.time(media.currentTime / duration, true);
	}
	tl.pause = function() {
		media.pause();
		pause.apply(tl, arguments);
	};
	tl.play = function() {
		media.play();
		play.apply(tl, arguments);
		if (arguments.length) {
			media.currentTime = arguments[0];
		}
	}
	tl.media = media;
	return tl;
}

Just feed in a media element and it'll return a timeline that's properly linked up with that media so that if you pause/play the timeline, it'll handle doing that to the media too. And if you play/pause the media, it'll handle the timeline too. The timeline is 1 second long and you should always populate it accordingly (so that your animations are placed such that they end on or before 1 second) so that: 

  1. You don't have to wait for the media to load its meta data before being able to populate the timeline with animations in the proper ratios
  2. You can just build all your animations with the proper ratios and no matter what the duration is, everything will stretch/squish to play proportionally. 
  3. It'll dynamically adjust the timeScale() of the timeline according to the media's duration (when it's loaded). So if your media is 10 seconds long, it'll play at a timeScale() of 1/10 (0.1). 

Here's a simple demo:

See the Pen xxQrGBY?editors=0010 by GreenSock (@GreenSock) on CodePen

 

I hope that helps!

  • Like 1
Link to comment
Share on other sites

@GreenSock If I am not mistaken, this technique of overriding the play/pause/seek methods would not work if the timeline is a child timeline right? As if the parent timeline will not call these child timeline methods when playing, pausing, or seeking. Is there any obvious way to make this case work as well?

Link to comment
Share on other sites

And just to illustrate with one use case where this would be important. Let's say that you have a playlist with songs (or videos), and you want to control the playlist with one parent timeline so that you can have one slider used to move from the different songs, as if the songs where actually stitched together as one single piece. In this case, you would like to have one child timeline for every song, and then add every child timeline to the parent timeline which would then be the UI-facing timeline to control the playlist.

Link to comment
Share on other sites

Hi,

 

A child timeline will be subjected to the parent's playhead, is just the way Timelines work. They control the playhead of their child instances (regardless if they are an individual Tween or a Timeline). Think of a single song in a player, you can go back and forth in that song to different positions or times, but let's say that the particular song  is a mix of a bunch of songs, we're still talking about a single song. Again you can go back and forth in the song but the order and whether you can play/pause/seek each section of the mix is not a possibility. GSAP actually allows you to play/pause child timelines/tweens but that could lead to unexpected results.

 

In the case of a playlist or array of media files, we're talking about a different scenario. The helper function Jack created basically syncs a single GSAP Timeline to a media file in order to match their durations. The helper function does return a timeline though so you could create an array with your songs and create a single timeline for each and store that in another array, or you can create an array of objects with the media file and the associated timeline of that particular file. Think about a playlist in a music player, you can play/pause/seek each song, but if you pause song A at 1 minute and then go to song B, you can't go back to song A at exactly one minute, can you? You have to switch to Song A and seek to the 1 minute position.

 

I think an array is the way to go here, set an active song by it's index and create a matching array of timelines for each song:

let activeSong = 0;
const songs = gsap.utils.toArray(".song");

const songsTimelines = songs.map((song) => {
  const songConfig = {/* Create each song's config here */};
  return mediaTimeline(song, songConfig);
});

Hopefully this helps.

Happy Tweening!

Link to comment
Share on other sites

  • 6 months later...

I'm still grasping the power of GSAP,  I need to learn more about it.

 

But I have many questions regarding my identical need on this topic.

First one: Isn't a single master timeline enough to control every video and other animations in nested or different tree components (assuming a React and state management is being used)?

 

Link to comment
Share on other sites

15 minutes ago, Cyango said:

But I have many questions regarding my identical need on this topic.

First one: Isn't a single master timeline enough to control every video and other animations in nested or different tree components (assuming a React and state management is being used)?

Everything is possible, but complex scenarios and needs require complex solutions as well. If you're just starting I would strongly recommend to first read the resources we have here (if you plan on using GSAP in React projects/environments):

https://gsap.com/resources/React

 

If you setup your state management correctly, then definitely you can do pretty much anything you want, but as I mentioned before, as the app's complexity grows creating and maintaining the code becomes more complex as well. Now handling the control of every single animation in an app to a single Timeline might not be 100% recommended unless that is exactly what you have in mind. Sometimes just compartmentalize your app and GSAP instances could be the best choice.

 

For example every section in this page (GSAP/ScrollTrigger/DrawSVG + NextJS) is it's own component and it has it's own animation. Of course everything is controlled by ScrollTrigger but in this case if you'd want to control every single animation at your own will I wouldn't add it to a single Timeline, but keep them separate.

https://wiredave.com/artificial-intelligence

 

So is mostly a case-by-case thing, the more you code, the more you learn and you end up making the best decision based on the scenario you're facing.

 

Happy Tweening!

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...