mirror of
https://github.com/owncast/owncast.git
synced 2024-11-24 21:59:43 +03:00
Add initiallyMuted
query parameter to embed player (#2539)
* Add query param to initially mute embed player * Add stories for embed player * Improve VideoJS typing
This commit is contained in:
parent
db3e20b480
commit
2f2300db8d
6 changed files with 117 additions and 9 deletions
|
@ -1,6 +1,7 @@
|
|||
import React, { FC, useEffect } from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { VideoJsPlayerOptions } from 'video.js';
|
||||
import { VideoJS } from '../VideoJS/VideoJS';
|
||||
import ViewerPing from '../viewer-ping';
|
||||
import { VideoPoster } from '../VideoPoster/VideoPoster';
|
||||
|
@ -24,6 +25,7 @@ let latencyCompensatorEnabled = false;
|
|||
export type OwncastPlayerProps = {
|
||||
source: string;
|
||||
online: boolean;
|
||||
initiallyMuted?: boolean;
|
||||
};
|
||||
|
||||
async function getVideoSettings() {
|
||||
|
@ -38,7 +40,11 @@ async function getVideoSettings() {
|
|||
return qualities;
|
||||
}
|
||||
|
||||
export const OwncastPlayer: FC<OwncastPlayerProps> = ({ source, online }) => {
|
||||
export const OwncastPlayer: FC<OwncastPlayerProps> = ({
|
||||
source,
|
||||
online,
|
||||
initiallyMuted = false,
|
||||
}) => {
|
||||
const playerRef = React.useRef(null);
|
||||
const [videoPlaying, setVideoPlaying] = useRecoilState<boolean>(isVideoPlayingAtom);
|
||||
const clockSkew = useRecoilValue<Number>(clockSkewAtom);
|
||||
|
@ -215,6 +221,7 @@ export const OwncastPlayer: FC<OwncastPlayerProps> = ({ source, online }) => {
|
|||
playsinline: true,
|
||||
liveui: true,
|
||||
preload: 'auto',
|
||||
muted: initiallyMuted,
|
||||
controlBar: {
|
||||
progressControl: {
|
||||
seekBar: false,
|
||||
|
@ -239,7 +246,7 @@ export const OwncastPlayer: FC<OwncastPlayerProps> = ({ source, online }) => {
|
|||
type: 'application/x-mpegURL',
|
||||
},
|
||||
],
|
||||
};
|
||||
} satisfies VideoJsPlayerOptions;
|
||||
|
||||
const handlePlayerReady = (player, videojs) => {
|
||||
playerRef.current = player;
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
import React, { FC } from 'react';
|
||||
import videojs from 'video.js';
|
||||
import videojs, { VideoJsPlayer, VideoJsPlayerOptions } from 'video.js';
|
||||
import styles from './VideoJS.module.scss';
|
||||
|
||||
require('video.js/dist/video-js.css');
|
||||
|
||||
export type VideoJSProps = {
|
||||
options: any;
|
||||
onReady: (player: videojs.Player, vjsInstance: videojs) => void;
|
||||
options: VideoJsPlayerOptions;
|
||||
onReady: (player: videojs.Player, vjsInstance: typeof videojs) => void;
|
||||
};
|
||||
|
||||
export const VideoJS: FC<VideoJSProps> = ({ options, onReady }) => {
|
||||
const videoRef = React.useRef(null);
|
||||
const playerRef = React.useRef(null);
|
||||
const videoRef = React.useRef<HTMLVideoElement | null>(null);
|
||||
const playerRef = React.useRef<VideoJsPlayer | null>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
// Make sure Video.js player is only initialized once
|
||||
|
@ -19,7 +19,7 @@ export const VideoJS: FC<VideoJSProps> = ({ options, onReady }) => {
|
|||
const videoElement = videoRef.current;
|
||||
|
||||
// eslint-disable-next-line no-multi-assign
|
||||
const player = (playerRef.current = videojs(videoElement, options, () => {
|
||||
const player: VideoJsPlayer = (playerRef.current = videojs(videoElement, options, () => {
|
||||
console.debug('player is ready');
|
||||
return onReady && onReady(player, videojs);
|
||||
}));
|
||||
|
|
7
web/package-lock.json
generated
7
web/package-lock.json
generated
|
@ -79,6 +79,7 @@
|
|||
"@types/react": "18.0.26",
|
||||
"@types/react-linkify": "1.0.1",
|
||||
"@types/ua-parser-js": "0.7.36",
|
||||
"@types/video.js": "^7.3.50",
|
||||
"@typescript-eslint/eslint-plugin": "5.47.1",
|
||||
"@typescript-eslint/parser": "5.47.1",
|
||||
"babel-loader": "9.1.0",
|
||||
|
@ -11973,6 +11974,12 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz",
|
||||
"integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ=="
|
||||
},
|
||||
"node_modules/@types/video.js": {
|
||||
"version": "7.3.50",
|
||||
"resolved": "https://registry.npmjs.org/@types/video.js/-/video.js-7.3.50.tgz",
|
||||
"integrity": "sha512-xG0xoeyLGuWhtWMBBLRVhTEOfT2n6AjhNoWhFWVbpa6A8hSMi4eNvttuHYXsn6NslITu7IUdKPDRQ2bAWgXKDA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/webpack": {
|
||||
"version": "4.41.33",
|
||||
"resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.33.tgz",
|
||||
|
|
|
@ -83,6 +83,7 @@
|
|||
"@types/react": "18.0.26",
|
||||
"@types/react-linkify": "1.0.1",
|
||||
"@types/ua-parser-js": "0.7.36",
|
||||
"@types/video.js": "^7.3.50",
|
||||
"@typescript-eslint/eslint-plugin": "5.47.1",
|
||||
"@typescript-eslint/parser": "5.47.1",
|
||||
"babel-loader": "9.1.0",
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import React from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { useRouter } from 'next/router';
|
||||
import {
|
||||
clientConfigStateAtom,
|
||||
ClientConfigStore,
|
||||
|
@ -21,11 +22,34 @@ export default function VideoEmbed() {
|
|||
const { offlineMessage } = clientConfig;
|
||||
const { viewerCount, lastConnectTime, lastDisconnectTime } = status;
|
||||
const online = useRecoilValue<boolean>(isOnlineSelector);
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
/**
|
||||
* router.query isn't initialized until hydration
|
||||
* (see https://github.com/vercel/next.js/discussions/11484)
|
||||
* but router.asPath is initialized earlier, so we parse the
|
||||
* query parameters ourselves
|
||||
*/
|
||||
const path = router.asPath.split('?')[1] ?? '';
|
||||
const query = path.split('&').reduce((currQuery, part) => {
|
||||
const [key, value] = part.split('=');
|
||||
return { ...currQuery, [key]: value };
|
||||
}, {} as Record<string, string>);
|
||||
|
||||
const initiallyMuted = query.initiallyMuted === 'true';
|
||||
|
||||
return (
|
||||
<>
|
||||
<ClientConfigStore />
|
||||
<div className="video-embed">
|
||||
{online && <OwncastPlayer source="/hls/stream.m3u8" online={online} />}
|
||||
{online && (
|
||||
<OwncastPlayer
|
||||
source="/hls/stream.m3u8"
|
||||
online={online}
|
||||
initiallyMuted={initiallyMuted}
|
||||
/>
|
||||
)}
|
||||
{!online && (
|
||||
<OfflineBanner
|
||||
streamName={name}
|
||||
|
|
69
web/stories/VideoEmbed.stories.tsx
Normal file
69
web/stories/VideoEmbed.stories.tsx
Normal file
|
@ -0,0 +1,69 @@
|
|||
import { ComponentMeta, ComponentStory } from '@storybook/react';
|
||||
|
||||
const Template = ({
|
||||
origin,
|
||||
query,
|
||||
title,
|
||||
width,
|
||||
height,
|
||||
}: {
|
||||
origin: string;
|
||||
query: string;
|
||||
title: string;
|
||||
width: number;
|
||||
height: number;
|
||||
}) => (
|
||||
<iframe
|
||||
src={`${origin}/embed/video?${query}`}
|
||||
title={title}
|
||||
height={`${height}px`}
|
||||
width={`${width}px`}
|
||||
referrerPolicy="origin"
|
||||
scrolling="no"
|
||||
allowFullScreen
|
||||
/>
|
||||
);
|
||||
|
||||
const origins = {
|
||||
DemoServer: `https://watch.owncast.online`,
|
||||
RetroStrangeTV: `https://live.retrostrange.com`,
|
||||
localhost: `http://localhost:3000`,
|
||||
};
|
||||
|
||||
export default {
|
||||
title: 'owncast/Player/Embeds',
|
||||
component: Template,
|
||||
argTypes: {
|
||||
origin: {
|
||||
options: Object.keys(origins),
|
||||
mapping: origins,
|
||||
control: {
|
||||
type: 'select',
|
||||
},
|
||||
defaultValue: origins.DemoServer,
|
||||
},
|
||||
query: {
|
||||
type: 'string',
|
||||
},
|
||||
title: {
|
||||
defaultValue: 'My Title',
|
||||
type: 'string',
|
||||
},
|
||||
height: {
|
||||
defaultValue: 350,
|
||||
type: 'number',
|
||||
},
|
||||
width: {
|
||||
defaultValue: 550,
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
} satisfies ComponentMeta<typeof Template>;
|
||||
|
||||
export const Default: ComponentStory<typeof Template> = Template.bind({});
|
||||
Default.args = {};
|
||||
|
||||
export const InitiallyMuted: ComponentStory<typeof Template> = Template.bind({});
|
||||
InitiallyMuted.args = {
|
||||
query: 'initiallyMuted=true',
|
||||
};
|
Loading…
Reference in a new issue