<?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>A minimal experiment log for image-to-video workflows</title>
      <dc:creator>howard hua</dc:creator>
      <pubDate>Thu, 10 Sep 2026 06:16:30 +0000</pubDate>
      <link>https://dev.to/freyavideo/a-minimal-experiment-log-for-image-to-video-workflows-25im</link>
      <guid>https://dev.to/freyavideo/a-minimal-experiment-log-for-image-to-video-workflows-25im</guid>
      <description>&lt;p&gt;An image-to-video workflow has at least two quality gates: whether the source image is usable, and whether the generated motion follows the brief. If you change the image prompt, video prompt and model together, a better result tells you very little about which change helped.&lt;/p&gt;

&lt;p&gt;Here is a small experiment record you can use without building an evaluation service. It is a proposed workflow, not a report of measured model performance. I work on FreyaVideo; the linked product pages and guides below are ours.&lt;/p&gt;

&lt;h2&gt;
  
  
  Freeze the input before comparing video models
&lt;/h2&gt;

&lt;p&gt;Give each accepted source image a stable filename. Keep a copy of the original and record the exact edit instruction. An image that looks better at thumbnail size can still introduce a changed face, bent lettering or an object that will make motion harder to judge.&lt;/p&gt;

&lt;p&gt;A minimal record might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"case_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"window-shot-001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source_file"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"window-shot-001-approved.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source_revision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"must_preserve"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"subject identity"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"window position"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"shirt color"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"requested_edit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"reduce background clutter"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"video_brief"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"subject turns toward the window; static camera"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"duration_seconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"aspect_ratio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"16:9"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These values describe an example brief, not an actual generation. Keep the source revision fixed while comparing video results. If you need another image edit, create a new revision and label the subsequent runs accordingly.&lt;/p&gt;

&lt;p&gt;For the image stage, our &lt;a href="https://freyavideo.com/image-models/gpt-image-2-5" rel="noopener noreferrer"&gt;GPT Image 2.5 workspace&lt;/a&gt; exposes generation and reference-image editing. The accompanying &lt;a href="https://freyavideo.com/blog/gpt-image-2-5-noise-editing" rel="noopener noreferrer"&gt;noise and editing guide&lt;/a&gt; describes how to inspect edits. Regardless of the tool, compare against the last acceptable source rather than continuing indefinitely from a damaged result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate the three clocks
&lt;/h2&gt;

&lt;p&gt;The time a user experiences can include upload, queueing, generation and delivery. A provider completion timestamp is not necessarily the moment the user can play the output.&lt;/p&gt;

&lt;p&gt;Record timestamps only when the application exposes the relevant events:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;request_started_at
provider_accepted_at
provider_completed_at
playable_output_ready_at
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then calculate the intervals whose endpoints you actually have. Do not label the time between acceptance and completion as pure compute time if it includes queueing. Do not substitute polling frequency for an exact provider completion timestamp.&lt;/p&gt;

&lt;p&gt;For example, if a poll discovers completion at 12:00:30, all you know may be that the job completed between that poll and the previous one. Preserve that uncertainty in the result rather than reporting a precise processing duration.&lt;/p&gt;

&lt;p&gt;Our &lt;a href="https://freyavideo.com/blog/fastest-ai-video-generator" rel="noopener noreferrer"&gt;video generation speed guide&lt;/a&gt; provides context for assessing turnaround. For your own comparisons, total time to a playable result is often the most useful user-facing measure, while queue and processing intervals help diagnose delays.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep a failure row
&lt;/h2&gt;

&lt;p&gt;A failed or rejected run is still an observation. Dropping it makes both turnaround and cost look better than the experience a user actually had.&lt;/p&gt;

&lt;p&gt;Useful columns are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;case_id,source_revision,model,settings,status,total_seconds,credits,identity_ok,motion_ok,artifact_notes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Leave unknown values blank. Keep cancellation, validation rejection and provider failure separate: a request rejected before it reaches the provider is not evidence of model reliability. Likewise, an output that renders successfully can fail your visual acceptance criteria.&lt;/p&gt;

&lt;p&gt;Decide the acceptance criteria before seeing the output. For the example brief, ask whether the subject remained recognizable, whether the turn happened, whether the camera stayed static and whether the result has distracting artifacts. A short note is more informative than an unexplained overall score.&lt;/p&gt;

&lt;h2&gt;
  
  
  Change one variable per comparison
&lt;/h2&gt;

&lt;p&gt;Start by holding the source image, brief, duration and aspect ratio constant. Record any settings that cannot be matched between the models. If one output uses a different resolution or audio setting, make that difference visible instead of presenting it as a controlled comparison.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://freyavideo.com/blog/h3-max-vs-kling" rel="noopener noreferrer"&gt;H3 Max versus Kling guide&lt;/a&gt; is a starting point for choosing cases to investigate. It does not replace recording your own settings and observations. A single clip should not be treated as a universal ranking.&lt;/p&gt;

&lt;p&gt;If you are setting up an H3 run, the &lt;a href="https://freyavideo.com/blog/h3-max-tutorial" rel="noopener noreferrer"&gt;H3 Max tutorial&lt;/a&gt; and &lt;a href="https://freyavideo.com/video-models/minimax-h3-max" rel="noopener noreferrer"&gt;model workspace&lt;/a&gt; cover the practical next steps. Check current settings and credit estimates before generating.&lt;/p&gt;

&lt;h2&gt;
  
  
  A stopping rule prevents endless tweaking
&lt;/h2&gt;

&lt;p&gt;Before running a case, define how many attempts you are willing to spend and what would make the output acceptable. If the source image keeps causing ambiguity, go back to the image gate. If the action is too complex, simplify the brief and record that as a new case.&lt;/p&gt;

&lt;p&gt;The resulting table will be imperfect, but it will answer a concrete question: which combination met this brief, at what observed cost and turnaround, under which settings? That is much more actionable than collecting unrelated good-looking clips.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>productivity</category>
    </item>
    <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>
