@banou/media-player 0.8.15 → 0.8.16

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.
package/README.md CHANGED
@@ -1,3 +1,5 @@
1
+
2
+
1
3
  # @banou/media-player
2
4
 
3
5
  A React video player for files the browser cannot open on its own. It takes a `read(offset, size)` and
@@ -21,6 +23,7 @@ import MediaPlayer from '@banou/media-player'
21
23
  libavWorkerUrl="/libav-worker.js"
22
24
  jassubWorkerUrl={jassubWorkerUrl}
23
25
  jassubWasmUrl="/jassub-worker-modern.wasm"
26
+ jassubLegacyWasmUrl="/jassub-worker.wasm"
24
27
  defaultFontUrl="/default.woff2"
25
28
  title="episode.mkv"
26
29
  autoplay
@@ -1,2 +1,2 @@
1
- import { a as e, c as t, d as n, f as r, i, l as a, n as o, o as s, r as c, s as l, t as u, u as d } from "../engine-CDdjRm_U.js";
1
+ import { a as e, c as t, d as n, f as r, i, l as a, n as o, o as s, r as c, s as l, t as u, u as d } from "../engine-DPnChmYc.js";
2
2
  export { i as DEFAULT_BUFFER_SIZE, e as MediaElementError, a as SUBTITLES_OFF, u as createPictureInPicture, d as createSubtitleRenderer, c as createThumbnailGenerator, n as getTimeRanges, s as isMediaElementError, o as pictureInPictureMode, l as startPlayback, t as terminateRemuxer, r as updateSourceBuffer };
@@ -32,34 +32,10 @@ var a = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
32
32
  } catch {}
33
33
  })
34
34
  };
35
- }, c = -1, l = (e) => new Date(e).toISOString().slice(11, 22), u = (e, t) => e.createStyle({
36
- ...t,
37
- treat_fontname_as_pattern: 0,
38
- Blur: 0,
39
- Justify: 0,
40
- FontName: t.Fontname,
41
- FontSize: Number(t.Fontsize),
42
- PrimaryColour: Number(t.PrimaryColour),
43
- SecondaryColour: Number(t.SecondaryColour),
44
- OutlineColour: Number(t.OutlineColour),
45
- BackColour: Number(t.BackColour),
46
- Bold: Number(t.Bold),
47
- Italic: Number(t.Italic),
48
- Underline: Number(t.Underline),
49
- StrikeOut: Number(t.StrikeOut),
50
- ScaleX: Number(t.ScaleX),
51
- ScaleY: Number(t.ScaleY),
52
- Spacing: Number(t.Spacing),
53
- Angle: Number(t.Angle),
54
- BorderStyle: Number(t.BorderStyle),
55
- Outline: Number(t.Outline),
56
- Shadow: Number(t.Shadow),
57
- Alignment: Number(t.Alignment),
58
- MarginL: Number(t.MarginL),
59
- MarginR: Number(t.MarginR),
60
- MarginV: Number(t.MarginV),
61
- Encoding: Number(t.Encoding)
62
- }), d = (e) => {
35
+ }, c = -1, l = (e, t) => e.createEvent(t), u = (e) => new Date(e).toISOString().slice(11, 22), d = 1, f = (e) => new Map(r(e).styles.style.map((e, t) => [e.Name, t + d])), p = (e, t) => {
36
+ let n = t.replace(/^\*+/, ""), r = n.toLowerCase() === "default" ? "Default" : n;
37
+ return e.styles.get(r) ?? e.styles.get("Default") ?? 0;
38
+ }, m = (e) => {
63
39
  let t = r(e);
64
40
  return i({
65
41
  ...t,
@@ -70,17 +46,17 @@ var a = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
70
46
  LayoutResY: ""
71
47
  }
72
48
  });
73
- }, f = (e) => {
49
+ }, h = (e) => {
74
50
  let t = e.content.match(/\r?\n\[Events\]\r?\nFormat: (.*)/)?.[0];
75
51
  return t ? {
76
52
  type: "header",
77
53
  streamIndex: e.streamIndex,
78
54
  content: e.content,
79
55
  eventsContent: t,
80
- parsed: r(e.content)
56
+ styles: f(e.content)
81
57
  } : (console.warn(`subtitle stream ${e.streamIndex} has no Events format, ignoring the track`), null);
82
- }, p = (e, t) => {
83
- let [n, i] = t.content.split(","), a = Number(n), o = `Dialogue: ${i},${l(t.start)},${l(t.end)},${t.content.replace(`${a},${i},`, "")}`, s = r(`${e.eventsContent}\r\n${o}`).events.dialogue[0];
58
+ }, g = (e, t) => {
59
+ let [n, i] = t.content.split(","), a = Number(n), o = `Dialogue: ${i},${u(t.start)},${u(t.end)},${t.content.replace(`${a},${i},`, "")}`, s = r(`${e.eventsContent}\r\n${o}`).events.dialogue[0];
84
60
  if (!s) throw Error("dialogue event is undefined");
85
61
  return {
86
62
  type: "dialogue",
@@ -88,6 +64,7 @@ var a = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
88
64
  index: a,
89
65
  assEvent: {
90
66
  ...s,
67
+ Style: p(e, s.Style),
91
68
  Effect: s.Effect ?? "",
92
69
  Text: s.Text.raw,
93
70
  Duration: (s.End - s.Start) * 1e3,
@@ -97,63 +74,61 @@ var a = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
97
74
  _index: a
98
75
  }
99
76
  };
100
- }, m = (e) => {
101
- let { video: t, canvas: r, workerUrl: i, wasmUrl: a, legacyWasmUrl: o, defaultFontUrl: s } = e, c, l = [], m = /* @__PURE__ */ new Map(), h = [], g = /* @__PURE__ */ new Map(), _, v = e.onStreams, y = setInterval(() => {
77
+ }, _ = (e) => {
78
+ let { video: t, canvas: r, workerUrl: i, wasmUrl: a, legacyWasmUrl: o, defaultFontUrl: s } = e, c, u = [], d = /* @__PURE__ */ new Map(), f = [], p = /* @__PURE__ */ new Map(), _, v = e.onStreams, y = setInterval(() => {
102
79
  c?.setCurrentTime(t.paused, t.currentTime, t.playbackRate);
103
80
  }, 100), b = () => c?.setRate(t.playbackRate), x = (e) => {
104
81
  c = new n({
105
82
  onDemandRender: !1,
106
83
  video: t,
107
84
  canvas: r,
108
- subContent: d(e.content),
85
+ subContent: m(e.content),
109
86
  workerUrl: i,
110
87
  modernWasmUrl: a,
111
88
  ...o ? { wasmUrl: o } : {},
112
- fonts: l.map(([, e]) => e),
89
+ fonts: u.map(([, e]) => e),
113
90
  availableFonts: {
114
- ...Object.fromEntries(l),
91
+ ...Object.fromEntries(u),
115
92
  ...s ? { "liberation sans": s } : {}
116
93
  }
117
94
  }), t.removeEventListener("ratechange", c._boundSetRate), t.addEventListener("ratechange", b);
118
- for (let t of e.parsed.styles.style) u(c, t);
119
95
  };
120
96
  return {
121
97
  pushAttachments: (e) => {
122
- l = [...l, ...e.map((e) => [e.filename, new Uint8Array(e.data)])];
98
+ u = [...u, ...e.map((e) => [e.filename, new Uint8Array(e.data)])];
123
99
  },
124
100
  pushFragments: (e) => {
125
101
  for (let t of e) if (t.type === "header") {
126
- if (m.has(t.streamIndex)) continue;
127
- let e = f(t);
102
+ if (d.has(t.streamIndex)) continue;
103
+ let e = h(t);
128
104
  if (!e) continue;
129
- m.set(t.streamIndex, e), h.push({
105
+ d.set(t.streamIndex, e), f.push({
130
106
  streamIndex: t.streamIndex,
131
107
  title: t.title,
132
108
  language: t.language
133
- }), v?.([...h]), _ === void 0 && (_ = t.streamIndex), c || x(e);
109
+ }), v?.([...f]), _ === void 0 && (_ = t.streamIndex), c || x(e);
134
110
  } else {
135
- let e = m.get(t.streamIndex);
111
+ let e = d.get(t.streamIndex);
136
112
  if (!e) continue;
137
- let n = g.get(t.streamIndex);
138
- n || (n = /* @__PURE__ */ new Map(), g.set(t.streamIndex, n));
139
- let r = p(e, t);
113
+ let n = p.get(t.streamIndex);
114
+ n || (n = /* @__PURE__ */ new Map(), p.set(t.streamIndex, n));
115
+ let r = g(e, t);
140
116
  if (n.has(r.index)) continue;
141
- n.set(r.index, r), _ === t.streamIndex && c?.createEvent(r.assEvent);
117
+ n.set(r.index, r), _ === t.streamIndex && c && l(c, r.assEvent);
142
118
  }
143
119
  },
144
120
  selectStream: (e) => {
145
121
  let n = e ?? -1;
146
122
  if (n === _ || !c) return;
147
123
  _ = n, c.freeTrack();
148
- let r = m.get(n);
124
+ let r = d.get(n);
149
125
  if (r) {
150
- c.setTrack(d(r.content));
151
- for (let e of r.parsed.styles.style) u(c, e);
152
- for (let e of g.get(n)?.values() ?? []) c.createEvent(e.assEvent);
126
+ c.setTrack(m(r.content));
127
+ for (let e of p.get(n)?.values() ?? []) l(c, e.assEvent);
153
128
  c.setCurrentTime(t.paused, t.currentTime, t.playbackRate);
154
129
  }
155
130
  },
156
- getStreams: () => [...h],
131
+ getStreams: () => [...f],
157
132
  getSelectedStream: () => _,
158
133
  setOnStreams: (e) => {
159
134
  v = e;
@@ -162,24 +137,24 @@ var a = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
162
137
  clearInterval(y), t.removeEventListener("ratechange", b), c?.destroy(), c = void 0;
163
138
  }
164
139
  };
165
- }, h = class extends Error {
140
+ }, v = class extends Error {
166
141
  mediaElement = !0;
167
142
  constructor(e, t) {
168
143
  super(e, t), this.name = "MediaElementError";
169
144
  }
170
- }, g = (e) => !!e && typeof e == "object" && e.mediaElement === !0, _ = -20, v = 60, y = 30, b = -5, x = 20, ee = 5, S = 15e3, C = 1, w = 3, te = 12, T = 200, E = 250, D = 25e5, O = (e) => {
145
+ }, y = (e) => !!e && typeof e == "object" && e.mediaElement === !0, b = -20, x = 60, S = 30, C = -5, w = 20, T = 5, ee = 15e3, E = 1, D = 3, te = 12, ne = 200, re = 250, O = 25e5, k = (e) => {
171
146
  let { worker: t } = e, n = setTimeout(() => t.terminate(), 2e3);
172
147
  e.destroy().catch(() => {}).finally(() => {
173
148
  clearTimeout(n), t.terminate();
174
149
  });
175
- }, k = async (t) => {
176
- let { videoElement: n, canvasElement: r, read: i, length: o, publicPath: c, libavWorkerUrl: l, jassubWorkerUrl: u, jassubWasmUrl: d, jassubLegacyWasmUrl: f, defaultFontUrl: p, bufferSize: g = D, audioStreamIndex: k, onReady: ne, onError: re, onRecovered: ie, onSeek: ae, onSubtitleStreams: oe, onAudioStreams: se } = t, A = await e({
150
+ }, ie = async (t) => {
151
+ let { videoElement: n, canvasElement: r, read: i, length: o, publicPath: c, libavWorkerUrl: l, jassubWorkerUrl: u, jassubWasmUrl: d, jassubLegacyWasmUrl: f, defaultFontUrl: p, bufferSize: m = O, audioStreamIndex: h, onReady: g, onError: y, onRecovered: ie, onSeek: ae, onSubtitleStreams: oe, onAudioStreams: se } = t, A = await e({
177
152
  publicPath: c,
178
153
  workerUrl: l,
179
154
  workerOptions: { type: "module" },
180
- bufferSize: g,
155
+ bufferSize: m,
181
156
  length: o,
182
- audioStreamIndex: k,
157
+ audioStreamIndex: h,
183
158
  read: i
184
159
  }), j = [], M = !1, N = () => {
185
160
  if (!M) {
@@ -189,11 +164,11 @@ var a = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
189
164
  } catch {}
190
165
  }
191
166
  };
192
- j.push(() => O(A));
167
+ j.push(() => k(A));
193
168
  try {
194
- let e = await A.init(), t = e.audioStreams ?? [], i = t.some((e) => e.streamIndex === k) ? k : t[0]?.streamIndex ?? -1;
169
+ let e = await A.init(), t = e.audioStreams ?? [], i = t.some((e) => e.streamIndex === h) ? h : t[0]?.streamIndex ?? -1;
195
170
  se?.(t, i);
196
- let o = m({
171
+ let o = _({
197
172
  video: n,
198
173
  canvas: r,
199
174
  workerUrl: u,
@@ -205,85 +180,85 @@ var a = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
205
180
  if (j.push(() => o.destroy()), e.attachments?.length && o.pushAttachments(e.attachments), !e.info.output.videoMimeType) throw Error("The video codec in this file could not be identified");
206
181
  let c = [e.info.output.videoMimeType, e.info.output.audioMimeType].filter(Boolean).join(","), l = `video/mp4; codecs="${c}"`;
207
182
  if (!MediaSource.isTypeSupported(l)) throw Error(`This browser cannot play the codecs in this file: ${c}`);
208
- let g = new MediaSource(), D = URL.createObjectURL(g);
209
- j.push(() => URL.revokeObjectURL(D)), n.src = D, j.push(() => {
183
+ let m = new MediaSource(), O = URL.createObjectURL(m);
184
+ j.push(() => URL.revokeObjectURL(O)), n.src = O, j.push(() => {
210
185
  try {
211
186
  n.pause();
212
187
  } catch {}
213
188
  n.removeAttribute("src"), n.load();
214
189
  });
215
- let O = await new Promise((t, n) => {
190
+ let k = await new Promise((t, n) => {
216
191
  let r = setTimeout(() => {
217
192
  i(), n(/* @__PURE__ */ Error("The MediaSource never opened"));
218
- }, S), i = () => {
219
- clearTimeout(r), g.removeEventListener("sourceopen", a), g.removeEventListener("sourceclose", o), g.removeEventListener("error", o);
193
+ }, ee), i = () => {
194
+ clearTimeout(r), m.removeEventListener("sourceopen", a), m.removeEventListener("sourceclose", o), m.removeEventListener("error", o);
220
195
  }, a = () => {
221
196
  try {
222
- let n = g.addSourceBuffer(l);
197
+ let n = m.addSourceBuffer(l);
223
198
  n.mode = "segments";
224
199
  let r = e.info.input.duration;
225
- Number.isFinite(r) && r > 0 && (g.duration = r), i(), t(n);
200
+ Number.isFinite(r) && r > 0 && (m.duration = r), i(), t(n);
226
201
  } catch (e) {
227
202
  i(), n(e);
228
203
  }
229
204
  }, o = () => {
230
205
  i(), n(/* @__PURE__ */ Error("The MediaSource closed before a SourceBuffer could be created"));
231
206
  };
232
- g.addEventListener("sourceopen", a, { once: !0 }), g.addEventListener("sourceclose", o, { once: !0 }), g.addEventListener("error", o, { once: !0 });
233
- }), { appendBuffer: P, unbufferRange: ce, updateTimestampOffset: le, endOfStream: ue } = s(O, g);
234
- await P(e.data), e.subtitles?.length && o.pushFragments(e.subtitles), ne?.();
235
- let F = !1, I = !1, L = 0, R, de = () => R ?? n.currentTime, z = !1, B = 0, V = 0, H = null, U = 0, W = 0, G = !1, K = 0, q = (e, t, n = !1) => {
207
+ m.addEventListener("sourceopen", a, { once: !0 }), m.addEventListener("sourceclose", o, { once: !0 }), m.addEventListener("error", o, { once: !0 });
208
+ }), { appendBuffer: P, unbufferRange: ce, updateTimestampOffset: le, endOfStream: ue } = s(k, m);
209
+ await P(e.data), e.subtitles?.length && o.pushFragments(e.subtitles), g?.();
210
+ let F = !1, I = !1, L = 0, R, de = () => R ?? n.currentTime, z = !1, B = 0, V = 0, H = null, U = 0, W = 0, G = !1, fe = 0, K = (e, t, n = !1) => {
236
211
  let r = e?.message === "Cancelled";
237
- t && r || (console.error(e), !(G && !n) && (G = !0, re?.(r ? Error("Reading the video file failed", { cause: e }) : e)));
238
- }, fe = async (e = !1) => {
212
+ t && r || (console.error(e), !(G && !n) && (G = !0, y?.(r ? Error("Reading the video file failed", { cause: e }) : e)));
213
+ }, pe = async (e = !1) => {
239
214
  if (L > 0 && !e) return;
240
- let t = n.currentTime, r = e ? b : _, i = e ? x : v;
241
- for (let { start: e, end: n } of a(O)) e < t + r && await ce(e, t + r), n > t + i && await ce(t + i, n);
242
- }, pe = () => {
243
- let e = a(O);
244
- if (!e.length) return !0;
245
- let t = de(), n = e.find((e) => e.start <= t + C && t < e.end);
246
- return n && n.end >= t + v ? !1 : !n || n.end < t + y;
215
+ let t = n.currentTime, r = e ? C : b, i = e ? w : x;
216
+ for (let { start: e, end: n } of a(k)) e < t + r && await ce(e, t + r), n > t + i && await ce(t + i, n);
247
217
  }, me = () => {
248
- let e = n.currentTime;
249
- return a(O).find((t) => t.start <= e + C && e < t.end);
218
+ let e = a(k);
219
+ if (!e.length) return !0;
220
+ let t = de(), n = e.find((e) => e.start <= t + E && t < e.end);
221
+ return n && n.end >= t + x ? !1 : !n || n.end < t + S;
250
222
  }, he = () => {
251
- let e = me();
252
- return !!e && e.end >= W - .1;
223
+ let e = n.currentTime;
224
+ return a(k).find((t) => t.start <= e + E && e < t.end);
253
225
  }, ge = () => {
254
- let e = a(O).map((e) => e.end);
226
+ let e = he();
227
+ return !!e && e.end >= W - .1;
228
+ }, _e = () => {
229
+ let e = a(k).map((e) => e.end);
255
230
  e.length && (W = Math.max(W, ...e));
256
- }, J = async (e) => {
257
- I = !0, H = null, U = 0, K = e;
231
+ }, q = async (e) => {
232
+ I = !0, H = null, U = 0, fe = e;
258
233
  let t = ++V;
259
234
  try {
260
235
  let { data: n, pts: r, subtitles: i, finished: a } = await A.seek(e);
261
236
  if (M || t !== V || (a && (z = !0), i?.length && o.pushFragments(i), await le(r), M || t !== V)) return;
262
- n.byteLength && (await P(n), ge());
237
+ n.byteLength && (await P(n), _e());
263
238
  } catch (e) {
264
- q(e, M || t !== V);
239
+ K(e, M || t !== V);
265
240
  } finally {
266
241
  t === V && (I = !1);
267
242
  }
268
- }, _e = async () => {
243
+ }, ve = async () => {
269
244
  let e = H;
270
245
  if (e) try {
271
- await P(e), ge(), H === e && (H = null, U = 0);
246
+ await P(e), _e(), H === e && (H = null, U = 0);
272
247
  } catch (t) {
273
248
  if (H !== e) return;
274
249
  if (t?.name === "QuotaExceededError") {
275
- let t = await fe(!0).then(() => !0, () => !1);
250
+ let t = await pe(!0).then(() => !0, () => !1);
276
251
  if (H !== e) return;
277
252
  if (t) {
278
- z = !1, await J(me()?.end ?? n.currentTime);
253
+ z = !1, await q(he()?.end ?? n.currentTime);
279
254
  return;
280
255
  }
281
256
  }
282
- if (U += 1, U < ee) return;
257
+ if (U += 1, U < T) return;
283
258
  throw H = null, t;
284
259
  }
285
- }, ve = async () => {
286
- if (F || I || $ !== void 0 || M || !H && (z || !pe())) return;
260
+ }, ye = async () => {
261
+ if (F || I || Q !== void 0 || M || !H && (z || !me())) return;
287
262
  let e = ++B, t = V, n = () => M || e !== B || t !== V;
288
263
  F = !0;
289
264
  try {
@@ -292,78 +267,78 @@ var a = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
292
267
  if (n() || (r && (z = !0), t?.length && o.pushFragments(t), !e.byteLength)) return;
293
268
  H = e, U = 0;
294
269
  }
295
- await _e(), !n() && G && (G = !1, ie?.());
270
+ await ve(), !n() && G && (G = !1, ie?.());
296
271
  } catch (e) {
297
- q(e, n());
272
+ K(e, n());
298
273
  } finally {
299
274
  e === B && (F = !1);
300
275
  }
301
- }, ye = (e) => e >= K && !!me(), be = (e) => a(O).some((t) => t.start <= e + C && e < t.end), Y = (e) => {
302
- let t = a(O).find((t) => t.start <= e + C && e < t.end);
276
+ }, be = (e) => e >= fe && !!he(), xe = (e) => a(k).some((t) => t.start <= e + E && e < t.end), J = (e) => {
277
+ let t = a(k).find((t) => t.start <= e + E && e < t.end);
303
278
  return t ? t.end - e : 0;
304
- }, xe = async (e) => {
305
- if (!M && !(Y(e) >= w)) {
279
+ }, Se = async (e) => {
280
+ if (!M && !(J(e) >= D)) {
306
281
  z = !1, L++, R = e;
307
282
  try {
308
- be(e) || await J(e);
283
+ xe(e) || await q(e);
309
284
  let t = V;
310
- for (let n = 0; n < te && !(M || t !== V || z || Y(e) >= w); n++) {
311
- let t = Y(e);
312
- if (await ve(), Y(e) <= t) break;
285
+ for (let n = 0; n < te && !(M || t !== V || z || J(e) >= D); n++) {
286
+ let t = J(e);
287
+ if (await ye(), J(e) <= t) break;
313
288
  }
314
289
  } finally {
315
290
  L--, R === e && (R = void 0);
316
291
  }
317
292
  }
318
- }, X = 0, Z = null, Q, Se = (e) => {
319
- let t = performance.now(), n = t - X;
320
- if (n >= T) {
321
- X = t, Z = null, ae?.(e);
293
+ }, Y = 0, X = null, Z, Ce = (e) => {
294
+ let t = performance.now(), n = t - Y;
295
+ if (n >= ne) {
296
+ Y = t, X = null, ae?.(e);
322
297
  return;
323
298
  }
324
- Z = e, !Q && (Q = setTimeout(() => {
325
- Q = void 0;
326
- let e = Z;
327
- Z = null, !(e === null || M) && (X = performance.now(), ae?.(e));
328
- }, T - n));
299
+ X = e, !Z && (Z = setTimeout(() => {
300
+ Z = void 0;
301
+ let e = X;
302
+ X = null, !(e === null || M) && (Y = performance.now(), ae?.(e));
303
+ }, ne - n));
329
304
  };
330
305
  j.push(() => {
331
- Q && clearTimeout(Q);
306
+ Z && clearTimeout(Z);
332
307
  });
333
- let Ce = 0, $;
308
+ let we = 0, Q;
334
309
  j.push(() => {
335
- $ && clearTimeout($);
310
+ Q && clearTimeout(Q);
336
311
  });
337
- let we = () => {
312
+ let Te = () => {
338
313
  let t = n.currentTime, r = e.info.input.duration || n.duration;
339
- r > 0 && Se(Math.min(Math.max(t / r, 0), 1));
340
- let i = performance.now(), a = i - Ce < E;
341
- if (Ce = i, !ye(t) && !(I && Math.abs(t - K) < .001)) {
342
- if (z = !1, $ && clearTimeout($), !a) {
343
- $ = void 0, J(t);
314
+ r > 0 && Ce(Math.min(Math.max(t / r, 0), 1));
315
+ let i = performance.now(), a = i - we < re;
316
+ if (we = i, !be(t) && !(I && Math.abs(t - fe) < .001)) {
317
+ if (z = !1, Q && clearTimeout(Q), !a) {
318
+ Q = void 0, q(t);
344
319
  return;
345
320
  }
346
- $ = setTimeout(() => {
347
- if ($ = void 0, M) return;
321
+ Q = setTimeout(() => {
322
+ if (Q = void 0, M) return;
348
323
  let e = n.currentTime;
349
- ye(e) || (z = !1, J(e));
350
- }, E);
324
+ be(e) || (z = !1, q(e));
325
+ }, re);
351
326
  }
352
327
  };
353
- n.addEventListener("seeking", we), j.push(() => n.removeEventListener("seeking", we));
354
- let Te = !1, Ee = () => {
355
- if (Te) return;
356
- Te = !0;
328
+ n.addEventListener("seeking", Te), j.push(() => n.removeEventListener("seeking", Te));
329
+ let $ = !1, Ee = () => {
330
+ if ($) return;
331
+ $ = !0;
357
332
  let e = n.error;
358
- q(new h(`the media element failed: ${e?.message ?? "unknown"}`, { cause: e }), !1, !0);
333
+ K(new v(`the media element failed: ${e?.message ?? "unknown"}`, { cause: e }), !1, !0);
359
334
  };
360
335
  n.addEventListener("error", Ee), j.push(() => n.removeEventListener("error", Ee));
361
336
  let De = setInterval(() => {
362
- Te || n.error || (fe().catch(() => {}), ve().catch((e) => q(e, !1)), z && !H && he() && g.readyState === "open" && ue().catch(() => {}));
337
+ $ || n.error || (pe().catch(() => {}), ye().catch((e) => K(e, !1)), z && !H && ge() && m.readyState === "open" && ue().catch(() => {}));
363
338
  }, 100);
364
339
  return j.push(() => clearInterval(De)), {
365
340
  destroy: N,
366
- prepareSeek: xe,
341
+ prepareSeek: Se,
367
342
  videoElement: n,
368
343
  selectSubtitleStream: (e) => o.selectStream(e),
369
344
  indexes: e.indexes ?? [],
@@ -374,8 +349,8 @@ var a = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
374
349
  } catch (e) {
375
350
  throw N(), e;
376
351
  }
377
- }, ne = 5, re = 500, ie = 320, ae = 1e6, oe = 3, se = 1e4, A = async (e) => {
378
- let { publicPath: n, workerUrl: r, length: i, read: a, onThumbnails: o, width: s = ie } = e, c = await t({
352
+ }, ae = 5, oe = 500, se = 320, A = 1e6, j = 3, M = 1e4, N = async (e) => {
353
+ let { publicPath: n, workerUrl: r, length: i, read: a, onThumbnails: o, width: s = se } = e, c = await t({
379
354
  publicPath: n,
380
355
  workerUrl: r,
381
356
  workerOptions: { type: "module" },
@@ -383,14 +358,14 @@ var a = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
383
358
  read: a
384
359
  });
385
360
  try {
386
- let t = await c.init(), n = t.duration, r = Math.max(e.interval ?? ne, n / re), a = [];
361
+ let t = await c.init(), n = t.duration, r = Math.max(e.interval ?? ae, n / oe), a = [];
387
362
  for (let [e, o] of t.indexes.entries()) {
388
363
  let s = a.at(-1);
389
364
  s && o.timestamp - s.timestamp < r || a.push({
390
365
  timestamp: o.timestamp,
391
366
  endTime: n,
392
367
  startByte: o.pos,
393
- endByte: Math.min((t.indexes[e + 1]?.pos ?? i) + ae, i),
368
+ endByte: Math.min((t.indexes[e + 1]?.pos ?? i) + A, i),
394
369
  done: !1,
395
370
  attempts: 0
396
371
  });
@@ -421,7 +396,7 @@ var a = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
421
396
  }, p = (e) => {
422
397
  e.done = !0, d = d.then(async () => {
423
398
  if (u) return;
424
- let t = await Promise.race([c.readKeyframe(e.timestamp), new Promise((e, t) => setTimeout(() => t(/* @__PURE__ */ Error("timed out")), se))]), n = await createImageBitmap(new Blob([t], { type: "image/png" })), r = new OffscreenCanvas(s, Math.max(1, Math.round(n.height * (s / n.width))));
399
+ let t = await Promise.race([c.readKeyframe(e.timestamp), new Promise((e, t) => setTimeout(() => t(/* @__PURE__ */ Error("timed out")), M))]), n = await createImageBitmap(new Blob([t], { type: "image/png" })), r = new OffscreenCanvas(s, Math.max(1, Math.round(n.height * (s / n.width))));
425
400
  r.getContext("2d").drawImage(n, 0, 0, r.width, r.height), n.close();
426
401
  let i = await r.convertToBlob({
427
402
  type: "image/webp",
@@ -433,7 +408,7 @@ var a = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
433
408
  endTime: e.endTime
434
409
  }].sort((e, t) => e.startTime - t.startTime), f());
435
410
  }).catch(() => {
436
- e.attempts += 1, e.done = e.attempts >= oe;
411
+ e.attempts += 1, e.done = e.attempts >= j;
437
412
  });
438
413
  };
439
414
  return f(), {
@@ -443,14 +418,14 @@ var a = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
443
418
  destroy: () => {
444
419
  u = !0;
445
420
  for (let e of l) URL.revokeObjectURL(e.url);
446
- l = [], O(c);
421
+ l = [], k(c);
447
422
  }
448
423
  };
449
424
  } catch (e) {
450
- throw O(c), e;
425
+ throw k(c), e;
451
426
  }
452
- }, j = 1280, M = 1920, N = 250, P = 100, ce = () => typeof HTMLCanvasElement < "u" && "captureStream" in HTMLCanvasElement.prototype, le = () => typeof document < "u" && typeof HTMLVideoElement < "u" && typeof HTMLVideoElement.prototype.requestPictureInPicture == "function" && !!document.pictureInPictureEnabled, ue = () => typeof HTMLVideoElement < "u" && "mozCaptureStream" in HTMLVideoElement.prototype, F = () => ce() ? le() ? "window" : ue() ? "burn-in" : null : null, I = (e) => {
453
- let { video: t, canvas: n } = e, r = e.mode ?? F() ?? "window", i = e.maxWidth ?? (r === "burn-in" ? M : j), a, o, s, c = !1, l = !1, u = !1, d = !1, f, p = () => r === "burn-in" ? !!a : !!a && document.pictureInPictureElement === a.mirror, m = (e, r) => {
427
+ }, P = 1280, ce = 1920, le = 250, ue = 100, F = () => typeof HTMLCanvasElement < "u" && "captureStream" in HTMLCanvasElement.prototype, I = () => typeof document < "u" && typeof HTMLVideoElement < "u" && typeof HTMLVideoElement.prototype.requestPictureInPicture == "function" && !!document.pictureInPictureEnabled, L = () => typeof HTMLVideoElement < "u" && "mozCaptureStream" in HTMLVideoElement.prototype, R = () => F() ? I() ? "window" : L() ? "burn-in" : null : null, de = (e) => {
428
+ let { video: t, canvas: n } = e, r = e.mode ?? R() ?? "window", i = e.maxWidth ?? (r === "burn-in" ? ce : P), a, o, s, c = !1, l = !1, u = !1, d = !1, f, p = () => r === "burn-in" ? !!a : !!a && document.pictureInPictureElement === a.mirror, m = (e, r) => {
454
429
  r.drawImage(t, 0, 0, e.width, e.height), n.width > 0 && n.height > 0 && r.drawImage(n, 0, 0, e.width, e.height);
455
430
  }, h = () => {
456
431
  a && m(a.composite, a.context);
@@ -464,52 +439,52 @@ var a = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
464
439
  "mediaSession" in navigator && (navigator.mediaSession.playbackState = t.paused ? "paused" : "playing");
465
440
  }, x = () => {
466
441
  c || l || !t.paused || t.play().catch(() => {});
467
- }, ee = () => {
442
+ }, S = () => {
468
443
  c || l || (t.paused || t.pause(), r !== "burn-in" && (c = !0, a?.mirror.play().catch(() => {}).finally(() => {
469
444
  c = !1;
470
445
  })));
471
- }, S = () => {
472
- b(), a?.mirror.play().catch(() => {});
473
446
  }, C = () => {
474
- b(), r === "burn-in" && a?.mirror.pause();
447
+ b(), a?.mirror.play().catch(() => {});
475
448
  }, w = () => {
449
+ b(), r === "burn-in" && a?.mirror.pause();
450
+ }, T = () => {
476
451
  if (r !== "burn-in" || !a || !t.paused || l) return;
477
452
  l = !0;
478
453
  let { mirror: e } = a;
479
454
  h(), e.play().then(() => new Promise((e) => {
480
- setTimeout(e, P);
455
+ setTimeout(e, ue);
481
456
  })).catch(() => {}).finally(() => {
482
457
  e.pause(), setTimeout(() => {
483
458
  l = !1;
484
- }, P);
459
+ }, ue);
485
460
  });
486
- }, te = [["play", () => {
461
+ }, ee = [["play", () => {
487
462
  t.play().catch(() => {});
488
- }], ["pause", () => t.pause()]], T = (e) => {
489
- if ("mediaSession" in navigator) for (let [t, n] of te) try {
463
+ }], ["pause", () => t.pause()]], E = (e) => {
464
+ if ("mediaSession" in navigator) for (let [t, n] of ee) try {
490
465
  navigator.mediaSession.setActionHandler(t, e ? n : null);
491
466
  } catch {}
492
- }, E = () => {
493
- if (y(), T(!1), t.removeEventListener("play", S), t.removeEventListener("pause", C), t.removeEventListener("seeked", w), !a) {
467
+ }, D = () => {
468
+ if (y(), E(!1), t.removeEventListener("play", C), t.removeEventListener("pause", w), t.removeEventListener("seeked", T), !a) {
494
469
  f?.();
495
470
  return;
496
471
  }
497
472
  let { stream: e, mirror: n } = a;
498
- n.removeEventListener("play", x), n.removeEventListener("pause", ee), n.removeEventListener("leavepictureinpicture", D);
473
+ n.removeEventListener("play", x), n.removeEventListener("pause", S), n.removeEventListener("leavepictureinpicture", te);
499
474
  for (let t of e.getTracks()) t.stop();
500
475
  n.remove(), a = void 0, f?.();
501
- }, D = () => E(), O = (e) => {
476
+ }, te = () => D(), ne = (e) => {
502
477
  let r = t.style.opacity, i = n.style.display;
503
478
  e.style.cssText = "position:absolute;inset:0;width:100%;height:100%;object-fit:contain;background:#000;z-index:1", t.style.opacity = "0", n.style.display = "none", f = () => {
504
479
  f = void 0, t.style.opacity = r, n.style.display = i;
505
480
  };
506
- }, k = async () => {
481
+ }, re = async () => {
507
482
  let n = Math.min(t.videoWidth || i, i), o = Math.round(n * ((t.videoHeight || 9) / (t.videoWidth || 16))), c = document.createElement("canvas");
508
483
  c.width = n, c.height = o;
509
484
  let l = c.getContext("2d");
510
485
  if (!l) throw Error("the picture in picture composite has no 2d context");
511
486
  let u = document.createElement("video");
512
- u.muted = !0, u.playsInline = !0, r === "burn-in" ? O(u) : u.style.cssText = "position:fixed;width:1px;height:1px;opacity:0;pointer-events:none;left:-1px;top:-1px", m(c, l);
487
+ u.muted = !0, u.playsInline = !0, r === "burn-in" ? ne(u) : u.style.cssText = "position:fixed;width:1px;height:1px;opacity:0;pointer-events:none;left:-1px;top:-1px", m(c, l);
513
488
  let f = {
514
489
  composite: c,
515
490
  context: l,
@@ -518,7 +493,7 @@ var a = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
518
493
  };
519
494
  if (a = f, u.srcObject = f.stream, (e.container ?? t.parentElement ?? document.body).appendChild(u), v(), s = setInterval(() => {
520
495
  t.paused && h();
521
- }, N), u.play().catch(() => {}), u.readyState === 0 && await new Promise((e) => {
496
+ }, le), u.play().catch(() => {}), u.readyState === 0 && await new Promise((e) => {
522
497
  let t = () => {
523
498
  clearTimeout(n), e();
524
499
  }, n = setTimeout(t, 1e3);
@@ -527,7 +502,7 @@ var a = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
527
502
  u.remove();
528
503
  return;
529
504
  }
530
- if (u.addEventListener("play", x), u.addEventListener("pause", ee), u.addEventListener("leavepictureinpicture", D), t.addEventListener("play", S), t.addEventListener("pause", C), t.addEventListener("seeked", w), T(!0), b(), r === "burn-in") {
505
+ if (u.addEventListener("play", x), u.addEventListener("pause", S), u.addEventListener("leavepictureinpicture", te), t.addEventListener("play", C), t.addEventListener("pause", w), t.addEventListener("seeked", T), E(!0), b(), r === "burn-in") {
531
506
  t.paused && u.pause(), e.onBurnedInChange?.(!0);
532
507
  return;
533
508
  }
@@ -540,7 +515,7 @@ var a = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
540
515
  try {
541
516
  if (p()) {
542
517
  if (r === "burn-in") {
543
- E(), e.onBurnedInChange?.(!1);
518
+ D(), e.onBurnedInChange?.(!1);
544
519
  return;
545
520
  }
546
521
  await document.exitPictureInPicture().catch(() => {});
@@ -548,9 +523,9 @@ var a = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
548
523
  }
549
524
  r !== "burn-in" && document.pictureInPictureElement && await document.exitPictureInPicture().catch(() => {});
550
525
  try {
551
- await k();
526
+ await re();
552
527
  } catch (t) {
553
- console.warn("subtitle compositing for picture in picture failed", t), E(), r === "burn-in" && e.onBurnedInChange?.(!1);
528
+ console.warn("subtitle compositing for picture in picture failed", t), D(), r === "burn-in" && e.onBurnedInChange?.(!1);
554
529
  }
555
530
  } finally {
556
531
  u = !1;
@@ -559,9 +534,9 @@ var a = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
559
534
  },
560
535
  mode: r,
561
536
  destroy: () => {
562
- d = !0, r !== "burn-in" && p() && document.exitPictureInPicture().catch(() => {}), E(), r === "burn-in" && e.onBurnedInChange?.(!1);
537
+ d = !0, r !== "burn-in" && p() && document.exitPictureInPicture().catch(() => {}), D(), r === "burn-in" && e.onBurnedInChange?.(!1);
563
538
  }
564
539
  };
565
540
  };
566
541
  //#endregion
567
- export { h as a, O as c, a as d, s as f, D as i, c as l, F as n, g as o, A as r, k as s, I as t, m as u };
542
+ export { v as a, k as c, a as d, s as f, O as i, c as l, R as n, y as o, N as r, ie as s, de as t, _ as u };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { l as e, n as t, o as n, r, s as i, t as a, u as o } from "./engine-CDdjRm_U.js";
1
+ import { l as e, n as t, o as n, r, s as i, t as a, u as o } from "./engine-DPnChmYc.js";
2
2
  import { Children as s, Fragment as c, isValidElement as l, useCallback as u, useEffect as d, useMemo as f, useRef as p, useState as m } from "react";
3
3
  import { css as h, keyframes as g } from "@emotion/react";
4
4
  import { createPlayer as _, useContainerAttach as v, useMediaAttach as y } from "@videojs/react";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@banou/media-player",
3
- "version": "0.8.15",
3
+ "version": "0.8.16",
4
4
  "description": "A video player for containers and codecs the browser cannot play natively, remuxed on the fly",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,4 +1,3 @@
1
- import type { ParsedASS, ParsedASSStyles } from 'ass-compiler'
2
1
  import type { ASS_Event } from 'jassub'
3
2
  import type { Attachment, SubtitleFragment } from 'libav-wasm/build/worker'
4
3
 
@@ -10,8 +9,17 @@ export type SubtitleStream = { streamIndex: number, title: string, language: str
10
9
  /** -1 turns subtitles off. It frees the track and matches no header, so nothing is set. */
11
10
  export const SUBTITLES_OFF = -1
12
11
 
13
- type SubtitleHeaderPart = { type: 'header', streamIndex: number, content: string, eventsContent: string, parsed: ParsedASS }
14
- type SubtitleDialoguePart = { type: 'dialogue', streamIndex: number, index: number, assEvent: ASS_Event }
12
+ /**
13
+ * jassub's `ASS_Event` types `Style` as a string, which the library it wraps does not agree with:
14
+ * the field is written straight through to libass's `int Style`, an index into the track's style
15
+ * list. Corrected once, here, so a name cannot end up in it again.
16
+ */
17
+ type StyledEvent = Omit<ASS_Event, 'Style'> & { Style: number }
18
+
19
+ const createEvent = (jassub: JASSUB, event: StyledEvent) => jassub.createEvent(event as unknown as ASS_Event)
20
+
21
+ type SubtitleHeaderPart = { type: 'header', streamIndex: number, content: string, eventsContent: string, styles: Map<string, number> }
22
+ type SubtitleDialoguePart = { type: 'dialogue', streamIndex: number, index: number, assEvent: StyledEvent }
15
23
 
16
24
  export type SubtitleRendererOptions = {
17
25
  video: HTMLVideoElement
@@ -34,35 +42,36 @@ export type SubtitleRendererOptions = {
34
42
 
35
43
  const convertTimestamp = (ms: number) => new Date(ms).toISOString().slice(11, 22)
36
44
 
37
- const appendParsedStyle = (jassub: JASSUB, style: ParsedASSStyles['style'][number]) =>
38
- jassub.createStyle({
39
- ...style,
40
- treat_fontname_as_pattern: 0,
41
- Blur: 0,
42
- Justify: 0,
43
- FontName: style.Fontname,
44
- FontSize: Number(style.Fontsize),
45
- PrimaryColour: Number(style.PrimaryColour),
46
- SecondaryColour: Number(style.SecondaryColour),
47
- OutlineColour: Number(style.OutlineColour),
48
- BackColour: Number(style.BackColour),
49
- Bold: Number(style.Bold),
50
- Italic: Number(style.Italic),
51
- Underline: Number(style.Underline),
52
- StrikeOut: Number(style.StrikeOut),
53
- ScaleX: Number(style.ScaleX),
54
- ScaleY: Number(style.ScaleY),
55
- Spacing: Number(style.Spacing),
56
- Angle: Number(style.Angle),
57
- BorderStyle: Number(style.BorderStyle),
58
- Outline: Number(style.Outline),
59
- Shadow: Number(style.Shadow),
60
- Alignment: Number(style.Alignment),
61
- MarginL: Number(style.MarginL),
62
- MarginR: Number(style.MarginR),
63
- MarginV: Number(style.MarginV),
64
- Encoding: Number(style.Encoding),
65
- } as Parameters<JASSUB['createStyle']>[0])
45
+ /**
46
+ * How many styles libass keeps in front of the ones the file declares.
47
+ *
48
+ * `ass_new_track` always allocates its own "Default" at index 0 before parsing a line of the header,
49
+ * so the file's first style lands at 1. That number is what an event's `Style` field holds: libass
50
+ * reads it as an index into the track's style list and nothing else. `subtitle-scale.browser.test.tsx`
51
+ * is what pins this, by measuring the rendered text against what libass itself draws.
52
+ */
53
+ const LIBASS_OWN_STYLES = 1
54
+
55
+ const headerStyles = (content: string) =>
56
+ // a duplicated name resolves to the LAST one, the way libass's own lookup scans the list backwards
57
+ new Map(parse(content).styles.style.map((style, index) => [style.Name, index + LIBASS_OWN_STYLES]))
58
+
59
+ /**
60
+ * The index libass will resolve this event's style to.
61
+ *
62
+ * jassub types `ASS_Event.Style` as a string and writes it straight through to an `int`, so handing
63
+ * it a style NAME stores 0, which is libass's own default: Arial at size 18, with margins and a drop
64
+ * shadow of its own. Subtitles still appear, in a face and a size the file never asked for.
65
+ *
66
+ * Falling back to the header's own "Default", then to 0, is what libass does for a name it cannot
67
+ * find. The leading-`*` strip and the case fold on "Default" are its normalisation, kept so a
68
+ * `*Default` written by an older tool resolves here too.
69
+ */
70
+ const styleIndex = (header: SubtitleHeaderPart, name: string) => {
71
+ const stripped = name.replace(/^\*+/, '')
72
+ const key = stripped.toLowerCase() === 'default' ? 'Default' : stripped
73
+ return header.styles.get(key) ?? header.styles.get('Default') ?? 0
74
+ }
66
75
 
67
76
  // cleared so jassub scales the script to the canvas, not to the authored resolution
68
77
  const renderable = (content: string) => {
@@ -84,7 +93,7 @@ const toHeaderPart = (fragment: SubtitleFragment & { type: 'header' }): Subtitle
84
93
  console.warn(`subtitle stream ${fragment.streamIndex} has no Events format, ignoring the track`)
85
94
  return null
86
95
  }
87
- return { type: 'header', streamIndex: fragment.streamIndex, content: fragment.content, eventsContent, parsed: parse(fragment.content) }
96
+ return { type: 'header', streamIndex: fragment.streamIndex, content: fragment.content, eventsContent, styles: headerStyles(fragment.content) }
88
97
  }
89
98
 
90
99
  const toDialoguePart = (header: SubtitleHeaderPart, fragment: SubtitleFragment & { type: 'dialogue' }): SubtitleDialoguePart => {
@@ -102,6 +111,7 @@ const toDialoguePart = (header: SubtitleHeaderPart, fragment: SubtitleFragment &
102
111
  index: dialogueIndex,
103
112
  assEvent: {
104
113
  ...event,
114
+ Style: styleIndex(header, event.Style),
105
115
  Effect: event.Effect ?? '',
106
116
  Text: event.Text.raw,
107
117
  Duration: (event.End - event.Start) * 1000,
@@ -109,7 +119,7 @@ const toDialoguePart = (header: SubtitleHeaderPart, fragment: SubtitleFragment &
109
119
  End: event.End * 1000,
110
120
  ReadOrder: dialogueIndex,
111
121
  _index: dialogueIndex,
112
- } as ASS_Event,
122
+ } as StyledEvent,
113
123
  }
114
124
  }
115
125
 
@@ -149,7 +159,6 @@ export const createSubtitleRenderer = (options: SubtitleRendererOptions) => {
149
159
  // jassub 1.8.x binds setRate as the ratechange listener, so the Event becomes the rate
150
160
  video.removeEventListener('ratechange', (jassub as unknown as { _boundSetRate: EventListener })._boundSetRate)
151
161
  video.addEventListener('ratechange', onRateChange)
152
- for (const style of header.parsed.styles.style) appendParsedStyle(jassub, style)
153
162
  }
154
163
 
155
164
  const pushAttachments = (incoming: Attachment[]) => {
@@ -175,7 +184,7 @@ export const createSubtitleRenderer = (options: SubtitleRendererOptions) => {
175
184
  const part = toDialoguePart(header, fragment)
176
185
  if (byIndex.has(part.index)) continue
177
186
  byIndex.set(part.index, part)
178
- if (selected === fragment.streamIndex) jassub?.createEvent(part.assEvent)
187
+ if (selected === fragment.streamIndex && jassub) createEvent(jassub, part.assEvent)
179
188
  }
180
189
  }
181
190
  }
@@ -188,8 +197,7 @@ export const createSubtitleRenderer = (options: SubtitleRendererOptions) => {
188
197
  const header = headers.get(next)
189
198
  if (!header) return
190
199
  jassub.setTrack(renderable(header.content))
191
- for (const style of header.parsed.styles.style) appendParsedStyle(jassub, style)
192
- for (const part of dialogues.get(next)?.values() ?? []) jassub.createEvent(part.assEvent)
200
+ for (const part of dialogues.get(next)?.values() ?? []) createEvent(jassub, part.assEvent)
193
201
  jassub.setCurrentTime(video.paused, video.currentTime, video.playbackRate)
194
202
  }
195
203