<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: FreyaVideo</title>
    <description>The latest articles on DEV Community by FreyaVideo (freyavideo).</description>
    <link>https://dev.to/freyavideo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F14632%2F4ace9869-497c-4a89-b327-c22394709706.png</url>
      <title>DEV Community: FreyaVideo</title>
      <link>https://dev.to/freyavideo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/freyavideo"/>
    <language>en</language>
    <item>
      <title>Do not stop your AI video timer at provider success</title>
      <dc:creator>howard hua</dc:creator>
      <pubDate>Fri, 04 Sep 2026 06:30:24 +0000</pubDate>
      <link>https://dev.to/freyavideo/do-not-stop-your-ai-video-timer-at-provider-success-3jm2</link>
      <guid>https://dev.to/freyavideo/do-not-stop-your-ai-video-timer-at-provider-success-3jm2</guid>
      <description>&lt;p&gt;A successful video-generation response is not the same event as a video becoming playable in a browser. If a dashboard stops its timer at provider success, the number can exclude output transfer, browser loading, and the creator's review.&lt;/p&gt;

&lt;p&gt;Here is a small, provider-independent pattern for instrumenting that gap. It is an example you can adapt, not a benchmark or a claim that one model is faster than another.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: this engineering note was written with AI assistance and is published by FreyaVideo, an AI video tool. The related company-blog guides are linked where they provide background.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Give each milestone a precise meaning
&lt;/h2&gt;

&lt;p&gt;Start by naming what the browser actually observes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;th&gt;Meaning in this example&lt;/th&gt;
&lt;th&gt;What it does not prove&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;submitted&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The user starts the generation request&lt;/td&gt;
&lt;td&gt;The provider has accepted or begun work&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;acknowledged&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The app receives a job acknowledgement&lt;/td&gt;
&lt;td&gt;Rendering is finished&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;provider_done_observed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A poll or notification tells the app that output is ready&lt;/td&gt;
&lt;td&gt;The browser can play that output&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;playing&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The browser emits its first &lt;code&gt;playing&lt;/code&gt; event for the output&lt;/td&gt;
&lt;td&gt;The result meets the creator's brief&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;accepted&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A person marks the output usable after review&lt;/td&gt;
&lt;td&gt;Every previous attempt was successful&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The word &lt;strong&gt;observed&lt;/strong&gt; matters. A polling client can learn about completion several seconds after it happened. That delay belongs in a user-facing waiting-time measurement, but it is not evidence about GPU inference time.&lt;/p&gt;

&lt;p&gt;Similarly, &lt;code&gt;playing&lt;/code&gt; is a practical playback milestone, not an exact “first pixel appeared on the screen” measurement. It can also occur after playback resumes. Record the first event for a particular output, and document the definition.&lt;/p&gt;

&lt;p&gt;This distinction is the central question in our &lt;a href="https://freyavideo.com/blog/fastest-ai-video-generator" rel="noopener noreferrer"&gt;guide to comparing AI video speed claims&lt;/a&gt;: which clock is the claim actually measuring?&lt;/p&gt;

&lt;h2&gt;
  
  
  Use one local clock for a single page visit
&lt;/h2&gt;

&lt;p&gt;For elapsed time within one document, &lt;code&gt;performance.now()&lt;/code&gt; provides a monotonic clock. It avoids the wall-clock adjustments that can affect a subtraction of two &lt;code&gt;Date.now()&lt;/code&gt; values. The &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Performance/now" rel="noopener noreferrer"&gt;MDN reference&lt;/a&gt; also documents precision limits and differences in how browsers handle operating-system sleep.&lt;/p&gt;

&lt;p&gt;Do not subtract a server timestamp from &lt;code&gt;performance.now()&lt;/code&gt;. Do not take a value from an old page load and subtract it from a new page's reading. Both cases mix clock contexts.&lt;/p&gt;

&lt;p&gt;The following ledger deliberately exists only in memory. Call &lt;code&gt;start()&lt;/code&gt; directly before initiating the generation request, rather than when the page loads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createRunLedger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;runId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;performance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;startedAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;marks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;runId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nf"&gt;mark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Keep the first observation of each milestone.&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;marks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nx"&gt;marks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;startedAt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nf"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;outcome&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;runId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;outcome&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;browser-document-monotonic&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;elapsedMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;marks&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;runId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ledger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createRunLedger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;runId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;ledger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;submitted&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;ledger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;now&lt;/code&gt; argument makes the ledger easy to check with a deterministic clock. Calling &lt;code&gt;mark("acknowledged")&lt;/code&gt; twice must keep the first value. An event that never happened stays absent; it is not recorded as zero milliseconds.&lt;/p&gt;

&lt;p&gt;This code is only the ledger. Your application still needs request handling, authentication, polling, cancellation, persistence, and an explicit job state machine. Keeping those responsibilities separate makes the timing definitions easier to inspect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attach playback observation to the correct output
&lt;/h2&gt;

&lt;p&gt;After the application observes a completed job, mark &lt;code&gt;provider_done_observed&lt;/code&gt;. Before assigning the new output URL to its video element, register a listener for the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/playing_event" rel="noopener noreferrer"&gt;media element's &lt;code&gt;playing&lt;/code&gt; event&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;observeFirstPlayback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ledger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onPlaying&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;ledger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;playing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;playing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onPlaying&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;once&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;playing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onPlaying&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use this with an element dedicated to that output. Call the returned cleanup function when abandoning the run or replacing the element. Otherwise, a listener from an old run could record playback of a different result.&lt;/p&gt;

&lt;p&gt;A play button and autoplay have different implications. If the browser waits for the user to click Play, submission-to-playing includes that human delay. If autoplay is blocked, the job may still be perfectly valid even though no &lt;code&gt;playing&lt;/code&gt; mark exists. Store the playback mode alongside the measurements and avoid mixing the two populations.&lt;/p&gt;

&lt;p&gt;If your question is instead “when was media data available?”, choose and document a different milestone. Do not silently rename a media-loading event “visible to the user.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep run outcomes and review decisions explicit
&lt;/h2&gt;

&lt;p&gt;One local run should have its own identifier. A retry should receive a new run identifier and retain a reference to the overall user request. This avoids overwriting the time and outcome of the first attempt with the successful second attempt.&lt;/p&gt;

&lt;p&gt;The application, not the sample ledger, must decide when a run is failed, cancelled, timed out locally, or complete. A local timeout means the observer stopped waiting; it does not prove that the provider stopped working. Keep that distinction in reconciliation and avoid treating it as permission to dispatch another chargeable job automatically.&lt;/p&gt;

&lt;p&gt;Record acceptance through a separate, deliberate review action. This gives submission-to-acceptance for one run; measuring brief-to-accepted-result across retries needs a separate workflow-level starting point. An output can be playable but unusable because it changed the product, missed a requested action, or broke continuity. Conversely, “not reviewed yet” should remain unknown rather than being converted into rejection.&lt;/p&gt;

&lt;p&gt;For a first experiment, an empty worksheet is enough:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;request_id&lt;/th&gt;
&lt;th&gt;run_id&lt;/th&gt;
&lt;th&gt;model_version&lt;/th&gt;
&lt;th&gt;settings&lt;/th&gt;
&lt;th&gt;outcome&lt;/th&gt;
&lt;th&gt;elapsed_ms&lt;/th&gt;
&lt;th&gt;accepted&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Fill from a real run&lt;/td&gt;
&lt;td&gt;Fill from a real run&lt;/td&gt;
&lt;td&gt;Record exact version&lt;/td&gt;
&lt;td&gt;Record actual settings&lt;/td&gt;
&lt;td&gt;Explicit state&lt;/td&gt;
&lt;td&gt;Milestone map&lt;/td&gt;
&lt;td&gt;yes / no / unknown&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This table contains no measurements. Populate it from real observations before publishing performance conclusions. Avoid storing prompts, signed media URLs, or personal data unless they are necessary for the analysis and have appropriate handling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compare the same brief before comparing the clocks
&lt;/h2&gt;

&lt;p&gt;Timing data is difficult to interpret when one run uses a shorter duration, lower resolution, or a different input image. Record those settings and keep the creative brief stable. For model-specific context, our &lt;a href="https://freyavideo.com/blog/h3-max-vs-kling" rel="noopener noreferrer"&gt;H3 Max and Kling comparison&lt;/a&gt; separates documented controls and specifications from measured results.&lt;/p&gt;

&lt;p&gt;For an initial H3 Max workflow, the &lt;a href="https://freyavideo.com/blog/h3-max-tutorial" rel="noopener noreferrer"&gt;first-shot tutorial&lt;/a&gt; provides a scoped starting point. The instrumentation above applies regardless of the selected provider; a tutorial's example settings are not a substitute for recording the settings your application actually sent.&lt;/p&gt;

&lt;p&gt;Only after collecting enough comparable runs should you summarize distributions. Report the number of observations, the outcome breakdown, and the measurement definition next to a median or percentile. If you report successful runs separately, label that selection clearly and show how many attempts failed or remain unresolved.&lt;/p&gt;

&lt;p&gt;A useful dashboard lets its reader answer three different questions: when did the application learn that rendering was finished, when did playback begin, and did a person accept the result? Keeping those events separate makes the numbers much easier to trust.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>ai</category>
      <category>abotwrotethis</category>
    </item>
  </channel>
</rss>
