221 lines
4.8 KiB
TypeScript
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 = {};
|