Files
_hublib-web/stories/angular-video-player-adapter.stories.tsx
2026-02-27 09:50:13 +03:00

221 lines
4.8 KiB
TypeScript

import type { Meta, StoryObj } from "@storybook/react";
import React, { useEffect, useMemo, useRef, useState } from "react";
import { AngularVideoPlayerAdapter } from "../src/angular";
import type { EngineStrategy, VideoPlayerRuntimePreload } from "../src/core";
interface AngularAdapterStoryArgs {
src: string;
type: string;
strategy: EngineStrategy;
preload: VideoPlayerRuntimePreload;
autoplay: boolean;
controls: boolean;
responsive: boolean;
aspectRatio: string;
fluid: boolean;
muted: boolean;
poster: string;
preferHQ: boolean;
debug: boolean;
initialTime: number;
isIOS: boolean;
isMobile: boolean;
full: boolean;
withRewind: boolean;
skipSeconds: number;
classNames: string;
}
const DEMO_HLS_SOURCE = "https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8";
const DEMO_POSTER =
"https://images.unsplash.com/photo-1574267432553-4b4628081c31?auto=format&fit=crop&w=1280&q=80";
const toAdapterInput = (args: AngularAdapterStoryArgs) => ({
source: {
src: args.src,
type: args.type,
},
strategy: args.strategy,
preload: args.preload,
autoplay: args.autoplay,
controls: args.controls,
responsive: args.responsive,
aspectRatio: args.aspectRatio === "none" ? undefined : args.aspectRatio,
fluid: args.fluid,
muted: args.muted,
poster: args.poster || undefined,
preferHQ: args.preferHQ,
debug: args.debug,
initialTime: args.initialTime,
isIOS: args.isIOS,
isMobile: args.isMobile,
full: args.full,
withRewind: args.withRewind,
skipSeconds: args.skipSeconds,
classNames: args.classNames
.split(",")
.map(item => item.trim())
.filter(Boolean),
});
const statusText = ({
initialized,
engine,
source,
}: {
initialized: boolean;
engine: string | null;
source?: { src: string } | null;
}) => {
const sourceText = source?.src ? source.src : "none";
return `initialized=${initialized}; engine=${engine ?? "none"}; source=${sourceText}`;
};
const AngularAdapterHarness = (args: AngularAdapterStoryArgs) => {
const hostRef = useRef<HTMLDivElement | null>(null);
const adapterRef = useRef<AngularVideoPlayerAdapter | null>(null);
const [status, setStatus] = useState("initializing");
const input = useMemo(() => toAdapterInput(args), [args]);
useEffect(() => {
const host = hostRef.current;
if (!host) {
return;
}
const adapter = new AngularVideoPlayerAdapter();
adapterRef.current = adapter;
let active = true;
void adapter
.attach(host, input)
.then(state => {
if (!active) {
return;
}
setStatus(statusText(state.runtime));
})
.catch(error => {
if (!active) {
return;
}
setStatus(`attach error: ${String(error)}`);
});
return () => {
active = false;
adapter.destroy();
adapterRef.current = null;
};
}, []);
useEffect(() => {
const adapter = adapterRef.current;
if (!adapter) {
return;
}
let active = true;
void adapter
.update(input)
.then(state => {
if (!active) {
return;
}
setStatus(statusText(state.runtime));
})
.catch(error => {
if (!active) {
return;
}
setStatus(`update error: ${String(error)}`);
});
return () => {
active = false;
};
}, [input]);
return (
<div>
<div
style={{
marginBottom: 12,
fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
fontSize: 12,
color: "#4f4f4f",
}}
>
{status}
</div>
<div style={{ width: "100%", maxWidth: 960, minHeight: 320 }} ref={hostRef} />
</div>
);
};
const meta: Meta<AngularAdapterStoryArgs> = {
title: "Angular/VideoPlayerAdapter",
component: AngularAdapterHarness,
tags: ["autodocs"],
args: {
src: DEMO_HLS_SOURCE,
type: "application/x-mpegurl",
strategy: "auto",
preload: "auto",
autoplay: false,
controls: true,
responsive: true,
aspectRatio: "16:9",
fluid: true,
muted: true,
poster: DEMO_POSTER,
preferHQ: false,
debug: false,
initialTime: 0,
isIOS: false,
isMobile: false,
full: true,
withRewind: true,
skipSeconds: 10,
classNames: "",
},
argTypes: {
src: { control: "text" },
type: {
control: { type: "select" },
options: [
"application/x-mpegurl",
"application/vnd.apple.mpegurl",
"video/mp4",
],
},
strategy: {
control: { type: "inline-radio" },
options: ["auto", "force-hls", "force-videojs"],
},
preload: {
control: { type: "inline-radio" },
options: ["auto", "metadata", "none", "visibility"],
},
aspectRatio: {
control: { type: "select" },
options: ["16:9", "4:3", "1:1", "none"],
},
initialTime: { control: { type: "number", min: 0, step: 1 } },
skipSeconds: { control: { type: "number", min: 1, step: 1 } },
classNames: {
control: "text",
description: "Comma-separated class names (e.g. test-one,test-two)",
},
},
render: args => <AngularAdapterHarness {...args} />,
};
export default meta;
type Story = StoryObj<AngularAdapterStoryArgs>;
export const Playground: Story = {};