@gentomiyano/optimized-web-audio-player 0.2.0 → 0.2.1
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 +12 -0
- package/dist/react/index.d.ts +10 -2
- package/dist/react/index.js +530 -2
- package/dist/react/index.js.map +1 -1
- package/dist/ui/index.d.ts +14 -6
- package/dist/ui/index.js +31 -109
- package/dist/ui/index.js.map +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -39,6 +39,18 @@ The public npm registry, scoped package name, and release workflow are fixed.
|
|
|
39
39
|
Initial npm publication still requires the scope owner's authentication.
|
|
40
40
|
Release and consumer pinning rules are documented in `docs/RELEASING.md`.
|
|
41
41
|
|
|
42
|
+
## WP-04.3 React package boundary
|
|
43
|
+
|
|
44
|
+
Version 0.2.1 keeps React Context consumers in the `/react` entrypoint and
|
|
45
|
+
exports props-only views from `/ui`. Import `PlayerProvider`, `usePlayer`,
|
|
46
|
+
`useBoundPlayer`, and `BoundCompactPlayer` from `/react`; import
|
|
47
|
+
`BoundCompactPlayerView`, `MiniPlayer`, and the other pure views from `/ui`.
|
|
48
|
+
|
|
49
|
+
The release check packs the built package, installs that tarball into an empty
|
|
50
|
+
consumer, renders a bound inline Player with a shared Mini Player, and verifies
|
|
51
|
+
one Store, one active item, and one audio element. See
|
|
52
|
+
`docs/WP-04.3-REACT-BOUNDARY.md`.
|
|
53
|
+
|
|
42
54
|
## WP-04.2 playback contexts and bound inline players
|
|
43
55
|
|
|
44
56
|
Version 0.2.0 adds consumer-neutral `manual-history` and `ordered-queue`
|
package/dist/react/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
|
-
import { j as PlayerCommands, x as PlayerMediaSyncReason, Q as QueueItem, p as PlayerExperienceConfig, z as PlayerStore, r as PlayerExperienceConfigStore, y as PlayerState, c as PlaybackContextInput, a as BoundPlayerViewState, B as BoundPlayerCommands } from '../player-BxNFzRq4.js';
|
|
3
|
+
import { j as PlayerCommands, x as PlayerMediaSyncReason, Q as QueueItem, p as PlayerExperienceConfig, z as PlayerStore, r as PlayerExperienceConfigStore, y as PlayerState, c as PlaybackContextInput, a as BoundPlayerViewState, B as BoundPlayerCommands, h as PlayerAppearance } from '../player-BxNFzRq4.js';
|
|
4
4
|
import { c as PlayerMediaEventType, a as MediaElementStateSnapshot } from '../media-element-DyMO-PhK.js';
|
|
5
5
|
|
|
6
6
|
type PlayerAudioMode = "auto" | "web-audio" | "html-media";
|
|
@@ -94,4 +94,12 @@ interface UseBoundPlayerResult {
|
|
|
94
94
|
declare function deriveBoundPlayerViewState(playerState: Readonly<PlayerState>, contextId: string, queueItem: QueueItem): Readonly<BoundPlayerViewState>;
|
|
95
95
|
declare function useBoundPlayer({ context, queueItem, }: UseBoundPlayerOptions): UseBoundPlayerResult;
|
|
96
96
|
|
|
97
|
-
|
|
97
|
+
interface BoundCompactPlayerProps {
|
|
98
|
+
readonly appearance: PlayerAppearance;
|
|
99
|
+
readonly context: PlaybackContextInput;
|
|
100
|
+
readonly queueItem: QueueItem;
|
|
101
|
+
readonly className?: string;
|
|
102
|
+
}
|
|
103
|
+
declare function BoundCompactPlayer({ appearance, className, context, queueItem, }: BoundCompactPlayerProps): react.JSX.Element;
|
|
104
|
+
|
|
105
|
+
export { BoundCompactPlayer, type BoundCompactPlayerProps, MiniPlayerHost, type MiniPlayerHostProps, PlayerProvider, type PlayerProviderProps, type PlayerRuntimeContextValue, type PlayerRuntimeDiagnosticEvent, type PlayerRuntimeDiagnosticListener, type PlayerRuntimeDiagnosticPayload, type UseBoundPlayerOptions, type UseBoundPlayerResult, type UsePlayerResult, deriveBoundPlayerViewState, useBoundPlayer, usePlayer };
|
package/dist/react/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createContext, useRef, useState, useEffect, useSyncExternalStore, useMemo, useContext } from 'react';
|
|
1
|
+
import { createContext, useRef, useState, useEffect, useSyncExternalStore, useMemo, useContext, useId } from 'react';
|
|
2
2
|
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
3
3
|
|
|
4
4
|
// src/react/player-provider.tsx
|
|
@@ -2568,7 +2568,535 @@ function useBoundPlayer({
|
|
|
2568
2568
|
}, [context, player.commands, player.state, queueItem]);
|
|
2569
2569
|
return { state, commands };
|
|
2570
2570
|
}
|
|
2571
|
+
function IconFrame({
|
|
2572
|
+
children,
|
|
2573
|
+
size = 20,
|
|
2574
|
+
...props
|
|
2575
|
+
}) {
|
|
2576
|
+
return /* @__PURE__ */ jsx(
|
|
2577
|
+
"svg",
|
|
2578
|
+
{
|
|
2579
|
+
"aria-hidden": "true",
|
|
2580
|
+
focusable: "false",
|
|
2581
|
+
viewBox: "0 0 24 24",
|
|
2582
|
+
width: size,
|
|
2583
|
+
height: size,
|
|
2584
|
+
fill: "currentColor",
|
|
2585
|
+
...props,
|
|
2586
|
+
children
|
|
2587
|
+
}
|
|
2588
|
+
);
|
|
2589
|
+
}
|
|
2590
|
+
function PlayIcon(props) {
|
|
2591
|
+
return /* @__PURE__ */ jsx(IconFrame, { ...props, children: /* @__PURE__ */ jsx("path", { d: "M7 4.8c0-1.05 1.16-1.68 2.04-1.1l11 7.2a1.3 1.3 0 0 1 0 2.2l-11 7.2A1.31 1.31 0 0 1 7 19.2V4.8Z" }) });
|
|
2592
|
+
}
|
|
2593
|
+
function PauseIcon(props) {
|
|
2594
|
+
return /* @__PURE__ */ jsxs(IconFrame, { ...props, children: [
|
|
2595
|
+
/* @__PURE__ */ jsx("rect", { x: "5", y: "4", width: "5", height: "16", rx: "0.8" }),
|
|
2596
|
+
/* @__PURE__ */ jsx("rect", { x: "14", y: "4", width: "5", height: "16", rx: "0.8" })
|
|
2597
|
+
] });
|
|
2598
|
+
}
|
|
2599
|
+
|
|
2600
|
+
// src/ui/player-utils.ts
|
|
2601
|
+
function clamp(value, min, max) {
|
|
2602
|
+
return Math.min(Math.max(value, min), max);
|
|
2603
|
+
}
|
|
2604
|
+
function formatTime(seconds) {
|
|
2605
|
+
if (seconds === null || !Number.isFinite(seconds) || seconds < 0) {
|
|
2606
|
+
return "--:--";
|
|
2607
|
+
}
|
|
2608
|
+
const wholeSeconds = Math.floor(seconds);
|
|
2609
|
+
const hours = Math.floor(wholeSeconds / 3600);
|
|
2610
|
+
const minutes = Math.floor(wholeSeconds % 3600 / 60);
|
|
2611
|
+
const remainder = wholeSeconds % 60;
|
|
2612
|
+
if (hours > 0) {
|
|
2613
|
+
return `${String(hours)}:${String(minutes).padStart(2, "0")}:${String(remainder).padStart(2, "0")}`;
|
|
2614
|
+
}
|
|
2615
|
+
return `${String(minutes).padStart(2, "0")}:${String(remainder).padStart(2, "0")}`;
|
|
2616
|
+
}
|
|
2617
|
+
function formatTimeAria(seconds) {
|
|
2618
|
+
const safeSeconds = Math.max(0, Math.floor(seconds));
|
|
2619
|
+
const hours = Math.floor(safeSeconds / 3600);
|
|
2620
|
+
const minutes = Math.floor(safeSeconds % 3600 / 60);
|
|
2621
|
+
const remainder = safeSeconds % 60;
|
|
2622
|
+
const parts = [
|
|
2623
|
+
hours > 0 ? `${String(hours)} hour${hours === 1 ? "" : "s"}` : "",
|
|
2624
|
+
minutes > 0 ? `${String(minutes)} minute${minutes === 1 ? "" : "s"}` : "",
|
|
2625
|
+
`${String(remainder)} second${remainder === 1 ? "" : "s"}`
|
|
2626
|
+
].filter(Boolean);
|
|
2627
|
+
return parts.join(" ");
|
|
2628
|
+
}
|
|
2629
|
+
function PlayerSurface({
|
|
2630
|
+
appearance,
|
|
2631
|
+
children,
|
|
2632
|
+
className,
|
|
2633
|
+
label
|
|
2634
|
+
}) {
|
|
2635
|
+
return /* @__PURE__ */ jsx(
|
|
2636
|
+
"section",
|
|
2637
|
+
{
|
|
2638
|
+
"aria-label": label,
|
|
2639
|
+
className: ["player-surface", className].filter(Boolean).join(" "),
|
|
2640
|
+
"data-appearance": appearance,
|
|
2641
|
+
children
|
|
2642
|
+
}
|
|
2643
|
+
);
|
|
2644
|
+
}
|
|
2645
|
+
function Artwork({ className, item }) {
|
|
2646
|
+
const classes = ["player-artwork", className].filter(Boolean).join(" ");
|
|
2647
|
+
if (item?.artwork !== void 0) {
|
|
2648
|
+
return /* @__PURE__ */ jsx("span", { className: `${classes} player-artwork--image`, children: /* @__PURE__ */ jsx(
|
|
2649
|
+
"img",
|
|
2650
|
+
{
|
|
2651
|
+
alt: item.artwork.alt,
|
|
2652
|
+
className: "player-artwork__image",
|
|
2653
|
+
decoding: "async",
|
|
2654
|
+
src: item.artwork.url
|
|
2655
|
+
}
|
|
2656
|
+
) });
|
|
2657
|
+
}
|
|
2658
|
+
return /* @__PURE__ */ jsx(
|
|
2659
|
+
"div",
|
|
2660
|
+
{
|
|
2661
|
+
"aria-label": "Artwork unavailable",
|
|
2662
|
+
className: `${classes} player-artwork--fallback`,
|
|
2663
|
+
role: "img",
|
|
2664
|
+
children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true" })
|
|
2665
|
+
}
|
|
2666
|
+
);
|
|
2667
|
+
}
|
|
2668
|
+
function ScrollingTitle({
|
|
2669
|
+
enabled,
|
|
2670
|
+
title
|
|
2671
|
+
}) {
|
|
2672
|
+
const containsJapanese = /[\u3040-\u30ff\u3400-\u9fff]/u.test(title);
|
|
2673
|
+
const viewportRef = useRef(null);
|
|
2674
|
+
const contentRef = useRef(null);
|
|
2675
|
+
const [marquee, setMarquee] = useState({
|
|
2676
|
+
distance: 0,
|
|
2677
|
+
duration: 10,
|
|
2678
|
+
overflow: false
|
|
2679
|
+
});
|
|
2680
|
+
useEffect(() => {
|
|
2681
|
+
const viewport = viewportRef.current;
|
|
2682
|
+
const content = contentRef.current;
|
|
2683
|
+
if (viewport === null || content === null) {
|
|
2684
|
+
return;
|
|
2685
|
+
}
|
|
2686
|
+
const viewportElement = viewport;
|
|
2687
|
+
const contentElement = content;
|
|
2688
|
+
function measure() {
|
|
2689
|
+
const viewportWidth = Math.max(
|
|
2690
|
+
viewportElement.getBoundingClientRect().width,
|
|
2691
|
+
viewportElement.clientWidth
|
|
2692
|
+
);
|
|
2693
|
+
const contentWidth = Math.max(
|
|
2694
|
+
contentElement.getBoundingClientRect().width,
|
|
2695
|
+
contentElement.scrollWidth
|
|
2696
|
+
);
|
|
2697
|
+
const overflow = contentWidth - viewportWidth > 1;
|
|
2698
|
+
const distance = enabled ? contentWidth + 12 : 0;
|
|
2699
|
+
setMarquee({
|
|
2700
|
+
distance,
|
|
2701
|
+
duration: Math.max(10, distance / 22 + 4),
|
|
2702
|
+
overflow
|
|
2703
|
+
});
|
|
2704
|
+
}
|
|
2705
|
+
measure();
|
|
2706
|
+
if (typeof ResizeObserver === "undefined") {
|
|
2707
|
+
window.addEventListener("resize", measure);
|
|
2708
|
+
return () => {
|
|
2709
|
+
window.removeEventListener("resize", measure);
|
|
2710
|
+
};
|
|
2711
|
+
}
|
|
2712
|
+
const observer = new ResizeObserver(measure);
|
|
2713
|
+
observer.observe(viewportElement);
|
|
2714
|
+
observer.observe(contentElement);
|
|
2715
|
+
return () => {
|
|
2716
|
+
observer.disconnect();
|
|
2717
|
+
};
|
|
2718
|
+
}, [enabled, title]);
|
|
2719
|
+
const style = {
|
|
2720
|
+
"--player-title-scroll-offset": `${String(-marquee.distance)}px`,
|
|
2721
|
+
"--player-title-scroll-duration": `${String(marquee.duration)}s`
|
|
2722
|
+
};
|
|
2723
|
+
return /* @__PURE__ */ jsx(
|
|
2724
|
+
"strong",
|
|
2725
|
+
{
|
|
2726
|
+
className: "player-title",
|
|
2727
|
+
"data-overflow": marquee.overflow ? "true" : "false",
|
|
2728
|
+
"data-scroll": enabled ? "true" : "false",
|
|
2729
|
+
"data-script": containsJapanese ? "japanese" : "latin",
|
|
2730
|
+
ref: viewportRef,
|
|
2731
|
+
style,
|
|
2732
|
+
children: /* @__PURE__ */ jsx("span", { className: "player-title__text", ref: contentRef, children: title })
|
|
2733
|
+
}
|
|
2734
|
+
);
|
|
2735
|
+
}
|
|
2736
|
+
function TrackMetadata({
|
|
2737
|
+
className,
|
|
2738
|
+
item,
|
|
2739
|
+
scrollTitle = false,
|
|
2740
|
+
status
|
|
2741
|
+
}) {
|
|
2742
|
+
return /* @__PURE__ */ jsxs(
|
|
2743
|
+
"div",
|
|
2744
|
+
{
|
|
2745
|
+
className: ["player-metadata", className].filter(Boolean).join(" "),
|
|
2746
|
+
children: [
|
|
2747
|
+
status === void 0 ? null : /* @__PURE__ */ jsx("span", { className: "player-kicker", children: status }),
|
|
2748
|
+
/* @__PURE__ */ jsx(
|
|
2749
|
+
ScrollingTitle,
|
|
2750
|
+
{
|
|
2751
|
+
enabled: item !== null && scrollTitle,
|
|
2752
|
+
title: item?.title ?? "No media selected"
|
|
2753
|
+
},
|
|
2754
|
+
item?.title ?? "No media selected"
|
|
2755
|
+
),
|
|
2756
|
+
/* @__PURE__ */ jsx("span", { className: "player-creator", children: item === null ? "Select media to begin" : item.creatorName ?? "Artist unavailable" }),
|
|
2757
|
+
item?.collectionTitle === void 0 ? null : /* @__PURE__ */ jsx("span", { className: "player-collection", children: item.collectionTitle })
|
|
2758
|
+
]
|
|
2759
|
+
}
|
|
2760
|
+
);
|
|
2761
|
+
}
|
|
2762
|
+
function TimeReadout({
|
|
2763
|
+
className,
|
|
2764
|
+
currentTimeSec,
|
|
2765
|
+
durationSec
|
|
2766
|
+
}) {
|
|
2767
|
+
return /* @__PURE__ */ jsxs(
|
|
2768
|
+
"div",
|
|
2769
|
+
{
|
|
2770
|
+
"aria-label": `${formatTime(currentTimeSec)} of ${formatTime(durationSec)}`,
|
|
2771
|
+
className: ["player-time", className].filter(Boolean).join(" "),
|
|
2772
|
+
children: [
|
|
2773
|
+
/* @__PURE__ */ jsx("time", { children: formatTime(currentTimeSec) }),
|
|
2774
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "/" }),
|
|
2775
|
+
/* @__PURE__ */ jsx("time", { children: formatTime(durationSec) })
|
|
2776
|
+
]
|
|
2777
|
+
}
|
|
2778
|
+
);
|
|
2779
|
+
}
|
|
2780
|
+
function IconButton({
|
|
2781
|
+
children,
|
|
2782
|
+
className,
|
|
2783
|
+
disabled = false,
|
|
2784
|
+
label,
|
|
2785
|
+
onClick,
|
|
2786
|
+
pressed
|
|
2787
|
+
}) {
|
|
2788
|
+
return /* @__PURE__ */ jsx(
|
|
2789
|
+
"button",
|
|
2790
|
+
{
|
|
2791
|
+
"aria-label": label,
|
|
2792
|
+
...pressed === void 0 ? {} : { "aria-pressed": pressed },
|
|
2793
|
+
className: ["player-icon-button", className].filter(Boolean).join(" "),
|
|
2794
|
+
disabled,
|
|
2795
|
+
onClick,
|
|
2796
|
+
title: label,
|
|
2797
|
+
type: "button",
|
|
2798
|
+
children
|
|
2799
|
+
}
|
|
2800
|
+
);
|
|
2801
|
+
}
|
|
2802
|
+
function SeekBar({
|
|
2803
|
+
canSeek,
|
|
2804
|
+
className,
|
|
2805
|
+
commitMode,
|
|
2806
|
+
currentTimeSec,
|
|
2807
|
+
durationSec,
|
|
2808
|
+
label = "Seek",
|
|
2809
|
+
onSeek,
|
|
2810
|
+
status
|
|
2811
|
+
}) {
|
|
2812
|
+
const duration = durationSec !== null && Number.isFinite(durationSec) && durationSec > 0 ? durationSec : 0;
|
|
2813
|
+
const disabled = !canSeek || duration === 0 || status === "loading";
|
|
2814
|
+
const safeCurrentTime = clamp(currentTimeSec, 0, duration);
|
|
2815
|
+
const [previewTime, setPreviewTime] = useState(safeCurrentTime);
|
|
2816
|
+
const previewProgress = duration === 0 ? 0 : clamp(previewTime / duration, 0, 1) * 100;
|
|
2817
|
+
const isAdjusting = useRef(false);
|
|
2818
|
+
const lastCommitted = useRef(null);
|
|
2819
|
+
useEffect(() => {
|
|
2820
|
+
if (!isAdjusting.current) {
|
|
2821
|
+
setPreviewTime(safeCurrentTime);
|
|
2822
|
+
}
|
|
2823
|
+
}, [safeCurrentTime]);
|
|
2824
|
+
function updatePreview(value) {
|
|
2825
|
+
const nextValue = clamp(value, 0, duration);
|
|
2826
|
+
isAdjusting.current = true;
|
|
2827
|
+
setPreviewTime(nextValue);
|
|
2828
|
+
if (commitMode === "live") {
|
|
2829
|
+
onSeek(nextValue);
|
|
2830
|
+
lastCommitted.current = nextValue;
|
|
2831
|
+
}
|
|
2832
|
+
}
|
|
2833
|
+
function commitPreview(value = previewTime) {
|
|
2834
|
+
const nextValue = clamp(value, 0, duration);
|
|
2835
|
+
if (disabled || !isAdjusting.current || commitMode === "live" && lastCommitted.current === nextValue) {
|
|
2836
|
+
isAdjusting.current = false;
|
|
2837
|
+
return;
|
|
2838
|
+
}
|
|
2839
|
+
onSeek(nextValue);
|
|
2840
|
+
lastCommitted.current = nextValue;
|
|
2841
|
+
isAdjusting.current = false;
|
|
2842
|
+
}
|
|
2843
|
+
function cancelPreview() {
|
|
2844
|
+
isAdjusting.current = false;
|
|
2845
|
+
lastCommitted.current = null;
|
|
2846
|
+
setPreviewTime(safeCurrentTime);
|
|
2847
|
+
}
|
|
2848
|
+
return /* @__PURE__ */ jsxs(
|
|
2849
|
+
"label",
|
|
2850
|
+
{
|
|
2851
|
+
className: ["player-seek", className].filter(Boolean).join(" "),
|
|
2852
|
+
"data-disabled": disabled ? "true" : "false",
|
|
2853
|
+
children: [
|
|
2854
|
+
/* @__PURE__ */ jsx("span", { className: "player-visually-hidden", children: label }),
|
|
2855
|
+
/* @__PURE__ */ jsx(
|
|
2856
|
+
"span",
|
|
2857
|
+
{
|
|
2858
|
+
"aria-hidden": "true",
|
|
2859
|
+
className: "player-seek__progress",
|
|
2860
|
+
style: { width: `${String(disabled ? 0 : previewProgress)}%` }
|
|
2861
|
+
}
|
|
2862
|
+
),
|
|
2863
|
+
/* @__PURE__ */ jsx(
|
|
2864
|
+
"input",
|
|
2865
|
+
{
|
|
2866
|
+
"aria-label": label,
|
|
2867
|
+
"aria-valuetext": formatTimeAria(previewTime),
|
|
2868
|
+
disabled,
|
|
2869
|
+
max: duration || 1,
|
|
2870
|
+
min: 0,
|
|
2871
|
+
onBlur: (event) => {
|
|
2872
|
+
commitPreview(Number(event.currentTarget.value));
|
|
2873
|
+
},
|
|
2874
|
+
onChange: (event) => {
|
|
2875
|
+
updatePreview(Number(event.currentTarget.value));
|
|
2876
|
+
},
|
|
2877
|
+
onKeyUp: (event) => {
|
|
2878
|
+
commitPreview(Number(event.currentTarget.value));
|
|
2879
|
+
},
|
|
2880
|
+
onPointerDown: () => {
|
|
2881
|
+
isAdjusting.current = true;
|
|
2882
|
+
},
|
|
2883
|
+
onPointerCancel: cancelPreview,
|
|
2884
|
+
onPointerUp: (event) => {
|
|
2885
|
+
commitPreview(Number(event.currentTarget.value));
|
|
2886
|
+
},
|
|
2887
|
+
step: 0.1,
|
|
2888
|
+
type: "range",
|
|
2889
|
+
value: disabled ? 0 : previewTime
|
|
2890
|
+
}
|
|
2891
|
+
)
|
|
2892
|
+
]
|
|
2893
|
+
}
|
|
2894
|
+
);
|
|
2895
|
+
}
|
|
2896
|
+
var FALLBACK_PEAKS = [
|
|
2897
|
+
0.16,
|
|
2898
|
+
0.24,
|
|
2899
|
+
0.42,
|
|
2900
|
+
0.3,
|
|
2901
|
+
0.58,
|
|
2902
|
+
0.76,
|
|
2903
|
+
0.48,
|
|
2904
|
+
0.34,
|
|
2905
|
+
0.66,
|
|
2906
|
+
0.88,
|
|
2907
|
+
0.62,
|
|
2908
|
+
0.4,
|
|
2909
|
+
0.52,
|
|
2910
|
+
0.72,
|
|
2911
|
+
0.44,
|
|
2912
|
+
0.28,
|
|
2913
|
+
0.56,
|
|
2914
|
+
0.82,
|
|
2915
|
+
0.68,
|
|
2916
|
+
0.38,
|
|
2917
|
+
0.24,
|
|
2918
|
+
0.46,
|
|
2919
|
+
0.32,
|
|
2920
|
+
0.18
|
|
2921
|
+
];
|
|
2922
|
+
var BAR_COUNT = 512;
|
|
2923
|
+
var VIEWBOX_WIDTH = BAR_COUNT;
|
|
2924
|
+
function createWaveformBars(peaks) {
|
|
2925
|
+
const source = peaks.length > 1 ? peaks : FALLBACK_PEAKS;
|
|
2926
|
+
return Array.from({ length: BAR_COUNT }, (_, index) => {
|
|
2927
|
+
const sourcePosition = index / Math.max(BAR_COUNT - 1, 1) * (source.length - 1);
|
|
2928
|
+
const leftIndex = Math.floor(sourcePosition);
|
|
2929
|
+
const rightIndex = Math.min(leftIndex + 1, source.length - 1);
|
|
2930
|
+
const mix = sourcePosition - leftIndex;
|
|
2931
|
+
const leftPeak = Math.abs(source[leftIndex] ?? 0);
|
|
2932
|
+
const rightPeak = Math.abs(source[rightIndex] ?? leftPeak);
|
|
2933
|
+
const interpolated = leftPeak + (rightPeak - leftPeak) * mix;
|
|
2934
|
+
if (source.length >= BAR_COUNT) {
|
|
2935
|
+
return clamp(Math.pow(interpolated, 1.18), 0.04, 1);
|
|
2936
|
+
}
|
|
2937
|
+
const noiseSeed = Math.sin((index + 1) * 12.9898 + source.length * 78.233) * 43758.5453;
|
|
2938
|
+
const noise = noiseSeed - Math.floor(noiseSeed);
|
|
2939
|
+
const fineDetail = 0.76 + noise * 0.34;
|
|
2940
|
+
const transient = noise > 0.975 ? 3.2 : noise > 0.91 ? 1.75 : 1;
|
|
2941
|
+
return clamp(
|
|
2942
|
+
Math.pow(interpolated, 1.22) * fineDetail * transient,
|
|
2943
|
+
0.04,
|
|
2944
|
+
1
|
|
2945
|
+
);
|
|
2946
|
+
});
|
|
2947
|
+
}
|
|
2948
|
+
function Waveform({
|
|
2949
|
+
className,
|
|
2950
|
+
opacity = 1,
|
|
2951
|
+
playedStateEnabled = true,
|
|
2952
|
+
progress = 0,
|
|
2953
|
+
waveform
|
|
2954
|
+
}) {
|
|
2955
|
+
const clipId = useId().replaceAll(":", "");
|
|
2956
|
+
const bars = createWaveformBars(waveform?.peaks ?? FALLBACK_PEAKS);
|
|
2957
|
+
const progressWidth = clamp(progress, 0, 1) * VIEWBOX_WIDTH;
|
|
2958
|
+
const classes = ["player-waveform", className].filter(Boolean).join(" ");
|
|
2959
|
+
const barPath = bars.map((peak, index) => {
|
|
2960
|
+
const halfHeight = peak * 47;
|
|
2961
|
+
const x = index + 0.5;
|
|
2962
|
+
return `M ${String(x)} ${String(50 - halfHeight)} V ${String(
|
|
2963
|
+
50 + halfHeight
|
|
2964
|
+
)}`;
|
|
2965
|
+
}).join(" ");
|
|
2966
|
+
return /* @__PURE__ */ jsxs(
|
|
2967
|
+
"svg",
|
|
2968
|
+
{
|
|
2969
|
+
"aria-hidden": "true",
|
|
2970
|
+
className: classes,
|
|
2971
|
+
focusable: "false",
|
|
2972
|
+
preserveAspectRatio: "none",
|
|
2973
|
+
style: { opacity: clamp(opacity, 0, 1), pointerEvents: "none" },
|
|
2974
|
+
viewBox: `0 0 ${String(VIEWBOX_WIDTH)} 100`,
|
|
2975
|
+
children: [
|
|
2976
|
+
/* @__PURE__ */ jsx("path", { className: "player-waveform__base", d: barPath }),
|
|
2977
|
+
playedStateEnabled ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2978
|
+
/* @__PURE__ */ jsx("clipPath", { id: clipId, children: /* @__PURE__ */ jsx("rect", { width: progressWidth, height: "100" }) }),
|
|
2979
|
+
/* @__PURE__ */ jsx(
|
|
2980
|
+
"path",
|
|
2981
|
+
{
|
|
2982
|
+
className: "player-waveform__played",
|
|
2983
|
+
clipPath: `url(#${clipId})`,
|
|
2984
|
+
d: barPath
|
|
2985
|
+
}
|
|
2986
|
+
)
|
|
2987
|
+
] }) : null
|
|
2988
|
+
]
|
|
2989
|
+
}
|
|
2990
|
+
);
|
|
2991
|
+
}
|
|
2992
|
+
function BoundCompactPlayerView({
|
|
2993
|
+
appearance,
|
|
2994
|
+
className,
|
|
2995
|
+
commands,
|
|
2996
|
+
contextId,
|
|
2997
|
+
contextMode,
|
|
2998
|
+
options,
|
|
2999
|
+
state
|
|
3000
|
+
}) {
|
|
3001
|
+
const progress = state.durationSec === null || state.durationSec <= 0 ? 0 : state.currentTimeSec / state.durationSec;
|
|
3002
|
+
return /* @__PURE__ */ jsx(
|
|
3003
|
+
PlayerSurface,
|
|
3004
|
+
{
|
|
3005
|
+
appearance,
|
|
3006
|
+
className: ["player-bound-compact", className].filter(Boolean).join(" "),
|
|
3007
|
+
label: `Inline player: ${state.item.title}`,
|
|
3008
|
+
children: /* @__PURE__ */ jsxs(
|
|
3009
|
+
"div",
|
|
3010
|
+
{
|
|
3011
|
+
className: "player-bound-compact__main",
|
|
3012
|
+
"data-active": state.isActive ? "true" : "false",
|
|
3013
|
+
"data-playback-context-id": contextId,
|
|
3014
|
+
"data-playback-context-mode": contextMode,
|
|
3015
|
+
"data-queue-item-id": state.queueItemId,
|
|
3016
|
+
children: [
|
|
3017
|
+
/* @__PURE__ */ jsx(
|
|
3018
|
+
IconButton,
|
|
3019
|
+
{
|
|
3020
|
+
className: "player-play-button",
|
|
3021
|
+
label: state.isPlaying ? "Pause" : "Play",
|
|
3022
|
+
onClick: () => {
|
|
3023
|
+
void commands.togglePlayback();
|
|
3024
|
+
},
|
|
3025
|
+
children: state.isPlaying ? /* @__PURE__ */ jsx(PauseIcon, { size: 22 }) : /* @__PURE__ */ jsx(PlayIcon, { size: 22 })
|
|
3026
|
+
}
|
|
3027
|
+
),
|
|
3028
|
+
/* @__PURE__ */ jsx(
|
|
3029
|
+
Artwork,
|
|
3030
|
+
{
|
|
3031
|
+
className: "player-bound-compact__artwork",
|
|
3032
|
+
item: state.item
|
|
3033
|
+
}
|
|
3034
|
+
),
|
|
3035
|
+
/* @__PURE__ */ jsx(TrackMetadata, { item: state.item, scrollTitle: state.isPlaying }),
|
|
3036
|
+
/* @__PURE__ */ jsxs("div", { className: "player-bound-compact__signal", children: [
|
|
3037
|
+
/* @__PURE__ */ jsx(
|
|
3038
|
+
Waveform,
|
|
3039
|
+
{
|
|
3040
|
+
opacity: options.waveformOpacity,
|
|
3041
|
+
playedStateEnabled: options.waveformPlayedStateEnabled,
|
|
3042
|
+
progress,
|
|
3043
|
+
waveform: state.item.waveform
|
|
3044
|
+
}
|
|
3045
|
+
),
|
|
3046
|
+
/* @__PURE__ */ jsx(
|
|
3047
|
+
TimeReadout,
|
|
3048
|
+
{
|
|
3049
|
+
currentTimeSec: state.currentTimeSec,
|
|
3050
|
+
durationSec: state.durationSec
|
|
3051
|
+
}
|
|
3052
|
+
)
|
|
3053
|
+
] }),
|
|
3054
|
+
/* @__PURE__ */ jsx(
|
|
3055
|
+
SeekBar,
|
|
3056
|
+
{
|
|
3057
|
+
canSeek: state.canSeek,
|
|
3058
|
+
commitMode: options.seekCommitMode,
|
|
3059
|
+
currentTimeSec: state.currentTimeSec,
|
|
3060
|
+
durationSec: state.durationSec,
|
|
3061
|
+
label: `Seek in ${state.item.title}`,
|
|
3062
|
+
onSeek: (seconds) => {
|
|
3063
|
+
commands.seekTo(seconds);
|
|
3064
|
+
},
|
|
3065
|
+
status: state.status
|
|
3066
|
+
}
|
|
3067
|
+
)
|
|
3068
|
+
]
|
|
3069
|
+
}
|
|
3070
|
+
)
|
|
3071
|
+
}
|
|
3072
|
+
);
|
|
3073
|
+
}
|
|
3074
|
+
function BoundCompactPlayer({
|
|
3075
|
+
appearance,
|
|
3076
|
+
className,
|
|
3077
|
+
context,
|
|
3078
|
+
queueItem
|
|
3079
|
+
}) {
|
|
3080
|
+
const bound = useBoundPlayer({ context, queueItem });
|
|
3081
|
+
const player = usePlayer();
|
|
3082
|
+
return /* @__PURE__ */ jsx(
|
|
3083
|
+
BoundCompactPlayerView,
|
|
3084
|
+
{
|
|
3085
|
+
appearance,
|
|
3086
|
+
...className === void 0 ? {} : { className },
|
|
3087
|
+
commands: bound.commands,
|
|
3088
|
+
contextId: context.id,
|
|
3089
|
+
contextMode: context.mode,
|
|
3090
|
+
options: {
|
|
3091
|
+
seekCommitMode: player.state.experienceConfig.seekCommitMode,
|
|
3092
|
+
waveformOpacity: player.state.experienceConfig.waveformOpacity,
|
|
3093
|
+
waveformPlayedStateEnabled: player.state.experienceConfig.waveformPlayedStateEnabled
|
|
3094
|
+
},
|
|
3095
|
+
state: bound.state
|
|
3096
|
+
}
|
|
3097
|
+
);
|
|
3098
|
+
}
|
|
2571
3099
|
|
|
2572
|
-
export { MiniPlayerHost, PlayerProvider, deriveBoundPlayerViewState, useBoundPlayer, usePlayer };
|
|
3100
|
+
export { BoundCompactPlayer, MiniPlayerHost, PlayerProvider, deriveBoundPlayerViewState, useBoundPlayer, usePlayer };
|
|
2573
3101
|
//# sourceMappingURL=index.js.map
|
|
2574
3102
|
//# sourceMappingURL=index.js.map
|