@joewinke/jatui 0.50.1 → 0.51.0
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.
|
@@ -267,6 +267,7 @@
|
|
|
267
267
|
SETTINGS_WRITE_DELAY_MS,
|
|
268
268
|
VIDEO_SETTINGS,
|
|
269
269
|
REMEMBERED_MUTED,
|
|
270
|
+
REMEMBERED_SPEAKER_SPEEDS,
|
|
270
271
|
REMEMBERED_TRANSCRIPT,
|
|
271
272
|
REMEMBERED_VOLUME,
|
|
272
273
|
type SettingAvailability,
|
|
@@ -1566,6 +1567,53 @@
|
|
|
1566
1567
|
resumeFollow();
|
|
1567
1568
|
}
|
|
1568
1569
|
|
|
1570
|
+
// ── Per-speaker manual speed (E73, jatui-p3m3u) ──────────────────
|
|
1571
|
+
// Extension parity (library.js:4605): the chip menu's "this speaker at
|
|
1572
|
+
// 1.5×". Remembered per viewer through the remembered-settings split (the
|
|
1573
|
+
// host's settingsStore, else this browser's localStorage) — a choice made
|
|
1574
|
+
// before the load-time read resolves wins, like every remembered state.
|
|
1575
|
+
const localSpeedStore = localSettingsStore();
|
|
1576
|
+
let speedsTouched = false;
|
|
1577
|
+
let viewerSpeakerSpeeds = $state<Record<string, number>>({});
|
|
1578
|
+
|
|
1579
|
+
/** Set (or, with rate 1/null, clear) one speaker's manual speed and save it. */
|
|
1580
|
+
function setSpeakerSpeed(key: string, rate: number | null) {
|
|
1581
|
+
const next = { ...viewerSpeakerSpeeds };
|
|
1582
|
+
const r = rate == null || rate === 1 ? null : Math.min(16, Math.max(0.0625, rate));
|
|
1583
|
+
if (r != null) next[key] = r;
|
|
1584
|
+
else delete next[key];
|
|
1585
|
+
viewerSpeakerSpeeds = next;
|
|
1586
|
+
speedsTouched = true;
|
|
1587
|
+
writeRemembered(
|
|
1588
|
+
settingsStore ?? localSpeedStore,
|
|
1589
|
+
REMEMBERED_SPEAKER_SPEEDS.id,
|
|
1590
|
+
Object.keys(next).length > 0 ? { ...next } : {}
|
|
1591
|
+
);
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
function speakerSpeedOf(key: string): number {
|
|
1595
|
+
return viewerSpeakerSpeeds[key] ?? 1;
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
// The load-time read, once: a junk store reads as nothing stored, and a
|
|
1599
|
+
// choice made before the read resolves wins (the settings session's rule).
|
|
1600
|
+
$effect(() => {
|
|
1601
|
+
const store = settingsStore ?? localSpeedStore;
|
|
1602
|
+
let cancelled = false;
|
|
1603
|
+
untrack(() => {
|
|
1604
|
+
void Promise.resolve(store.get(REMEMBERED_SPEAKER_SPEEDS.id))
|
|
1605
|
+
.then((raw) => {
|
|
1606
|
+
if (cancelled || speedsTouched) return;
|
|
1607
|
+
const v = REMEMBERED_SPEAKER_SPEEDS.coerce(raw);
|
|
1608
|
+
if (v !== undefined) viewerSpeakerSpeeds = v;
|
|
1609
|
+
})
|
|
1610
|
+
.catch(() => {});
|
|
1611
|
+
});
|
|
1612
|
+
return () => {
|
|
1613
|
+
cancelled = true;
|
|
1614
|
+
};
|
|
1615
|
+
});
|
|
1616
|
+
|
|
1569
1617
|
// ── Speed + rate composer (video/rate/*, mos-5lzns.32) ──────────────────
|
|
1570
1618
|
// rate = base × speaker × silence. `base` is the only rate a human chose
|
|
1571
1619
|
// and the only one the OSD reports; the advisors multiply into the APPLIED
|
|
@@ -1574,10 +1622,27 @@
|
|
|
1574
1622
|
// exactly `base` — the contract in video/rate/compose.ts.
|
|
1575
1623
|
const rateTurns = $derived(toRateTurns(speakers));
|
|
1576
1624
|
const rateSegments = $derived(toRateSegments(transcript));
|
|
1625
|
+
// E73 (jatui-p3m3u) — the viewer's hand-set per-speaker speeds (the chip
|
|
1626
|
+
// menu, remembered per viewer). They multiply with the automatic advisor
|
|
1627
|
+
// and the base, whatever either is doing; an empty map is the identity.
|
|
1628
|
+
// The chip/legend keys a speaker by IDENTITY (speakerKeyOf), the rate
|
|
1629
|
+
// composer keys by NAME (speakerNameOf) — the index bridges the two.
|
|
1630
|
+
const viewerSpeedByName = $derived.by(() => {
|
|
1631
|
+
const keys = Object.keys(viewerSpeakerSpeeds);
|
|
1632
|
+
if (keys.length === 0) return null;
|
|
1633
|
+
const out: Record<string, number> = {};
|
|
1634
|
+
for (const key of keys) {
|
|
1635
|
+
const info = speakerIndex.get(key);
|
|
1636
|
+
const name = info && info.name !== 'Unknown speaker' ? info.name : key;
|
|
1637
|
+
out[name] = viewerSpeakerSpeeds[key];
|
|
1638
|
+
}
|
|
1639
|
+
return out;
|
|
1640
|
+
});
|
|
1577
1641
|
const rateAdvisors = $derived({
|
|
1578
1642
|
speakerRates: speakerPace
|
|
1579
1643
|
? speakerRateMap({ turns: rateTurns, segments: rateSegments, manual: speakerRates, measure: true })
|
|
1580
1644
|
: null,
|
|
1645
|
+
manual: viewerSpeedByName,
|
|
1581
1646
|
turns: rateTurns,
|
|
1582
1647
|
silence: smartSpeed ? silenceMapFor(waveform, effectiveDuration) : null
|
|
1583
1648
|
});
|
|
@@ -3679,6 +3744,31 @@
|
|
|
3679
3744
|
return t.speakerIdentity ?? t.speakerName ?? UNKNOWN_KEY;
|
|
3680
3745
|
}
|
|
3681
3746
|
|
|
3747
|
+
// ── Chip speed menu (E73, jatui-p3m3u) ──────────────────────────────────
|
|
3748
|
+
// One menu open at a time; a click anywhere else closes it.
|
|
3749
|
+
let speedMenuKey = $state<string | null>(null);
|
|
3750
|
+
const SPEED_CHOICES = [0.75, 1, 1.25, 1.5, 1.75, 2, 3];
|
|
3751
|
+
|
|
3752
|
+
function toggleSpeedMenu(key: string) {
|
|
3753
|
+
speedMenuKey = speedMenuKey === key ? null : key;
|
|
3754
|
+
}
|
|
3755
|
+
function pickSpeakerSpeed(key: string, name: string, rate: number) {
|
|
3756
|
+
setSpeakerSpeed(key, rate);
|
|
3757
|
+
speedMenuKey = null;
|
|
3758
|
+
flashOsd(rate === 1 ? `${name} · 1×` : `${name} · ${formatSpeedLabel(rate)}`);
|
|
3759
|
+
}
|
|
3760
|
+
$effect(() => {
|
|
3761
|
+
if (speedMenuKey == null) return;
|
|
3762
|
+
const close = (e: PointerEvent) => {
|
|
3763
|
+
// A press inside the open menu (or on its trigger) is the menu's own
|
|
3764
|
+
// click — the buttons close it themselves.
|
|
3765
|
+
if ((e.target as Element | null)?.closest?.('.vp-speed-menu')) return;
|
|
3766
|
+
speedMenuKey = null;
|
|
3767
|
+
};
|
|
3768
|
+
document.addEventListener('pointerdown', close);
|
|
3769
|
+
return () => document.removeEventListener('pointerdown', close);
|
|
3770
|
+
});
|
|
3771
|
+
|
|
3682
3772
|
function submitBookmark() {
|
|
3683
3773
|
onAddBookmark?.(currentTime, bookmarkLabel.trim());
|
|
3684
3774
|
bookmarkLabel = '';
|
|
@@ -4201,6 +4291,7 @@
|
|
|
4201
4291
|
{@const key = talkTimeKey(tt)}
|
|
4202
4292
|
{@const name = tt.speakerName ?? 'Unknown speaker'}
|
|
4203
4293
|
<span class="vp-legend-item">
|
|
4294
|
+
<span class="vp-speed-menu">
|
|
4204
4295
|
<button
|
|
4205
4296
|
type="button"
|
|
4206
4297
|
class="vp-chip"
|
|
@@ -4219,6 +4310,37 @@
|
|
|
4219
4310
|
{name}
|
|
4220
4311
|
<span class="vp-chip-time">{formatDuration(tt.seconds)}</span>
|
|
4221
4312
|
</button>
|
|
4313
|
+
<!-- E73: this speaker's own speed, remembered. 1× is the
|
|
4314
|
+
reset; the composer clamps the product. -->
|
|
4315
|
+
<button
|
|
4316
|
+
type="button"
|
|
4317
|
+
class="vp-chip-speed"
|
|
4318
|
+
class:on={speakerSpeedOf(key) !== 1}
|
|
4319
|
+
onclick={() => toggleSpeedMenu(key)}
|
|
4320
|
+
aria-expanded={speedMenuKey === key}
|
|
4321
|
+
aria-haspopup="menu"
|
|
4322
|
+
aria-label="Speed for {name}: {formatSpeedLabel(speakerSpeedOf(key))}"
|
|
4323
|
+
title="Speed for {name}"
|
|
4324
|
+
>
|
|
4325
|
+
{formatSpeedLabel(speakerSpeedOf(key))}
|
|
4326
|
+
</button>
|
|
4327
|
+
{#if speedMenuKey === key}
|
|
4328
|
+
<div class="vp-speed-pop" role="menu" aria-label="Speed for {name}">
|
|
4329
|
+
{#each SPEED_CHOICES as choice (choice)}
|
|
4330
|
+
<button
|
|
4331
|
+
type="button"
|
|
4332
|
+
role="menuitemradio"
|
|
4333
|
+
aria-checked={speakerSpeedOf(key) === choice}
|
|
4334
|
+
class="vp-speed-opt"
|
|
4335
|
+
class:current={speakerSpeedOf(key) === choice}
|
|
4336
|
+
onclick={() => pickSpeakerSpeed(key, name, choice)}
|
|
4337
|
+
>
|
|
4338
|
+
{formatSpeedLabel(choice)}{choice === 1 ? ' (reset)' : ''}
|
|
4339
|
+
</button>
|
|
4340
|
+
{/each}
|
|
4341
|
+
</div>
|
|
4342
|
+
{/if}
|
|
4343
|
+
</span>
|
|
4222
4344
|
<button
|
|
4223
4345
|
type="button"
|
|
4224
4346
|
class="vp-mute"
|
|
@@ -5499,6 +5621,66 @@
|
|
|
5499
5621
|
border-color: var(--color-primary);
|
|
5500
5622
|
}
|
|
5501
5623
|
|
|
5624
|
+
/* E73 — per-speaker speed trigger + its menu. The wrapper is the
|
|
5625
|
+
click-outside boundary: a press inside stays the menu's own click. */
|
|
5626
|
+
.vp-speed-menu {
|
|
5627
|
+
position: relative;
|
|
5628
|
+
display: inline-flex;
|
|
5629
|
+
align-items: center;
|
|
5630
|
+
}
|
|
5631
|
+
|
|
5632
|
+
.vp-chip-speed {
|
|
5633
|
+
padding: var(--jatui-space-xs, 2px) var(--jatui-space-xs, 2px);
|
|
5634
|
+
background: none;
|
|
5635
|
+
border: none;
|
|
5636
|
+
border-radius: var(--jatui-radius-selector, 0.375rem);
|
|
5637
|
+
color: color-mix(in oklch, var(--color-base-content) 60%, transparent);
|
|
5638
|
+
font-size: inherit;
|
|
5639
|
+
font-variant-numeric: tabular-nums;
|
|
5640
|
+
cursor: pointer;
|
|
5641
|
+
}
|
|
5642
|
+
|
|
5643
|
+
.vp-chip-speed.on {
|
|
5644
|
+
color: var(--color-primary);
|
|
5645
|
+
font-weight: 600;
|
|
5646
|
+
}
|
|
5647
|
+
|
|
5648
|
+
.vp-speed-pop {
|
|
5649
|
+
position: absolute;
|
|
5650
|
+
z-index: 30;
|
|
5651
|
+
top: calc(100% + 4px);
|
|
5652
|
+
left: 0;
|
|
5653
|
+
display: flex;
|
|
5654
|
+
flex-direction: column;
|
|
5655
|
+
min-width: 7rem;
|
|
5656
|
+
padding: var(--jatui-space-sm, 4px);
|
|
5657
|
+
background: var(--color-base-100);
|
|
5658
|
+
border: var(--jatui-border-w, 1px) solid color-mix(in oklch, var(--color-base-content) 15%, transparent);
|
|
5659
|
+
border-radius: var(--jatui-radius-selector, 0.375rem);
|
|
5660
|
+
box-shadow: 0 4px 12px rgb(0 0 0 / 0.25);
|
|
5661
|
+
}
|
|
5662
|
+
|
|
5663
|
+
.vp-speed-opt {
|
|
5664
|
+
padding: var(--jatui-space-xs, 2px) var(--jatui-space-md, 6px);
|
|
5665
|
+
background: none;
|
|
5666
|
+
border: none;
|
|
5667
|
+
border-radius: var(--jatui-radius-selector, 0.375rem);
|
|
5668
|
+
color: var(--color-base-content);
|
|
5669
|
+
font-size: inherit;
|
|
5670
|
+
font-variant-numeric: tabular-nums;
|
|
5671
|
+
text-align: left;
|
|
5672
|
+
cursor: pointer;
|
|
5673
|
+
}
|
|
5674
|
+
|
|
5675
|
+
.vp-speed-opt:hover {
|
|
5676
|
+
background: color-mix(in oklch, var(--color-base-content) 8%, transparent);
|
|
5677
|
+
}
|
|
5678
|
+
|
|
5679
|
+
.vp-speed-opt.current {
|
|
5680
|
+
color: var(--color-primary);
|
|
5681
|
+
font-weight: 600;
|
|
5682
|
+
}
|
|
5683
|
+
|
|
5502
5684
|
.vp-chip-clear {
|
|
5503
5685
|
background: none;
|
|
5504
5686
|
color: var(--color-primary);
|
package/dist/video/README.md
CHANGED
|
@@ -18,7 +18,7 @@ props + callback surfaces.
|
|
|
18
18
|
| `band.ts` | .28 (done) — extended by **.30** | The timeline band: zoom window (`BandWindow`, `zoomAt`, `followPlayhead`), hit tests (`segmentAt`, `chapterAt`, `biasToSegmentStart`, `nearestBookmark`), and `paintBand` (canvas). .30 adds one-axis arbitration + the light ribbon; its state lives HERE. |
|
|
19
19
|
| `band/` | **.30** (done) | `window.ts` — the zoom-window math (moved out of band.ts, re-exported there; shared by every band layer without an import cycle). `axis.ts` — one-axis four-state arbitration (`resolveAxis`, `BandState`, the churn-guarded microtask `createBandReporter`), `playheadX` (the one "where you are" mapping — ≤0.2px agreement) and `overviewBracket`. `luma.ts` — the light track (`VideoLumaTrack`, the sidecar verbatim) → ribbon (`v² × 0.55`, max-per-interval). `markers.ts` — `VideoMarker` comments (clustered dots) + like heat. |
|
|
20
20
|
| `keys.ts` | .28 (done) | The keyboard map — `PLAYER_KEYS` (the `?` overlay renders it), `chapterJump`, `FRAME_STEP_SEC`. Every key that exists is in the map; a key not in the map doesn't exist. |
|
|
21
|
-
| `rate.ts` + `rate/` | .28 contract — **.32** (done) | `rate.ts` is the barrel. `compose.ts` — the contract (`composeRate`, clamp [0.0625, 16], `shouldWriteRate`'s 0.001 deadband with an exact return to base), the `SPEED_STEPS` staircase, `formatSpeedLabel`. `speaker.ts` — props → `advisors/speaker-rate`; manual rates keyed by NAME always beat measured. `silence.ts` — waveform → `advisors/smart-speed` map + boost + the OSD saving claim. `ads.ts` — `adSpans` ∪ `ad: true` turns; skip only when PLAYBACK runs into an ad, never when a seek lands in one. `tick.ts` — the component's own 120ms loop's one-tick decision. None of them write `playbackRate`; they advise. |
|
|
21
|
+
| `rate.ts` + `rate/` | .28 contract — **.32** (done) | `rate.ts` is the barrel. `compose.ts` — the contract (`composeRate`, clamp [0.0625, 16], `shouldWriteRate`'s 0.001 deadband with an exact return to base), the `SPEED_STEPS` staircase, `formatSpeedLabel`. `speaker.ts` — props → `advisors/speaker-rate`; manual rates keyed by NAME always beat measured. `silence.ts` — waveform → `advisors/smart-speed` map + boost + the OSD saving claim. `ads.ts` — `adSpans` ∪ `ad: true` turns; skip only when PLAYBACK runs into an ad, never when a seek lands in one. `tick.ts` — the component's own 120ms loop's one-tick decision. `manual` (E73, jatui-p3m3u) — the viewer's remembered per-speaker speeds from the chip menu, multiplying with the automatic advisor; the composer clamps the product. None of them write `playbackRate`; they advise. |
|
|
22
22
|
| `deepLink.ts` | .28 (done) | `?t=` parse/format/read/write (replaceState, throttled, other params preserved) + `buildTimeUrl` (jatui-3wl5x) — the `?t=` URL builder hosts pass as `buildTimeLink` (MOS's "copy link at this time" shape). |
|
|
23
23
|
| `lineMenu.ts` | **jatui-3wl5x** | The transcript line right-click menu's pure pieces: `isContextMenuKey` (the keyboard's right-click — Context-menu key or Shift+F10) and `lineTextWithTime` (`[1:23] text`, the row's own clock). The component owns the DOM — which row opened it, cursor vs row anchoring, focus return — and the menu itself is the shared `ContextMenu`. |
|
|
24
24
|
| `transcriptSearch.ts` | **jatui-xmtnv** (E25/E57) | The transcript search + wheel line-scrub pure helpers: `searchHits` (case-insensitive, non-overlapping), `highlightRowHtml` (`<mark>`s around matches, everything else escaped — host transcript text is never markup), `pickMatch` (Enter walks matches from the playhead, wrapping), `lineScrubTarget` + `transcriptWheelDecision`/`effectiveWheelDelta` (Shift+wheel steps a line per ~100px notch; a plain wheel is never claimed). The component owns the box, focus, Esc layering and the `n / m` count. |
|
|
@@ -17,6 +17,13 @@ export declare const RATE_TICK_MS = 120;
|
|
|
17
17
|
export interface RateAdvisors {
|
|
18
18
|
/** null ⇒ the speaker advisor is off (collapses to 1). */
|
|
19
19
|
speakerRates: RateMap | null;
|
|
20
|
+
/**
|
|
21
|
+
* Viewer hand-set per-speaker speeds (the chip menu, remembered). They
|
|
22
|
+
* MULTIPLY with the automatic advisor — "this speaker at 1.5×" is the
|
|
23
|
+
* viewer's intent and stands on top of whatever the pace advisor measured.
|
|
24
|
+
* null/empty ⇒ 1 (the identity; see compose.ts).
|
|
25
|
+
*/
|
|
26
|
+
manual?: RateMap | null;
|
|
20
27
|
turns: RateTurn[];
|
|
21
28
|
/** null ⇒ smart speed is off or has no map yet (collapses to 1). */
|
|
22
29
|
silence: SilenceMap | null;
|
package/dist/video/rate/tick.js
CHANGED
|
@@ -5,11 +5,14 @@ import { speakerFactor } from './speaker';
|
|
|
5
5
|
export const RATE_TICK_MS = 120;
|
|
6
6
|
/** The composed rate at playhead `t` for the human's `base`. */
|
|
7
7
|
export function adviseRate(base, t, a) {
|
|
8
|
-
const speaker = speakerFactor(t, a.turns, a.speakerRates);
|
|
8
|
+
const speaker = speakerFactor(t, a.turns, a.speakerRates) * speakerFactor(t, a.turns, a.manual ?? null);
|
|
9
9
|
const silence = silenceFactor(t, a.silence, a.boost ?? null);
|
|
10
10
|
return { speaker, silence, rate: composeRate(base, speaker, silence).rate };
|
|
11
11
|
}
|
|
12
12
|
/** Is any advisor able to move the rate? False ⇒ the loop need not run. */
|
|
13
13
|
export function advisorsActive(a) {
|
|
14
|
-
|
|
14
|
+
const manual = a.manual;
|
|
15
|
+
return (!!a.silence ||
|
|
16
|
+
(!!manual && a.turns.length > 0 && Object.keys(manual).length > 0) ||
|
|
17
|
+
(!!a.speakerRates && a.turns.length > 0 && Object.keys(a.speakerRates).length > 0));
|
|
15
18
|
}
|
|
@@ -35,5 +35,15 @@ export declare const REMEMBERED_VOLUME: RememberedSpec<number>;
|
|
|
35
35
|
*/
|
|
36
36
|
export type TranscriptLayout = 'side' | 'below' | 'hidden';
|
|
37
37
|
export declare const REMEMBERED_TRANSCRIPT: RememberedSpec<TranscriptLayout>;
|
|
38
|
+
/**
|
|
39
|
+
* Remembered per-speaker playback speed (E73, jatui-p3m3u) — the chip menu's
|
|
40
|
+
* "this speaker at 1.5×" survives a reload. A record keyed by speaker NAME
|
|
41
|
+
* (the display name in the legend), coerced entry-by-entry: junk names and
|
|
42
|
+
* rates outside the composer's clamp range drop out, so a hostile store reads
|
|
43
|
+
* as nothing stored rather than a speed of 10⁹. Composed multiplicatively
|
|
44
|
+
* with the user's speed and the automatic pace advisor by the rate composer
|
|
45
|
+
* (rate/tick.ts), which clamps the product.
|
|
46
|
+
*/
|
|
47
|
+
export declare const REMEMBERED_SPEAKER_SPEEDS: RememberedSpec<Record<string, number>>;
|
|
38
48
|
/** Whether playback starts muted. */
|
|
39
49
|
export declare const REMEMBERED_MUTED: RememberedSpec<boolean>;
|
|
@@ -39,6 +39,40 @@ export const REMEMBERED_TRANSCRIPT = {
|
|
|
39
39
|
return undefined;
|
|
40
40
|
}
|
|
41
41
|
};
|
|
42
|
+
/**
|
|
43
|
+
* Remembered per-speaker playback speed (E73, jatui-p3m3u) — the chip menu's
|
|
44
|
+
* "this speaker at 1.5×" survives a reload. A record keyed by speaker NAME
|
|
45
|
+
* (the display name in the legend), coerced entry-by-entry: junk names and
|
|
46
|
+
* rates outside the composer's clamp range drop out, so a hostile store reads
|
|
47
|
+
* as nothing stored rather than a speed of 10⁹. Composed multiplicatively
|
|
48
|
+
* with the user's speed and the automatic pace advisor by the rate composer
|
|
49
|
+
* (rate/tick.ts), which clamps the product.
|
|
50
|
+
*/
|
|
51
|
+
export const REMEMBERED_SPEAKER_SPEEDS = {
|
|
52
|
+
id: 'player.speakerSpeeds',
|
|
53
|
+
default: {},
|
|
54
|
+
coerce(raw) {
|
|
55
|
+
if (typeof raw !== 'object' || raw === null || Array.isArray(raw))
|
|
56
|
+
return undefined;
|
|
57
|
+
const src = raw;
|
|
58
|
+
const out = {};
|
|
59
|
+
let any = false;
|
|
60
|
+
for (const [k, v] of Object.entries(src)) {
|
|
61
|
+
const name = k.trim();
|
|
62
|
+
try {
|
|
63
|
+
const n = typeof v === 'number' ? v : typeof v === 'string' && v.trim() !== '' ? Number(v) : NaN;
|
|
64
|
+
if (name && Number.isFinite(n) && n >= 0.0625 && n <= 16) {
|
|
65
|
+
out[name] = n;
|
|
66
|
+
any = true;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
/* a throwing valueOf is a value not stored */
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return any ? out : undefined;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
42
76
|
/** Whether playback starts muted. */
|
|
43
77
|
export const REMEMBERED_MUTED = {
|
|
44
78
|
id: 'player.muted',
|