@omnimedia/omnitool 1.1.0-5 → 1.1.0-8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/package.json +3 -2
  2. package/s/demo/demo.css +5 -0
  3. package/s/demo/routines/transcode-test.ts +4 -2
  4. package/s/demo/routines/transitions-test.ts +2 -2
  5. package/s/driver/driver.ts +17 -9
  6. package/s/driver/fns/schematic.ts +44 -21
  7. package/s/driver/fns/work.ts +112 -97
  8. package/s/index.html.ts +6 -1
  9. package/s/timeline/index.ts +1 -0
  10. package/s/timeline/parts/basics.ts +1 -1
  11. package/s/timeline/parts/compositor/export.ts +77 -0
  12. package/s/timeline/parts/compositor/parts/html-tree.ts +37 -0
  13. package/s/timeline/parts/compositor/parts/schedulers.ts +85 -0
  14. package/s/timeline/parts/compositor/parts/tree-builder.ts +184 -0
  15. package/s/timeline/parts/compositor/parts/webcodecs-tree.ts +30 -0
  16. package/s/timeline/parts/compositor/playback.ts +81 -0
  17. package/s/timeline/parts/compositor/samplers/html.ts +115 -0
  18. package/s/timeline/parts/compositor/samplers/webcodecs.ts +60 -0
  19. package/s/timeline/parts/item.ts +38 -6
  20. package/s/timeline/parts/media.ts +21 -0
  21. package/s/timeline/parts/waveform.ts +1 -1
  22. package/s/timeline/sugar/builders.ts +102 -0
  23. package/s/timeline/sugar/o.ts +75 -16
  24. package/s/timeline/sugar/omni-test.ts +2 -2
  25. package/s/timeline/sugar/omni.ts +14 -11
  26. package/s/timeline/timeline.ts +22 -0
  27. package/s/timeline/types.ts +29 -0
  28. package/s/timeline/utils/audio-stream.ts +15 -0
  29. package/s/timeline/utils/matrix.ts +33 -0
  30. package/s/timeline/utils/video-cursor.ts +40 -0
  31. package/x/demo/demo.bundle.min.js +39 -37
  32. package/x/demo/demo.bundle.min.js.map +4 -4
  33. package/x/demo/demo.css +5 -0
  34. package/x/demo/routines/transcode-test.js +4 -2
  35. package/x/demo/routines/transcode-test.js.map +1 -1
  36. package/x/demo/routines/transitions-test.js +2 -2
  37. package/x/demo/routines/transitions-test.js.map +1 -1
  38. package/x/driver/driver.d.ts +3 -5
  39. package/x/driver/driver.js +16 -9
  40. package/x/driver/driver.js.map +1 -1
  41. package/x/driver/driver.worker.bundle.min.js +2537 -148
  42. package/x/driver/driver.worker.bundle.min.js.map +4 -4
  43. package/x/driver/fns/host.d.ts +9 -2
  44. package/x/driver/fns/schematic.d.ts +38 -20
  45. package/x/driver/fns/work.d.ts +11 -4
  46. package/x/driver/fns/work.js +105 -96
  47. package/x/driver/fns/work.js.map +1 -1
  48. package/x/features/speech/transcribe/worker.bundle.min.js +541 -541
  49. package/x/features/speech/transcribe/worker.bundle.min.js.map +4 -4
  50. package/x/index.html +13 -3
  51. package/x/index.html.js +6 -1
  52. package/x/index.html.js.map +1 -1
  53. package/x/timeline/index.d.ts +1 -0
  54. package/x/timeline/index.js +1 -0
  55. package/x/timeline/index.js.map +1 -1
  56. package/x/timeline/parts/basics.d.ts +1 -1
  57. package/x/timeline/parts/compositor/export.d.ts +9 -0
  58. package/x/timeline/parts/compositor/export.js +64 -0
  59. package/x/timeline/parts/compositor/export.js.map +1 -0
  60. package/x/timeline/parts/compositor/parts/html-tree.d.ts +3 -0
  61. package/x/timeline/parts/compositor/parts/html-tree.js +40 -0
  62. package/x/timeline/parts/compositor/parts/html-tree.js.map +1 -0
  63. package/x/timeline/parts/compositor/parts/schedulers.d.ts +15 -0
  64. package/x/timeline/parts/compositor/parts/schedulers.js +64 -0
  65. package/x/timeline/parts/compositor/parts/schedulers.js.map +1 -0
  66. package/x/timeline/parts/compositor/parts/tree-builder.d.ts +37 -0
  67. package/x/timeline/parts/compositor/parts/tree-builder.js +147 -0
  68. package/x/timeline/parts/compositor/parts/tree-builder.js.map +1 -0
  69. package/x/timeline/parts/compositor/parts/webcodecs-tree.d.ts +3 -0
  70. package/x/timeline/parts/compositor/parts/webcodecs-tree.js +28 -0
  71. package/x/timeline/parts/compositor/parts/webcodecs-tree.js.map +1 -0
  72. package/x/timeline/parts/compositor/playback.d.ts +19 -0
  73. package/x/timeline/parts/compositor/playback.js +71 -0
  74. package/x/timeline/parts/compositor/playback.js.map +1 -0
  75. package/x/timeline/parts/compositor/samplers/html.d.ts +3 -0
  76. package/x/timeline/parts/compositor/samplers/html.js +106 -0
  77. package/x/timeline/parts/compositor/samplers/html.js.map +1 -0
  78. package/x/timeline/parts/compositor/samplers/webcodecs.d.ts +2 -0
  79. package/x/timeline/parts/compositor/samplers/webcodecs.js +55 -0
  80. package/x/timeline/parts/compositor/samplers/webcodecs.js.map +1 -0
  81. package/x/timeline/parts/item.d.ts +34 -8
  82. package/x/timeline/parts/item.js +6 -3
  83. package/x/timeline/parts/item.js.map +1 -1
  84. package/x/timeline/parts/media.d.ts +3 -0
  85. package/x/timeline/parts/media.js +17 -0
  86. package/x/timeline/parts/media.js.map +1 -1
  87. package/x/timeline/parts/waveform.js +1 -1
  88. package/x/timeline/parts/waveform.js.map +1 -1
  89. package/x/timeline/sugar/builders.d.ts +96 -0
  90. package/x/timeline/sugar/builders.js +108 -0
  91. package/x/timeline/sugar/builders.js.map +1 -0
  92. package/x/timeline/sugar/o.d.ts +21 -8
  93. package/x/timeline/sugar/o.js +63 -14
  94. package/x/timeline/sugar/o.js.map +1 -1
  95. package/x/timeline/sugar/omni-test.js +1 -1
  96. package/x/timeline/sugar/omni-test.js.map +1 -1
  97. package/x/timeline/sugar/omni.d.ts +7 -3
  98. package/x/timeline/sugar/omni.js +9 -8
  99. package/x/timeline/sugar/omni.js.map +1 -1
  100. package/x/timeline/timeline.d.ts +9 -0
  101. package/x/timeline/timeline.js +22 -0
  102. package/x/timeline/timeline.js.map +1 -0
  103. package/x/timeline/types.d.ts +24 -0
  104. package/x/timeline/types.js +2 -0
  105. package/x/timeline/types.js.map +1 -0
  106. package/x/timeline/utils/audio-stream.d.ts +6 -0
  107. package/x/timeline/utils/audio-stream.js +17 -0
  108. package/x/timeline/utils/audio-stream.js.map +1 -0
  109. package/x/timeline/utils/matrix.d.ts +8 -0
  110. package/x/timeline/utils/matrix.js +26 -0
  111. package/x/timeline/utils/matrix.js.map +1 -0
  112. package/x/timeline/utils/video-cursor.d.ts +10 -0
  113. package/x/timeline/utils/video-cursor.js +36 -0
  114. package/x/timeline/utils/video-cursor.js.map +1 -0
  115. package/x/tools/speech-recognition/whisper/parts/worker.bundle.min.js +6 -6
  116. package/x/tools/speech-recognition/whisper/parts/worker.bundle.min.js.map +4 -4
@@ -1,14 +1,35 @@
1
1
 
2
+ import {ALL_FORMATS, Input} from "mediabunny"
3
+
2
4
  import {Datafile} from "../utils/datafile.js"
5
+ import {DecoderSource} from "../../driver/fns/schematic.js"
6
+ import {loadDecoderSource} from "../../driver/utils/load-decoder-source.js"
3
7
 
4
8
  export class Media {
5
9
  duration = 0
10
+ hasVideo = false
11
+ hasAudio = false
12
+
6
13
  constructor(public datafile: Datafile) {}
7
14
 
8
15
  static async analyze(datafile: Datafile) {
9
16
  const media = new this(datafile)
10
17
  media.duration = 10
18
+ const {video, audio} = await this.#has("/assets/temp/gl.mp4")
19
+ media.hasAudio = audio
20
+ media.hasVideo = video
11
21
  return media
12
22
  }
23
+
24
+ static async #has(source: DecoderSource) {
25
+ const input = new Input({
26
+ formats: ALL_FORMATS,
27
+ source: await loadDecoderSource(source)
28
+ })
29
+ return {
30
+ audio: !!(await input.getPrimaryAudioTrack()),
31
+ video: !!(await input.getPrimaryVideoTrack())
32
+ }
33
+ }
13
34
  }
14
35
 
@@ -21,7 +21,7 @@ export class Waveform {
21
21
 
22
22
  static async init(source: DecoderSource, container: HTMLElement) {
23
23
  const driver = await context.driver
24
- const reader = driver.decode({ source }).audio.getReader()
24
+ const reader = driver.decodeAudio({source}).getReader()
25
25
 
26
26
  const peaks: number[] = []
27
27
  let buffer: number[] = []
@@ -0,0 +1,102 @@
1
+ import {O} from "./o.js"
2
+ import {Id} from "../parts/basics.js"
3
+ import {Item} from "../parts/item.js"
4
+
5
+ export class TimelineItem {
6
+ public readonly id: Id
7
+
8
+ constructor(public item: Item.Any) {
9
+ this.id = item.id
10
+ }
11
+
12
+ toJSON() {
13
+ return {
14
+ ...this.item
15
+ }
16
+ }
17
+ }
18
+
19
+ abstract class VisualItem extends TimelineItem {
20
+ abstract spatial(spatial: Spatial): TimelineItem
21
+ }
22
+
23
+ export class Stack extends VisualItem {
24
+ constructor(private o: O, public item: Item.Stack) {
25
+ super(item)
26
+ }
27
+
28
+ spatial(spatial: Spatial) {
29
+ this.item.spatialId = spatial.item.id
30
+ return this
31
+ }
32
+
33
+ addChildren(fn: (o: O) => TimelineItem | TimelineItem[]) {
34
+ const result = fn(this.o)
35
+ const items = Array.isArray(result) ? result : [result]
36
+ this.item.childrenIds.push(...items.map(c => c.item.id))
37
+ return this
38
+ }
39
+ }
40
+
41
+ export class Spatial extends TimelineItem {
42
+ constructor(public item: Item.Spatial) {super(item)}
43
+ }
44
+
45
+ export class Gap extends TimelineItem {
46
+ constructor(public item: Item.Gap) {super(item)}
47
+ }
48
+
49
+ export class Audio extends TimelineItem {
50
+ constructor(public item: Item.Audio) {super(item)}
51
+ }
52
+
53
+ export class Video extends VisualItem {
54
+ constructor(public item: Item.Video) {
55
+ super(item)
56
+ }
57
+
58
+ spatial(spatial: Spatial) {
59
+ this.item.spatialId = spatial.item.id
60
+ return this
61
+ }
62
+ }
63
+
64
+ export class Text extends VisualItem {
65
+ constructor(public item: Item.Text) {
66
+ super(item)
67
+ }
68
+
69
+ color(color: string) {
70
+ this.item.color = color
71
+ return this
72
+ }
73
+
74
+ spatial(spatial: Spatial) {
75
+ this.item.spatialId = spatial.item.id
76
+ return this
77
+ }
78
+ }
79
+
80
+ export class Sequence extends VisualItem {
81
+ constructor(private o: O, public item: Item.Sequence) {
82
+ super(item)
83
+ }
84
+
85
+ spatial(spatial: Spatial) {
86
+ this.item.spatialId = spatial.item.id
87
+ return this
88
+ }
89
+
90
+ addChildren(fn: (o: O) => TimelineItem | TimelineItem[]) {
91
+ const result = fn(this.o)
92
+ const items = Array.isArray(result) ? result : [result]
93
+ this.item.childrenIds.push(...items.map(c => c.item.id))
94
+ return this
95
+ }
96
+ }
97
+
98
+ export class Transition extends TimelineItem {
99
+ constructor(public item: Item.Transition) {
100
+ super(item)
101
+ }
102
+ }
@@ -3,16 +3,18 @@ import {MapG} from "@e280/stz"
3
3
  import {Id} from "../parts/basics.js"
4
4
  import {Media} from "../parts/media.js"
5
5
  import {Effect, Item, Kind} from "../parts/item.js"
6
+ import {Transform, TransformOptions, Vec2} from "../types.js"
7
+ import {Video, Gap, Sequence, Stack, Text, TimelineItem, Spatial, Audio, Transition} from "./builders.js"
6
8
 
7
9
  export class O {
8
10
  #nextId = 0
9
- #items = new MapG<Id, Item.Any>()
11
+ #items = new MapG<Id, TimelineItem>()
10
12
 
11
13
  #getId() {
12
14
  return this.#nextId++
13
15
  }
14
16
 
15
- register(item: Item.Any) {
17
+ register(item: TimelineItem) {
16
18
  if (!this.#items.has(item.id))
17
19
  this.#items.set(item.id, item)
18
20
  return item.id
@@ -22,39 +24,96 @@ export class O {
22
24
  return [...this.#items.values()]
23
25
  }
24
26
 
25
- sequence = (...items: Item.Any[]): Item.Sequence => ({
27
+ get itemsMap() {
28
+ return this.#items
29
+ }
30
+
31
+ spatial = (transform: Transform) => {
32
+ const item: Item.Spatial = {
33
+ id: this.#getId(),
34
+ kind: Kind.Spatial,
35
+ transform
36
+ }
37
+ const spatial = new Spatial(item)
38
+ this.register(spatial)
39
+ return spatial
40
+ }
41
+
42
+ sequence = (...items: TimelineItem[]) => new Sequence(this, {
26
43
  id: this.#getId(),
27
44
  kind: Kind.Sequence,
28
- children: items.map(item => this.register(item)),
45
+ childrenIds: items.map((item) => this.register(item))
29
46
  })
30
47
 
31
- stack = (...items: Item.Any[]): Item.Stack => ({
32
- id: this.#getId(),
48
+ stack = (...items: TimelineItem[]) => new Stack(this, {
33
49
  kind: Kind.Stack,
34
- children: items.map(item => this.register(item)),
50
+ id: this.#getId(),
51
+ childrenIds: items.map(item => this.register(item))
35
52
  })
36
53
 
37
- clip = (media: Media, start?: number, duration?: number): Item.Clip => ({
54
+ video = (media: Media, options?: {start?: number, duration?: number}) => {
55
+ if(!media.hasVideo)
56
+ throw new Error(`Video clip error: media "${media.datafile.filename}" has no video track.`)
57
+
58
+ const item: Item.Video = {
59
+ kind: Kind.Video,
60
+ id: this.#getId(),
61
+ mediaHash: media.datafile.checksum.hash,
62
+ start: options?.start ?? 0,
63
+ duration: options?.duration ?? media.duration
64
+ }
65
+
66
+ return new Video(item)
67
+ }
68
+
69
+ audio = (media: Media, options?: {start?: number, duration?: number}) => {
70
+ if(!media.hasAudio)
71
+ throw new Error(`Audio clip error: media "${media.datafile.filename}" has no audio track.`)
72
+
73
+ const item: Item.Audio = {
74
+ kind: Kind.Audio,
75
+ id: this.#getId(),
76
+ mediaHash: media.datafile.checksum.hash,
77
+ start: options?.start ?? 0,
78
+ duration: options?.duration ?? media.duration
79
+ }
80
+
81
+ return new Audio(item)
82
+ }
83
+
84
+ text = (content: string) => new Text({
38
85
  id: this.#getId(),
39
- kind: Kind.Clip,
40
- mediaHash: media.datafile.checksum.hash,
41
- start: start ?? 0,
42
- duration: duration ?? media.duration,
86
+ content,
87
+ kind: Kind.Text,
88
+ color: "#FFFFF"
43
89
  })
44
90
 
45
- text = (content: string): Item.Text => ({
91
+ gap = (duration: number) => new Gap({
46
92
  id: this.#getId(),
47
- kind: Kind.Text,
48
- content,
93
+ kind: Kind.Gap,
94
+ duration
49
95
  })
50
96
 
51
97
  transition = {
52
- crossfade: (duration: number): Item.Transition => ({
98
+ crossfade: (duration: number) => new Transition({
53
99
  id: this.#getId(),
54
100
  kind: Kind.Transition,
55
101
  effect: Effect.Crossfade,
56
102
  duration,
57
103
  }),
58
104
  }
105
+
106
+ transform = (options?: TransformOptions): Transform => {
107
+ const position: Vec2 = [
108
+ options?.position?.[0] ?? 0,
109
+ options?.position?.[1] ?? 0
110
+ ]
111
+ const scale: Vec2 = [
112
+ options?.scale?.[0] ?? 1,
113
+ options?.scale?.[1] ?? 1
114
+ ]
115
+ const rotation = options?.rotation ?? 0
116
+ return [position, scale, rotation]
117
+ }
59
118
  }
60
119
 
@@ -22,10 +22,10 @@ const {mediaA, mediaB} = await omni.load({
22
22
  //
23
23
 
24
24
  const timeline = omni.timeline(o => o.sequence(
25
- o.clip(mediaA),
25
+ o.video(mediaA),
26
26
  o.transition.crossfade(600),
27
27
  o.stack(
28
- o.clip(mediaB),
28
+ o.video(mediaB),
29
29
  o.text("hello world"),
30
30
  ),
31
31
  ))
@@ -1,13 +1,17 @@
1
1
 
2
2
  import {O} from "./o.js"
3
- import {Item} from "../parts/item.js"
3
+ import {Timeline} from "../timeline.js"
4
4
  import {Media} from "../parts/media.js"
5
- import {TimelineFile} from "../parts/basics.js"
5
+ import {TimelineItem} from "./builders.js"
6
6
  import {Datafile} from "../utils/datafile.js"
7
+ import {TimelineFile} from "../parts/basics.js"
8
+ import {Export} from "../parts/compositor/export.js"
7
9
  import {ResourcePool} from "../parts/resource-pool.js"
10
+ import {RenderConfig} from "../../driver/fns/schematic.js"
8
11
 
9
12
  export class Omni {
10
13
  resources = new ResourcePool()
14
+ #export = new Export()
11
15
 
12
16
  load = async<S extends Record<string, Promise<Datafile>>>(spec: S) => {
13
17
  return Object.fromEntries(await Promise.all(Object.entries(spec).map(
@@ -15,16 +19,15 @@ export class Omni {
15
19
  ))) as {[K in keyof S]: Media}
16
20
  }
17
21
 
18
- timeline = (fn: (o: O) => Item.Sequence): TimelineFile => {
22
+ timeline = (fn: (o: O) => TimelineItem): Timeline => {
19
23
  const o = new O()
20
- const sequence = fn(o)
21
- return {
22
- format: "timeline",
23
- info: "https://omniclip.app/",
24
- version: 0,
25
- root: o.register(sequence),
26
- items: o.items,
27
- }
24
+ const root = fn(o)
25
+ o.register(root)
26
+ return new Timeline(root, o.itemsMap)
27
+ }
28
+
29
+ render = async (timeline: TimelineFile, config: RenderConfig) => {
30
+ await this.#export.render(timeline)
28
31
  }
29
32
  }
30
33
 
@@ -0,0 +1,22 @@
1
+ import {TimelineItem} from "./sugar/builders.js"
2
+ import {Id, TimelineFile} from "./parts/basics.js"
3
+
4
+ export class Timeline {
5
+ constructor(public root: TimelineItem, private items: Map<Id, TimelineItem>) {}
6
+
7
+ require<T extends TimelineItem>(id: Id): T {
8
+ const item = this.items.get(id)
9
+ return item as T
10
+ }
11
+
12
+ toJSON(): TimelineFile {
13
+ return {
14
+ format: "timeline",
15
+ info: "https://omniclip.app/",
16
+ version: 0,
17
+ rootId: this.root.item.id,
18
+ items: Array.from(this.items.values()).map(item => item.toJSON())
19
+ }
20
+ }
21
+ }
22
+
@@ -0,0 +1,29 @@
1
+ export type Interpolation = "linear" | "catmullRom"
2
+ export type Keyframe<Value = number> = [time: number, value: Value]
3
+ export type Keyframes<Value = number> = Keyframe<Value>[]
4
+ export type Vec2 = [x: number, y: number]
5
+ export type Transform = [position: Vec2, scale: Vec2, rotation: number]
6
+
7
+ export type TrackVec2 = {
8
+ x: Keyframes
9
+ y: Keyframes
10
+ }
11
+
12
+ export type Anim<T> = {
13
+ terp: Interpolation
14
+ track: T
15
+ }
16
+
17
+ export type Animations = Anim<TrackTransform>
18
+
19
+ export type TrackTransform = {
20
+ position: TrackVec2
21
+ scale: TrackVec2
22
+ rotation: Keyframes
23
+ }
24
+
25
+ export type TransformOptions = {
26
+ position?: Vec2
27
+ scale?: Vec2
28
+ rotation?: number
29
+ }
@@ -0,0 +1,15 @@
1
+ export class AudioStream {
2
+ constructor(private reader: ReadableStreamDefaultReader<AudioData>) {}
3
+
4
+ async *stream(): AsyncGenerator<AudioData> {
5
+ while (true) {
6
+ const {done, value: hit} = await this.reader.read()
7
+ if (done) {
8
+ break
9
+ }
10
+ yield hit
11
+ }
12
+ }
13
+
14
+ cancel = async () => await this.reader.cancel()
15
+ }
@@ -0,0 +1,33 @@
1
+ import {Matrix} from "pixi.js"
2
+ import {Transform} from "../types.js"
3
+
4
+ export const transformToMat6 = (t: Transform): Mat6 => {
5
+ const [pos, scl, rotDeg] = t
6
+ const [x, y] = pos
7
+ const [sx, sy] = scl
8
+ const r = rotDeg * Math.PI / 180
9
+ const cos = Math.cos(r)
10
+ const sin = Math.sin(r)
11
+ return [cos * sx, sin * sx, -sin * sy, cos * sy, x, y]
12
+ }
13
+
14
+ export const mat6ToMatrix = ([a, b, c, d, tx, ty]: Mat6): Matrix =>
15
+ new Matrix(a, b, c, d, tx, ty)
16
+
17
+ export const transformToMatrix = (t: Transform) => mat6ToMatrix(transformToMat6(t))
18
+
19
+ export const mul6 = (local: Mat6, parent: Mat6): Mat6 => {
20
+ const [a1, b1, c1, d1, tx1, ty1] = local
21
+ const [a2, b2, c2, d2, tx2, ty2] = parent
22
+ return [
23
+ a1 * a2 + c1 * b2,
24
+ b1 * a2 + d1 * b2,
25
+ a1 * c2 + c1 * d2,
26
+ b1 * c2 + d1 * d2,
27
+ a1 * tx2 + c1 * ty2 + tx1,
28
+ b1 * tx2 + d1 * ty2 + ty1
29
+ ]
30
+ }
31
+
32
+ export const I6: Mat6 = [1, 0, 0, 1, 0, 0]
33
+ export type Mat6 = [a: number, b: number, c: number, d: number, tx: number, ty: number]
@@ -0,0 +1,40 @@
1
+ /**
2
+ * A stateful, forward-only frame cursor for a single clip instance.
3
+ * It efficiently reads a video stream to find the frame nearest to a target timestamp.
4
+ */
5
+
6
+ export class VideoCursor {
7
+ constructor(private reader: ReadableStreamDefaultReader<VideoFrame>) {}
8
+
9
+ async atOrNear(targetUs: number): Promise<VideoFrame | undefined> {
10
+ let prev: VideoFrame | null = null
11
+ while (true) {
12
+ const {done, value: hit} = await this.reader.read()
13
+
14
+ if (done) {
15
+ const out = prev ? new VideoFrame(prev) : undefined
16
+ prev?.close()
17
+ return out
18
+ }
19
+
20
+ const hitUs = hit.timestamp ?? 0
21
+ if (hitUs >= targetUs) {
22
+ const prevUs = prev?.timestamp ?? Number.NEGATIVE_INFINITY
23
+ const usePrev = !!prev && Math.abs(prevUs - targetUs) < Math.abs(hitUs - targetUs)
24
+
25
+ const chosen = usePrev ? prev! : hit
26
+ const other = usePrev ? hit : prev
27
+
28
+ const copy = new VideoFrame(chosen)
29
+ chosen.close()
30
+ other?.close()
31
+ return copy
32
+ }
33
+
34
+ prev?.close()
35
+ prev = hit
36
+ }
37
+ }
38
+
39
+ cancel = async () => await this.reader.cancel()
40
+ }