@@ -3,6 +3,13 @@ import { mkdtempSync, rmSync } from "node:fs";
33import { tmpdir } from "node:os" ;
44import { join } from "node:path" ;
55import type { RateSpec } from "@hyperframes/core" ;
6+ import { parseAudioElements , videoAudioId } from "../../../../../engine/src/services/audioMixer.js" ;
7+ import {
8+ parseVideoElements ,
9+ parseImageElements ,
10+ } from "../../../../../engine/src/services/videoFrameExtractor.js" ;
11+ import { resolveReferencedStart } from "../../../../../engine/src/services/referenceResolver.js" ;
12+ import { collectRenderMedia } from "../../renderMediaCollector.js" ;
613
714const workDir = mkdtempSync ( join ( tmpdir ( ) , "hf-stage-test-" ) ) ;
815afterAll ( ( ) => rmSync ( workDir , { recursive : true , force : true } ) ) ;
@@ -100,6 +107,11 @@ mock.module("../../assetMediaType.js", () => ({
100107} ) ) ;
101108
102109mock . module ( "@hyperframes/engine" , ( ) => ( {
110+ parseAudioElements,
111+ parseVideoElements,
112+ parseImageElements,
113+ resolveReferencedStart,
114+ videoAudioId,
103115 createCaptureSession : async (
104116 _url : string ,
105117 _dir : string ,
@@ -529,49 +541,124 @@ describe("runProbeStage — forceScreenshot threading", () => {
529541 } ,
530542 ) ;
531543
532- it . each ( runtimeRates ) (
533- "preserves a collected speed ramp during browser rate %j reconciliation" ,
534- async ( browserRate ) => {
544+ it . each ( [
545+ { tagName : "video" , pageStart : 1 , expected : 3 } ,
546+ { tagName : "video" , pageStart : 3 , expected : 5 } ,
547+ { tagName : "audio" , pageStart : 1 , expected : 3 } ,
548+ { tagName : "audio" , pageStart : 3 , expected : 5 } ,
549+ ] ) (
550+ "applies a scene trim when adopting a runtime $tagName lane (page start $pageStart)" ,
551+ async ( { tagName, pageStart, expected } ) => {
535552 resetRetryMocks ( ) ;
553+ const playbackRate = {
554+ target : "rate" ,
555+ points : [
556+ { t : 0 , v : 1 } ,
557+ { t : 2 , v : 1 } ,
558+ { t : 4 , v : 2 } ,
559+ ] ,
560+ } satisfies RateSpec ;
536561 browserMediaResults = [
537562 {
538563 id : "clip" ,
539- tagName : "video" ,
564+ tagName,
540565 src : "clip.mp4" ,
541566 start : 0 ,
542- end : 3 ,
543- duration : 3 ,
544- mediaStart : 0 ,
545- playbackRate : browserRate ,
567+ end : 5 ,
568+ duration : 5 ,
569+ mediaStart : pageStart ,
570+ playbackRate,
546571 loop : false ,
547- hasAudio : false ,
572+ hasAudio : true ,
548573 volume : 1 ,
549574 muted : false ,
550575 } ,
551576 ] ;
552577 const { runProbeStage } = await import ( "./probeStage.js" ) ;
553578 const input = makeProbeInput ( { } ) ;
554- const playbackRate = {
579+ input . compiled . html = `<div data-composition-id="scene" data-start="5" data-duration="3" data-playback-start="2">
580+ <${ tagName } id="clip" src="clip.mp4" data-start="0" data-duration="5" data-playback-start="1" data-playback-rate="2" data-has-audio="true"></${ tagName } >
581+ </div>` ;
582+ const collected = collectRenderMedia ( input . compiled . html ) ;
583+ input . composition . videos = collected . videos ;
584+ input . composition . audios = collected . audios ;
585+ const before = tagName === "video" ? collected . videos : collected . audios ;
586+ expect ( before [ 0 ] ) . toMatchObject ( { start : 5 , end : 8 , mediaStart : 5 , playbackRate : 2 } ) ;
587+
588+ await runProbeStage ( input ) ;
589+
590+ const shiftedRate = {
555591 target : "rate" ,
556592 points : [
557- { t : 0 , v : 1.25 } ,
593+ { t : 0 , v : 1 } ,
558594 { t : 2 , v : 2 } ,
559595 ] ,
560- } satisfies RateSpec ;
561- input . composition . videos . push ( {
562- id : "clip" ,
563- src : "clip.mp4" ,
564- start : 0 ,
565- end : 3 ,
566- mediaStart : 0 ,
567- playbackRate,
568- loop : false ,
569- hasAudio : false ,
596+ } ;
597+ const media = tagName === "video" ? input . composition . videos : input . composition . audios ;
598+ expect ( media [ 0 ] ) . toMatchObject ( {
599+ start : 5 ,
600+ end : 8 ,
601+ mediaStart : expected ,
602+ playbackRate : shiftedRate ,
570603 } ) ;
604+ if ( tagName === "video" ) {
605+ expect ( input . composition . audios [ 0 ] ) . toMatchObject ( {
606+ mediaStart : expected ,
607+ playbackRate : shiftedRate ,
608+ } ) ;
609+ }
610+ } ,
611+ ) ;
612+
613+ it . each (
614+ runtimeRates . flatMap ( ( playbackRate ) => [
615+ { tagName : "video" , playbackRate } ,
616+ { tagName : "audio" , playbackRate } ,
617+ ] ) ,
618+ ) (
619+ "preserves a collected $tagName speed ramp during reconciliation (browser rate: $playbackRate)" ,
620+ async ( { tagName, playbackRate } ) => {
621+ resetRetryMocks ( ) ;
622+ browserMediaResults = [
623+ {
624+ id : "clip" ,
625+ tagName,
626+ src : "clip.mp4" ,
627+ start : 0 ,
628+ end : 5 ,
629+ duration : 5 ,
630+ mediaStart : 1 ,
631+ playbackRate,
632+ loop : false ,
633+ hasAudio : true ,
634+ volume : 1 ,
635+ muted : false ,
636+ } ,
637+ ] ;
638+ const { runProbeStage } = await import ( "./probeStage.js" ) ;
639+ const input = makeProbeInput ( { } ) ;
640+ const authoredRate = {
641+ target : "rate" ,
642+ points : [
643+ { t : 0 , v : 1 } ,
644+ { t : 2 , v : 1 } ,
645+ { t : 4 , v : 2 } ,
646+ ] ,
647+ } satisfies RateSpec ;
648+ input . compiled . html = `<div data-composition-id="scene" data-start="5" data-duration="3" data-playback-start="2">
649+ <${ tagName } id="clip" src="clip.mp4" data-start="0" data-duration="5" data-playback-start="1" data-has-audio="true" data-automation='${ JSON . stringify ( { version : 1 , lanes : [ authoredRate ] } ) } '></${ tagName } >
650+ </div>` ;
651+ const collected = collectRenderMedia ( input . compiled . html ) ;
652+ input . composition . videos = collected . videos ;
653+ input . composition . audios = collected . audios ;
654+ const before = structuredClone ( { videos : collected . videos , audios : collected . audios } ) ;
655+ const media = tagName === "video" ? collected . videos : collected . audios ;
656+ expect ( media [ 0 ] ) . toMatchObject ( { mediaStart : 3 } ) ;
571657
572658 await runProbeStage ( input ) ;
573659
574- expect ( input . composition . videos [ 0 ] ?. playbackRate ) . toEqual ( playbackRate ) ;
660+ expect ( input . composition . videos ) . toEqual ( before . videos ) ;
661+ expect ( input . composition . audios ) . toEqual ( before . audios ) ;
575662 } ,
576663 ) ;
577664
0 commit comments