WIP VideoPoster

This commit is contained in:
Gabe Kangas 2022-04-26 13:39:35 -07:00
parent e2e21d915b
commit 15ca73a438
No known key found for this signature in database
GPG key ID: 9A56337728BC81EA
4 changed files with 125 additions and 142 deletions

View file

@ -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;

View 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})`,
}}
/>
);
}

View 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({});

View file

@ -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})`,
}}
/>
`;
}