@banou/media-player 0.8.8 → 0.8.9

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.
@@ -1,2 +1,2 @@
1
- import { a as e, c as t, i as n, l as r, n as i, o as a, r as o, s, t as c, u as l } from "../engine-B6flRjLd.js";
1
+ import { a as e, c as t, i as n, l as r, n as i, o as a, r as o, s, t as c, u as l } from "../engine-BwK7ycv0.js";
2
2
  export { n as DEFAULT_BUFFER_SIZE, s as SUBTITLES_OFF, c as createPictureInPicture, t as createSubtitleRenderer, o as createThumbnailGenerator, r as getTimeRanges, i as pictureInPictureMode, e as startPlayback, a as terminateRemuxer, l as updateSourceBuffer };
@@ -201,7 +201,12 @@ var a = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
201
201
  let c = [e.info.output.videoMimeType, e.info.output.audioMimeType].filter(Boolean).join(","), l = `video/mp4; codecs="${c}"`;
202
202
  if (!MediaSource.isTypeSupported(l)) throw Error(`This browser cannot play the codecs in this file: ${c}`);
203
203
  let C = new MediaSource(), w = URL.createObjectURL(C);
204
- N.push(() => URL.revokeObjectURL(w)), n.src = w;
204
+ N.push(() => URL.revokeObjectURL(w)), n.src = w, N.push(() => {
205
+ try {
206
+ n.pause();
207
+ } catch {}
208
+ n.removeAttribute("src"), n.load();
209
+ });
205
210
  let T = await new Promise((t, n) => {
206
211
  let r = setTimeout(() => {
207
212
  i(), n(/* @__PURE__ */ Error("The MediaSource never opened"));
@@ -248,8 +253,8 @@ var a = (e) => Array(e.buffered.length).fill(void 0).map((t, n) => ({
248
253
  V = !0, G = null, K = 0, te = e;
249
254
  let t = ++W;
250
255
  try {
251
- let { data: n, pts: r, subtitles: i } = await M.seek(e);
252
- if (P || t !== W || (i?.length && o.pushFragments(i), await R(r), P || t !== W)) return;
256
+ let { data: n, pts: r, subtitles: i, finished: a } = await M.seek(e);
257
+ if (P || t !== W || (a && (H = !0), i?.length && o.pushFragments(i), await R(r), P || t !== W)) return;
253
258
  n.byteLength && (await I(n), Q());
254
259
  } catch (e) {
255
260
  Y(e, P || t !== W);
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as e, c as t, n, r, s as i, t as a } from "./engine-B6flRjLd.js";
1
+ import { a as e, c as t, n, r, s as i, t as a } from "./engine-BwK7ycv0.js";
2
2
  import { Children as o, Fragment as s, isValidElement as c, useCallback as l, useEffect as u, useMemo as d, useRef as f, useState as p } from "react";
3
3
  import { css as m, keyframes as h } from "@emotion/react";
4
4
  import { createPlayer as g, useContainerAttach as _, useMediaAttach as v } from "@videojs/react";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@banou/media-player",
3
- "version": "0.8.8",
3
+ "version": "0.8.9",
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",
@@ -130,6 +130,28 @@ export const startPlayback = async (options: PlaybackOptions): Promise<PlaybackC
130
130
  const mediaSourceUrl = URL.createObjectURL(mediaSource)
131
131
  teardown.push(() => URL.revokeObjectURL(mediaSourceUrl))
132
132
  videoElement.src = mediaSourceUrl
133
+ /**
134
+ * Give the element back, before the url it is holding is revoked.
135
+ *
136
+ * Revoking alone detaches nothing: `src` still names the dead MediaSource, the old SourceBuffer
137
+ * is still attached to it, and the decoder keeps whatever state it was in. That matters because
138
+ * this pipeline is REBUILT in place, on the same element, whenever the audio track changes, and
139
+ * the rebuild awaits a wasm load plus a header read before it assigns a new `src`. On a torrent
140
+ * that is seconds of the element sitting on a corpse.
141
+ *
142
+ * `load()` is the only thing that actually resets it: the media element load algorithm aborts
143
+ * the current resource and clears the decoder. Without it the element goes from an ended source
144
+ * straight into fresh appends with no seek in between, which is exactly the sequence that makes
145
+ * firefox hand a real packet to a drained decoder (`avcodec_send_packet error: End of file`).
146
+ * Pushed AFTER the revoke so the reversed teardown runs it FIRST.
147
+ */
148
+ teardown.push(() => {
149
+ try { videoElement.pause() } catch {}
150
+ videoElement.removeAttribute('src')
151
+ // no src and no <source> children means the load algorithm goes straight to NETWORK_EMPTY
152
+ // without firing `error`, so this cannot manufacture the failure it exists to prevent
153
+ videoElement.load()
154
+ })
133
155
 
134
156
  const sourceBuffer = await new Promise<SourceBuffer>((resolve, reject) => {
135
157
  const timeout = setTimeout(() => { cleanup(); reject(new Error('The MediaSource never opened')) }, SOURCE_OPEN_TIMEOUT)
@@ -230,8 +252,13 @@ export const startPlayback = async (options: PlaybackOptions): Promise<PlaybackC
230
252
  lastSeekPosition = time
231
253
  const generation = ++seekGeneration
232
254
  try {
233
- const { data, pts, subtitles: fragments } = await remuxer.seek(time)
255
+ // `seek` ends by reading, and that read can hit the end of the file, which finalizes the
256
+ // muxer: libav writes the mp4 trailer and cannot be read from again. Dropping the flag left
257
+ // this side believing the stream was still open, so the next pump read straight back into a
258
+ // context that had already been torn down.
259
+ const { data, pts, subtitles: fragments, finished: done } = await remuxer.seek(time)
234
260
  if (destroyed || generation !== seekGeneration) return
261
+ if (done) finished = true
235
262
  if (fragments?.length) subtitles.pushFragments(fragments)
236
263
  await updateTimestampOffset(pts)
237
264
  if (destroyed || generation !== seekGeneration) return