@banou/media-player 0.8.7 → 0.8.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.
package/dist/index.js CHANGED
@@ -61,7 +61,7 @@ var re = {
61
61
  id: e.streamIndex,
62
62
  label: t
63
63
  })), oe = (t, n, r) => {
64
- let i = F(), { read: a, size: o, publicPath: s = "", libavWorkerUrl: c = "", jassubWorkerUrl: d = "", jassubWasmUrl: m = "", jassubLegacyWasmUrl: h, defaultFontUrl: g, bufferSize: _, autoplay: v = !1 } = r ?? {}, [y, b] = p(void 0), x = f(null), S = f(0), C = f(!1), w = f(a);
64
+ let i = F(), { read: a, size: o, publicPath: s = "", libavWorkerUrl: c = "", jassubWorkerUrl: d = "", jassubWasmUrl: m = "", jassubLegacyWasmUrl: h, defaultFontUrl: g, bufferSize: _, autoplay: v = !1 } = r ?? {}, [y, b] = p(void 0), x = f(null), S = f(null), C = f(!1), w = f(a);
65
65
  w.current = a;
66
66
  let T = f(r?.onSeek);
67
67
  T.current = r?.onSeek;
@@ -108,7 +108,10 @@ var re = {
108
108
  bufferSize: _,
109
109
  audioStreamIndex: y,
110
110
  onReady: () => {
111
- r || (i.setSourceState({ ready: !0 }), S.current > 0 && (t.currentTime = S.current, S.current = 0), v && t.play().catch(() => {}));
111
+ if (r) return;
112
+ i.setSourceState({ ready: !0 });
113
+ let e = S.current;
114
+ S.current = null, e && e.size === o && e.time > 0 && (t.currentTime = e.time), v && t.play().catch(() => {});
112
115
  },
113
116
  onError: l,
114
117
  onRecovered: () => {
@@ -139,7 +142,10 @@ var re = {
139
142
  l(e);
140
143
  }
141
144
  })(), () => {
142
- r = !0, S.current = t.currentTime, x.current?.destroy(), x.current = null, i.setSourceState({ ready: !1 });
145
+ r = !0, S.current = {
146
+ time: t.currentTime,
147
+ size: o
148
+ }, x.current?.destroy(), x.current = null, i.setSourceState({ ready: !1 });
143
149
  };
144
150
  }, [
145
151
  i,
@@ -1088,6 +1094,8 @@ ${Y}
1088
1094
  className: "settings",
1089
1095
  type: "button",
1090
1096
  onClick: c,
1097
+ "aria-label": "Settings",
1098
+ "aria-expanded": s,
1091
1099
  children: /* @__PURE__ */ S(j, {})
1092
1100
  }),
1093
1101
  toolTipText: /* @__PURE__ */ S("span", { children: "Settings" })
@@ -1170,7 +1178,9 @@ position: static;
1170
1178
  ${Y}
1171
1179
  `, Oe = () => {
1172
1180
  let e = F((e) => e.subtitleTracks), t = F((e) => e.selectedSubtitleTrack), n = F((e) => e.selectSubtitleTrack), r = F((e) => e.subtitleOffLabel), { open: i, toggle: a, containerRef: o, pending: s, failed: c, runSelect: l } = Z();
1173
- return e.length === 0 ? null : /* @__PURE__ */ C("div", {
1181
+ if (e.length === 0) return null;
1182
+ let u = t !== void 0;
1183
+ return /* @__PURE__ */ C("div", {
1174
1184
  css: De,
1175
1185
  ref: o,
1176
1186
  children: [/* @__PURE__ */ S(K, {
@@ -1180,7 +1190,10 @@ ${Y}
1180
1190
  className: "subtitles",
1181
1191
  type: "button",
1182
1192
  onClick: a,
1183
- children: t === void 0 ? /* @__PURE__ */ S(xe, { className: "captions-off" }) : /* @__PURE__ */ S(be, { className: "captions" })
1193
+ "aria-label": "Subtitles",
1194
+ "aria-expanded": i,
1195
+ "aria-pressed": u,
1196
+ children: u ? /* @__PURE__ */ S(be, { className: "captions" }) : /* @__PURE__ */ S(xe, { className: "captions-off" })
1184
1197
  }),
1185
1198
  toolTipText: /* @__PURE__ */ S("span", { children: "Subtitles" })
1186
1199
  }), i && /* @__PURE__ */ S(X, {
@@ -1356,6 +1369,8 @@ ${Y}
1356
1369
  type: "button",
1357
1370
  onClick: () => t.toggleMuted(),
1358
1371
  ref: e,
1372
+ "aria-label": "Mute",
1373
+ "aria-pressed": r || n === 0,
1359
1374
  children: S(r || n === 0 ? te : n <= .5 ? M : ee, {
1360
1375
  size: 18,
1361
1376
  color: "#fff"
@@ -1583,6 +1598,7 @@ ${Y}
1583
1598
  className: "play",
1584
1599
  type: "button",
1585
1600
  onClick: () => e.togglePaused(),
1601
+ "aria-label": _ ? "Replay" : t ? "Play" : "Pause",
1586
1602
  children: S(_ ? A : t ? k : O, {})
1587
1603
  }),
1588
1604
  toolTipText: /* @__PURE__ */ S("span", { children: _ ? "Replay (k)" : t ? "Play (k)" : "Pause (k)" })
@@ -1631,6 +1647,8 @@ ${Y}
1631
1647
  className: "full-screen",
1632
1648
  type: "button",
1633
1649
  onClick: () => e.toggleFullscreen(),
1650
+ "aria-label": "Full screen",
1651
+ "aria-pressed": i,
1634
1652
  children: S(i ? D : E, {})
1635
1653
  }),
1636
1654
  toolTipText: /* @__PURE__ */ S("span", { children: i ? "Exit full screen (f)" : "Full screen (f)" })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@banou/media-player",
3
- "version": "0.8.7",
3
+ "version": "0.8.8",
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",
@@ -276,6 +276,9 @@ export const ControlBar = () => {
276
276
  className='play'
277
277
  type='button'
278
278
  onClick={() => player.togglePaused()}
279
+ // This one genuinely changes what it does, so the name changes with it rather than
280
+ // carrying a pressed state. Replay is a third action, not an on or off.
281
+ aria-label={ended ? 'Replay' : paused ? 'Play' : 'Pause'}
279
282
  >
280
283
  {
281
284
  ended
@@ -363,6 +366,8 @@ export const ControlBar = () => {
363
366
  className='full-screen'
364
367
  type='button'
365
368
  onClick={() => player.toggleFullscreen()}
369
+ aria-label='Full screen'
370
+ aria-pressed={fullscreen}
366
371
  >
367
372
  {
368
373
  fullscreen
@@ -112,6 +112,8 @@ export const SettingsAction = () => {
112
112
  className='settings'
113
113
  type='button'
114
114
  onClick={toggle}
115
+ aria-label='Settings'
116
+ aria-expanded={open}
115
117
  >
116
118
  <Settings />
117
119
  </button>
@@ -105,6 +105,10 @@ export const Sound = ({ ref }: SoundProps) => {
105
105
  type='button'
106
106
  onClick={() => player.toggleMuted()}
107
107
  ref={ref}
108
+ // A static name with the state on `aria-pressed`. Renaming a control when it is activated
109
+ // announces the state twice and breaks the expectation that a control keeps its name.
110
+ aria-label='Mute'
111
+ aria-pressed={muted || volume === 0}
108
112
  >
109
113
  {muted || volume === 0
110
114
  ? <VolumeX size={18} color='#fff' />
@@ -54,7 +54,14 @@ export const SubtitlesAction = () => {
54
54
  id='subtitles'
55
55
  disabled={open}
56
56
  text={
57
- <button className='subtitles' type='button' onClick={toggle}>
57
+ <button
58
+ className='subtitles'
59
+ type='button'
60
+ onClick={toggle}
61
+ aria-label='Subtitles'
62
+ aria-expanded={open}
63
+ aria-pressed={on}
64
+ >
58
65
  {on ? <Captions className='captions' /> : <CaptionsOff className='captions-off' />}
59
66
  </button>
60
67
  }
@@ -30,7 +30,18 @@ export const usePlayback = (
30
30
  const [audioStreamIndex, setAudioStreamIndex] = useState<number | undefined>(undefined)
31
31
 
32
32
  const controllerRef = useRef<PlaybackController | null>(null)
33
- const resumeTimeRef = useRef(0)
33
+ /**
34
+ * Where to pick playback back up, and WHICH media that position belongs to.
35
+ *
36
+ * The position exists so a rebuild of the pipeline is invisible: switching audio track tears the
37
+ * whole thing down and starts again, and dropping the viewer back at zero for that would be absurd.
38
+ * But the same rebuild happens when the media itself changes, and then the position is meaningless:
39
+ * dropping a second file in while the first was playing used to open it 40 minutes in.
40
+ *
41
+ * `size` is the identity, because it is the one piece of the source in the effect's deps. `read` is
42
+ * deliberately not, since a streaming consumer passes a fresh closure several times a second.
43
+ */
44
+ const resumeRef = useRef<{ time: number, size: number } | null>(null)
34
45
  // The renderer turns the first track on by itself, so the menu has to mirror that or it shows
35
46
  // "Disable" ticked over subtitles that are visibly on screen.
36
47
  const subtitleChoiceMade = useRef(false)
@@ -94,9 +105,11 @@ export const usePlayback = (
94
105
  onReady: () => {
95
106
  if (cancelled) return
96
107
  player.setSourceState({ ready: true })
97
- if (resumeTimeRef.current > 0) {
98
- video.currentTime = resumeTimeRef.current
99
- resumeTimeRef.current = 0
108
+ const resume = resumeRef.current
109
+ resumeRef.current = null
110
+ // only a position belonging to THIS media, or a new file opens where the last one stopped
111
+ if (resume && resume.size === size && resume.time > 0) {
112
+ video.currentTime = resume.time
100
113
  }
101
114
  if (autoplay) video.play().catch(() => {})
102
115
  },
@@ -130,7 +143,7 @@ export const usePlayback = (
130
143
  })()
131
144
  return () => {
132
145
  cancelled = true
133
- resumeTimeRef.current = video.currentTime
146
+ resumeRef.current = { time: video.currentTime, size }
134
147
  controllerRef.current?.destroy()
135
148
  controllerRef.current = null
136
149
  player.setSourceState({ ready: false })