How to Make a Video Player Prototype
Learn how to make an TikTok-inspired video player prototype with a scrubber, using formulas and variables.



We've divided this step-by-step guide into 6 sections:
- Part 1: Play/Pause a video on scroll
- Part 2: Tap a video card to make an expanding transition
- Part 3: Seek video by tapping the progress bar
- Part 4: Seek video by dragging the progress bar
- Part 5: Display the video's current playtime
- Part 6: Open and close the comment section while the video is playing
Make sure you're using version 5.5 or above!
Part 1: Play/Pause a video on scroll

1. In the scene Profile, create a Scroll Container the same size as your scene (834 x 1194) and put all layers into this.

2. Store Scroll Container's scroll value in a variable dynamically.
- Create a new For This Scene variable Scroll
- Select Number
- Check Use as Formula (only works for For This Scene variables)
- Enter 'Scroll'.scrollOffset
- Click on the debug icon next to the variable name
At all times, you keep track of Scroll Container scroll value. You don't need the Detect trigger and Assign response this way.

3. Play or Pause a video based on the scroll value.
- Add two Range triggers, both assigned to the variable Scroll
- For one, set the range as 210 ≤ Scroll
- For the other, set the range as Scroll ≤ 209
- Add two Playback responses, both assigned to video 1, one for each Range trigger
- For the first Range (210 ≤ Scroll), select Play and check Looping in Playback
- For the second Range (Scroll ≤ 209), select Pause in Playback

Part 2: Tap a video card to make an expanding transition

1. Make a video expand and display in fullscreen.
- Add a Tap trigger, assigned to Video 1, the container video 1 is in
- Add a Scale response, assigned to Video 1, scaling to 834 x 1194
2. Move the video to the correct relative position upon Tap.
- Add a Move response under Tap, moving to
toLayerX("Scroll",0,0)
for X andtoLayerY("Scroll",0,0)
for Y

As Video 1 is inside Scroll Container, its position is relative to Scroll Container. Instead, Video 1 should move to (0, 0) relative to the screen, regardless of the scroll value. This is why you need the toLayerX and toLayerY functions.


💡 toLayerX: Return the x-coordinate relative to a container or component. Layers in a container or component follow the coordinates relative to the container or component they are in.
toLayerX(containerName: LAYER, x: NUMBER, y: NUMBER)
→ NUMBER
💡 toLayerY: Return the y-coordinate relative to a container or component. Layers in a container follow the coordinates relative to the container or component they are in.
toLayerY(containerName: LAYER, x: NUMBER, y: NUMBER)
→ NUMBER
3. Bring the video to the front before jumping to the next scene.
- Add a Radius response, assigned to Video 1, to reset the radius to 0
- Add a Reorder response, assigned to Video 1, to bring Video 1 to the front
- Add a Jump response to the scene Video with a 0.2-second delay
Part 3: Seek video by tapping the progress bar

1. Make the video play automatically and loop at all times.
- In the second scene Video, check both “Play Automatically” and Looping in the video layer Video its property panel
2. Make the scrubber and play head progress while playing the video.
- Add a Chain trigger, assigned to Video and its Time property
- Add a Move response, assigned to Dot, mapping the video length with the distance Dot needs to move
- Add a Scale response, assigned to Progress, mapping the video length with the width Progress needs to scale to

3. Make the video seek a specific timestamp based on where you tap on the scrubber.
- Add a Tap trigger, assigned to the container Progress bar
- Add a Move response, assigned to Dot, moving to the x-coordinate
toLayerX('Progress bar', $touchX, 8)
With the predefined variable $touchX, retrieve the x-coordinate of a touch. As you need to turn this value into an x-coordinate relative to the Progress bar, use the toLayerX function. As Dot its initial position is (0, 8), use 8 as the third parameter.
- Add a Playback response, assigned to Video
- Seek the video to
17 * toLayerX(Progress bar, $touchX , 8) / 618
- 17: Video total duration. 618: Progress bar width
- Video current playtime = Video duration*Dot current position/Progress bar width
The scrubber Progress would scale along as it's chained to Video time property already. Hence, no need for an additional Scale response.
Part 4: Seek video by dragging the progress bar

1. Make a working scrubber with a draggable play head.
- Add a Drag trigger, assigned to Dot
- Add a Move response and assign it to Dot. Set the direction and select Limit to Container, so that Dot can only move within the Progress bar

2. Make the video seek to the timestamp matching the x position of Dot on the progress bar.
- Use a Detect trigger to keep track of Dot's x position
- Add a Playback response (Seek) and assign it to Video

❓ Why are we using Detect instead of Chain?
ProtoPie doesn't support Playback in Chain yet. Detect is the only solution currently available.
3. You'll notice a problem at this point: the video freezes while playing.

It's due to a conflict between the triggers Drag, Chain, and Tap assigned to Progress bar.
To solve the problem, you need to help ProtoPie distinguish between tapping and dragging Progress bar.
How are they different in ProtoPie?
- Drag:when the finger drags Dot, its x position affects the current playtime of Video. If the finger is lifted, Dot starts progressing together with Video
- Tap:the responses under Tap execute as soon as the finger is lifted
How can you distinguish between these two actions in ProtoPie? with Variables!
- Create a variable (for this scene) and assign the numeric value 0 to it
- Add a Touch Down trigger and Touch Up trigger with one Assign response each:
- If touchdown, variable = 0
- If touchup, variable = 1

.png)
- Trigger the Playback response only if variable = 1 by adding a Condition under Detect
Part 5: Display video's current playtime

1. Show video current playtime and duration:
- Add a Detect trigger assigned to Video Time
- Add two Text responses with formula
floor(Video.currentTime)
in Content
💡 currentTime: The current playtime of a video, audio, or Lottie layer. The default format is seconds and milliseconds.
- Floor or Round allow to round playtime values (we want to display minutes and seconds only). We chose to use Floor for better accuracy
💡 Floor: Returns the largest integer value smaller than or equal to a number. e.g.floor(1.5) -> 1
💡 Round: Returns the rounded value of a number. e.g.round(1.5)-> 2
- Add a Condition under each Text response:
- If play time < 10 sec, add 0:0 before
floor(Video.currentTime)
- If play time ≥ 10 sec, add 0: before
floor(Video.currentTime
)
- If play time < 10 sec, add 0:0 before

Part 6: Show/Close Comments

1. Resize Video to make room for Comments:
- Add a Tap trigger and assign it to Comments
- Add a Scale response and assign it to Video:
- Use the parent formula
parent(Video).height
in Height to scale Video to match the height of its parent Video contents
- Use the parent formula
💡 parent: refers to the parent layer, e.g. a container or component, or returns its layer property. Example: parent(Rectangle 1).x
→ x-coordinate of Rectangle 1 its parent layer
- In Width, use formula
Video.width/Video.height*parent(Video).height
to keep the same ratio

2. Move Comments to a y position matching the height of Video Contents:
- Add a Move response with the formula
'Container'.height
under Y
❓ Why did we make Video Contents smaller than the video's actual size?
Because this way you can simply use a parent formula to dynamically update the video's height. No need to readjust Scale values if the height of Video Contents changes (see image below) Quite a time saver.

3. Reset the scene to its initial state when tapping the Close button
- Add a Tap trigger and assign it to the Close button
- Add two Reset responses to reset Comments and Profile
- Add a Scale response and assign it to Video. Why not Reset? To avoid Progress bar resetting at the same time
- Use formulas
initial(Video,"width")
andinitial(Video,"height")
in Scale to avoid having to recalculate values in case you later change the video's size
💡 Initial: Return the initial value (before any interactions) of a specific layer property. Example: initial(Oval 1, "opacity")
→ Initial opacity of Oval 1
- Add a Move response and assign it to Progress bar:
- Use the formula
initial(Progress bar component,"y")
in Y, to move Progress bar back to its initial position
- Use the formula
Tips
For an even more realistic result, you can add a video thumbnail Fill to Video Contents!

Summary
In this tutorial, we've learned how to make a very realistic video player.
If you're still at a beginner stage, some of the steps above were probably quite difficult to grasp, especially those involving logic and formulas.
Here's a list of the formulas we used in this tutorial:
$touchX
toLayerX(containerName, x, y)
'Layername'.currentTime
floor(Number)
parent(LayerName).property
initial(LayerName, "Property")
For more details, check out our formulas documentation.
If you'd love to better understand how this video player interaction was made, join our ProtoPie 5.5 Launch event! Our team will show you step-by-step how to make it and answer any questions you may have!