mirror of
https://github.com/owncast/owncast.git
synced 2024-10-10 19:16:02 +00:00
WIP VideoPoster
This commit is contained in:
parent
e2e21d915b
commit
15ca73a438
@ -60,34 +60,6 @@ function Main() {
|
||||
</Layout>
|
||||
</>
|
||||
);
|
||||
// return (
|
||||
// <div>
|
||||
|
||||
// <Layout>
|
||||
// <Header className="header">
|
||||
// {name}
|
||||
// <button onClick={toggleChatCollapsed}>Toggle Chat</button>
|
||||
// </Header>
|
||||
// <Content>
|
||||
// <Layout>
|
||||
// <Row>
|
||||
// <Col span={24}>Video player goes here</Col>
|
||||
// </Row>
|
||||
// <Row>
|
||||
// <Col span={24}>
|
||||
// <Content dangerouslySetInnerHTML={{ __html: extraPageContent }} />
|
||||
// </Col>
|
||||
// </Row>
|
||||
|
||||
// <Sider collapsed={chatCollapsed} width={300}>
|
||||
// chat
|
||||
// </Sider>
|
||||
// </Layout>
|
||||
// </Content>
|
||||
// <Footer>Footer: Owncast {version}</Footer>
|
||||
// </Layout>
|
||||
// </div>
|
||||
// );
|
||||
}
|
||||
|
||||
export default Main;
|
||||
|
109
web/components/video/VideoPoster.tsx
Normal file
109
web/components/video/VideoPoster.tsx
Normal file
@ -0,0 +1,109 @@
|
||||
/*
|
||||
VideoPoster is the image that covers up the video component and shows a
|
||||
preview of the video, refreshing every N seconds.
|
||||
It's more complex than it needs to be, using the "double buffer" approach to
|
||||
cross-fade the two images. Now that we've moved to React we may be able to
|
||||
simply use some simple cross-fading component.
|
||||
*/
|
||||
|
||||
import { useEffect, useLayoutEffect, useState } from 'react';
|
||||
import { ReactElement } from 'react-markdown/lib/react-markdown';
|
||||
|
||||
const REFRESH_INTERVAL = 15000;
|
||||
const TEMP_IMAGE = 'http://localhost:8080/logo';
|
||||
const POSTER_BASE_URL = 'http://localhost:8080/';
|
||||
|
||||
export default function VideoPoster(props): ReactElement {
|
||||
const { active } = props;
|
||||
const [flipped, setFlipped] = useState(false);
|
||||
const [oldUrl, setOldUrl] = useState(TEMP_IMAGE);
|
||||
const [url, setUrl] = useState(props.url);
|
||||
const [currentUrl, setCurrentUrl] = useState(TEMP_IMAGE);
|
||||
const [loadingImage, setLoadingImage] = useState(TEMP_IMAGE);
|
||||
const [offlineImage, setOfflineImage] = useState(TEMP_IMAGE);
|
||||
|
||||
let refreshTimer = null;
|
||||
|
||||
const setLoaded = () => {
|
||||
setFlipped(!flipped);
|
||||
setUrl(loadingImage);
|
||||
setOldUrl(currentUrl);
|
||||
};
|
||||
|
||||
const fire = () => {
|
||||
const cachebuster = Math.round(new Date().getTime() / 1000);
|
||||
setLoadingImage(`${POSTER_BASE_URL}?cb=${cachebuster}`);
|
||||
const img = new Image();
|
||||
img.onload = setLoaded;
|
||||
img.src = loadingImage;
|
||||
};
|
||||
|
||||
const stopRefreshTimer = () => {
|
||||
clearInterval(refreshTimer);
|
||||
refreshTimer = null;
|
||||
};
|
||||
|
||||
const startRefreshTimer = () => {
|
||||
stopRefreshTimer();
|
||||
fire();
|
||||
// Load a new copy of the image every n seconds
|
||||
refreshTimer = setInterval(fire, REFRESH_INTERVAL);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (active) {
|
||||
fire();
|
||||
startRefreshTimer();
|
||||
} else {
|
||||
stopRefreshTimer();
|
||||
}
|
||||
}, [active]);
|
||||
|
||||
// On component unmount.
|
||||
useLayoutEffect(
|
||||
() => () => {
|
||||
stopRefreshTimer();
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
// TODO: Replace this with React memo logic.
|
||||
// shouldComponentUpdate(prevProps, prevState) {
|
||||
// return (
|
||||
// this.props.active !== prevProps.active ||
|
||||
// this.props.offlineImage !== prevProps.offlineImage ||
|
||||
// this.state.url !== prevState.url ||
|
||||
// this.state.oldUrl !== prevState.oldUrl
|
||||
// );
|
||||
// }
|
||||
|
||||
if (!active) {
|
||||
return (
|
||||
<div id="oc-custom-poster">
|
||||
<ThumbImage url={offlineImage} visible />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div id="oc-custom-poster">
|
||||
<ThumbImage url={!flipped ? oldUrl : url} visible />
|
||||
<ThumbImage url={flipped ? oldUrl : url} visible={!flipped} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ThumbImage({ url, visible }) {
|
||||
if (!url) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className="custom-thumbnail-image"
|
||||
style={{
|
||||
opacity: visible ? 1 : 0,
|
||||
backgroundImage: `url(${url})`,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
16
web/stories/Video.stories.tsx
Normal file
16
web/stories/Video.stories.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import VideoPoster from '../components/video/VideoPoster';
|
||||
|
||||
export default {
|
||||
title: 'owncast/VideoPoster',
|
||||
component: VideoPoster,
|
||||
parameters: {},
|
||||
} as ComponentMeta<typeof VideoPoster>;
|
||||
|
||||
const VideoPosterExample = () => <VideoPoster />;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const Template: ComponentStory<typeof VideoPoster> = args => <VideoPosterExample />;
|
||||
|
||||
export const Basic = Template.bind({});
|
@ -1,114 +0,0 @@
|
||||
import { h, Component } from '/js/web_modules/preact.js';
|
||||
import htm from '/js/web_modules/htm.js';
|
||||
const html = htm.bind(h);
|
||||
|
||||
import { TEMP_IMAGE } from '../utils/constants.js';
|
||||
|
||||
const REFRESH_INTERVAL = 15000;
|
||||
const POSTER_BASE_URL = '/thumbnail.jpg';
|
||||
|
||||
export default class VideoPoster extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
// flipped is the state of showing primary/secondary image views
|
||||
flipped: false,
|
||||
oldUrl: TEMP_IMAGE,
|
||||
url: TEMP_IMAGE,
|
||||
};
|
||||
|
||||
this.refreshTimer = null;
|
||||
this.startRefreshTimer = this.startRefreshTimer.bind(this);
|
||||
this.fire = this.fire.bind(this);
|
||||
this.setLoaded = this.setLoaded.bind(this);
|
||||
}
|
||||
componentDidMount() {
|
||||
if (this.props.active) {
|
||||
this.fire();
|
||||
this.startRefreshTimer();
|
||||
}
|
||||
}
|
||||
shouldComponentUpdate(prevProps, prevState) {
|
||||
return this.props.active !== prevProps.active ||
|
||||
this.props.offlineImage !== prevProps.offlineImage ||
|
||||
this.state.url !== prevState.url ||
|
||||
this.state.oldUrl !== prevState.oldUrl;
|
||||
}
|
||||
componentDidUpdate(prevProps) {
|
||||
const { active } = this.props;
|
||||
const { active: prevActive } = prevProps;
|
||||
|
||||
if (active && !prevActive) {
|
||||
this.startRefreshTimer();
|
||||
} else if (!active && prevActive) {
|
||||
this.stopRefreshTimer();
|
||||
}
|
||||
}
|
||||
componentWillUnmount() {
|
||||
this.stopRefreshTimer();
|
||||
}
|
||||
|
||||
startRefreshTimer() {
|
||||
this.stopRefreshTimer();
|
||||
this.fire();
|
||||
// Load a new copy of the image every n seconds
|
||||
this.refreshTimer = setInterval(this.fire, REFRESH_INTERVAL);
|
||||
}
|
||||
|
||||
// load new img
|
||||
fire() {
|
||||
const cachebuster = Math.round(new Date().getTime() / 1000);
|
||||
this.loadingImage = POSTER_BASE_URL + '?cb=' + cachebuster;
|
||||
const img = new Image();
|
||||
img.onload = this.setLoaded;
|
||||
img.src = this.loadingImage;
|
||||
}
|
||||
|
||||
setLoaded() {
|
||||
const { url: currentUrl, flipped } = this.state;
|
||||
this.setState({
|
||||
flipped: !flipped,
|
||||
url: this.loadingImage,
|
||||
oldUrl: currentUrl,
|
||||
});
|
||||
}
|
||||
|
||||
stopRefreshTimer() {
|
||||
clearInterval(this.refreshTimer);
|
||||
this.refreshTimer = null;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { active, offlineImage } = this.props;
|
||||
const { url, oldUrl, flipped } = this.state;
|
||||
if (!active) {
|
||||
return html`
|
||||
<div id="oc-custom-poster">
|
||||
<${ThumbImage} url=${offlineImage} visible=${true} />
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
return html`
|
||||
<div id="oc-custom-poster">
|
||||
<${ThumbImage} url=${!flipped ? oldUrl : url } visible=${true} />
|
||||
<${ThumbImage} url=${flipped ? oldUrl : url } visible=${!flipped} />
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
function ThumbImage({ url, visible }) {
|
||||
if (!url) {
|
||||
return null;
|
||||
}
|
||||
return html`
|
||||
<div
|
||||
class="custom-thumbnail-image"
|
||||
style=${{
|
||||
opacity: visible ? 1 : 0,
|
||||
backgroundImage: `url(${url})`,
|
||||
}}
|
||||
/>
|
||||
`;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user