@@ -53,6 +53,10 @@ const props = defineProps({
5353 type: Number ,
5454 default: 0
5555 },
56+ fps: {
57+ type: Number ,
58+ default: null
59+ },
5660 fullScreen: {
5761 type: Boolean ,
5862 default: false
@@ -143,7 +147,9 @@ const video = computed(() => movie.value)
143147
144148const extension = computed (() => (props .preview ? props .preview .extension : ' ' ))
145149
146- const fps = computed (() => parseFloat (currentProduction .value ? .fps ) || 25 )
150+ const fps = computed (
151+ () => props .fps || parseFloat (currentProduction .value ? .fps ) || 25
152+ )
147153
148154const frameDuration = computed (
149155 () => Math .round ((1 / fps .value ) * 10000 ) / 10000
@@ -203,8 +209,17 @@ const getLastPushedCurrentTime = () => {
203209 }
204210}
205211
212+ // Seek to the middle of the target frame rather than its boundary.
213+ // frame / fps lands right on the edge between two frames, and the rounding
214+ // in frameDuration (0.0833 instead of 0.083333… at 12fps) drifts ~1ms per
215+ // frame, so past ~frame 30 the seek tips into the previous frame and the
216+ // player renders a duplicate. Half a frame of slack dwarfs any rounding
217+ // error, so the correct frame is always decoded — and fps (unrounded) is
218+ // used so there is no drift to begin with.
219+ const frameToTime = frame => (frame + 0.5 ) / fps .value
220+
206221const setCurrentFrame = frame => {
207- setCurrentTime (frame * frameDuration . value )
222+ setCurrentTime (frameToTime ( frame) )
208223}
209224
210225const setCurrentTimeRaw = currentTime => {
@@ -218,7 +233,7 @@ const runSetCurrentTime = () => {
218233 } else {
219234 const currentTime = currentTimeCalls .shift ()
220235 if (video .value .currentTime !== currentTime) {
221- video .value .currentTime = Number ( currentTime . toPrecision ( 4 )) + 0.001
236+ video .value .currentTime = currentTime
222237 }
223238 setTimeout (() => {
224239 runSetCurrentTime ()
@@ -313,7 +328,7 @@ const play = () => {
313328const pause = () => {
314329 video .value .pause ()
315330 clearInterval (playLoop)
316- video .value .currentTime = props .currentFrame * frameDuration . value
331+ video .value .currentTime = frameToTime ( props .currentFrame )
317332 emit (' frame-update' , props .currentFrame )
318333}
319334
@@ -324,15 +339,15 @@ const toggleMute = () => {
324339const goPreviousFrame = () => {
325340 const nextFrame = props .currentFrame - 1
326341 if (nextFrame < 0 ) return
327- video .value .currentTime = nextFrame * frameDuration . value + 0.001
342+ video .value .currentTime = frameToTime ( nextFrame)
328343 emit (' frame-update' , nextFrame)
329344 return nextFrame
330345}
331346
332347const goNextFrame = () => {
333348 const nextFrame = props .currentFrame + 1
334349 if (nextFrame >= props .nbFrames ) return
335- video .value .currentTime = nextFrame * frameDuration . value + 0.001
350+ video .value .currentTime = frameToTime ( nextFrame)
336351 emit (' frame-update' , nextFrame)
337352 return nextFrame
338353}
0 commit comments