lbry-desktop/ui/page/livestream/view.jsx

214 lines
8 KiB
JavaScript

// @flow
import { formatLbryChannelName } from 'util/url';
import { lazyImport } from 'util/lazyImport';
import { LIVESTREAM_STARTS_SOON_BUFFER, LIVESTREAM_STARTED_RECENTLY_BUFFER } from 'constants/livestream';
import analytics from 'analytics';
import LivestreamLayout from 'component/livestreamLayout';
import moment from 'moment';
import Page from 'component/page';
import React from 'react';
import { useIsMobile } from 'effects/use-screensize';
import useFetchLiveStatus from 'effects/use-fetch-live';
const LivestreamChatLayout = lazyImport(() => import('component/livestreamChatLayout' /* webpackChunkName: "chat" */));
type Props = {
activeLivestreamForChannel: any,
activeLivestreamInitialized: boolean,
channelClaimId: ?string,
chatDisabled: boolean,
claim: StreamClaim,
isAuthenticated: boolean,
uri: string,
socketConnection: { connected: ?boolean },
isStreamPlaying: boolean,
doSetPrimaryUri: (uri: ?string) => void,
doCommentSocketConnect: (uri: string, channelName: string, claimId: string) => void,
doCommentSocketDisconnect: (claimId: string, channelName: string) => void,
doFetchChannelLiveStatus: (string) => void,
doUserSetReferrer: (string) => void,
};
export const LivestreamContext = React.createContext<any>();
export default function LivestreamPage(props: Props) {
const {
activeLivestreamForChannel,
activeLivestreamInitialized,
channelClaimId,
chatDisabled,
claim,
isAuthenticated,
uri,
socketConnection,
isStreamPlaying,
doSetPrimaryUri,
doCommentSocketConnect,
doCommentSocketDisconnect,
doFetchChannelLiveStatus,
doUserSetReferrer,
} = props;
const isMobile = useIsMobile();
const streamPlayingRef = React.useRef();
const [activeStreamUri, setActiveStreamUri] = React.useState(false);
const [showLivestream, setShowLivestream] = React.useState(false);
const [showScheduledInfo, setShowScheduledInfo] = React.useState(false);
const [hideComments, setHideComments] = React.useState(false);
const [layountRendered, setLayountRendered] = React.useState(chatDisabled || isMobile);
const isInitialized = Boolean(activeLivestreamForChannel) || activeLivestreamInitialized;
const isChannelBroadcasting = Boolean(activeLivestreamForChannel);
const claimId = claim && claim.claim_id;
const isCurrentClaimLive = isChannelBroadcasting && activeLivestreamForChannel.claimId === claimId;
const livestreamChannelId = channelClaimId || '';
const releaseTime: moment = moment.unix(claim?.value?.release_time || 0);
const stringifiedClaim = JSON.stringify(claim);
React.useEffect(() => {
// TODO: This should not be needed once we unify the livestream player (?)
analytics.playerLoadedEvent('livestream', false);
}, []);
const { signing_channel: channelClaim } = claim || {};
const { canonical_url: channelUrl } = channelClaim || {};
// On livestream page, only connect, fileRenderFloating will handle disconnect.
// (either by leaving page with floating player off, or by closing the player)
React.useEffect(() => {
const { claim_id: claimId, signing_channel: channelClaim } = claim;
const channelName = channelClaim && formatLbryChannelName(channelUrl);
if (claimId && channelName && !socketConnection?.connected) {
doCommentSocketConnect(uri, channelName, claimId);
}
// willAutoplay mount only
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [channelUrl, claim, doCommentSocketConnect, doCommentSocketDisconnect, socketConnection, uri]);
React.useEffect(() => {
// use for unmount case without triggering render
streamPlayingRef.current = isStreamPlaying;
}, [isStreamPlaying]);
React.useEffect(() => {
return () => {
if (!streamPlayingRef.current) {
const { claim_id: claimId, signing_channel: channelClaim } = claim;
const channelName = channelClaim && formatLbryChannelName(channelUrl);
if (claimId && channelName) doCommentSocketDisconnect(claimId, channelName);
}
};
// only on unmount -> leave page
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const claimReleaseStartingSoonStatic = () =>
releaseTime.isBetween(moment(), moment().add(LIVESTREAM_STARTS_SOON_BUFFER, 'minutes'));
const claimReleaseStartedRecentlyStatic = () =>
releaseTime.isBetween(moment().subtract(LIVESTREAM_STARTED_RECENTLY_BUFFER, 'minutes'), moment());
// Find out current channels status + active live claim every 30 seconds (or 15 if not live)
const fasterPoll = !isCurrentClaimLive && (claimReleaseStartingSoonStatic() || claimReleaseStartedRecentlyStatic());
useFetchLiveStatus(livestreamChannelId, doFetchChannelLiveStatus, fasterPoll);
React.useEffect(() => {
setActiveStreamUri(!isCurrentClaimLive && isChannelBroadcasting ? activeLivestreamForChannel.claimUri : false);
}, [isCurrentClaimLive, isChannelBroadcasting]); // eslint-disable-line react-hooks/exhaustive-deps
React.useEffect(() => {
if (!isInitialized) return;
const claimReleaseInFuture = () => releaseTime.isAfter();
const claimReleaseInPast = () => releaseTime.isBefore();
const claimReleaseStartingSoon = () =>
releaseTime.isBetween(moment(), moment().add(LIVESTREAM_STARTS_SOON_BUFFER, 'minutes'));
const claimReleaseStartedRecently = () =>
releaseTime.isBetween(moment().subtract(LIVESTREAM_STARTED_RECENTLY_BUFFER, 'minutes'), moment());
const checkShowLivestream = () =>
isChannelBroadcasting &&
isCurrentClaimLive &&
(claimReleaseInPast() || claimReleaseStartingSoon() || claimReleaseInFuture());
const checkShowScheduledInfo = () =>
(!isChannelBroadcasting && (claimReleaseInFuture() || claimReleaseStartedRecently())) ||
(isChannelBroadcasting &&
((!isCurrentClaimLive && (claimReleaseInFuture() || claimReleaseStartedRecently())) ||
(isCurrentClaimLive && claimReleaseInFuture() && !claimReleaseStartingSoon())));
const checkCommentsDisabled = () => chatDisabled || (claimReleaseInFuture() && !claimReleaseStartingSoon());
const calculateStreamReleaseState = () => {
setShowLivestream(checkShowLivestream());
setShowScheduledInfo(checkShowScheduledInfo());
setHideComments(checkCommentsDisabled());
};
calculateStreamReleaseState();
const intervalId = setInterval(calculateStreamReleaseState, 5000);
if (isCurrentClaimLive && claimReleaseInPast() && isChannelBroadcasting === true) {
clearInterval(intervalId);
}
return () => clearInterval(intervalId);
}, [chatDisabled, isChannelBroadcasting, releaseTime, isCurrentClaimLive, isInitialized]);
React.useEffect(() => {
if (uri && stringifiedClaim) {
const jsonClaim = JSON.parse(stringifiedClaim);
if (!isAuthenticated) {
const uri = jsonClaim.signing_channel && jsonClaim.signing_channel.permanent_url;
if (uri) doUserSetReferrer(uri.replace('lbry://', ''));
}
}
}, [uri, stringifiedClaim, isAuthenticated, doUserSetReferrer]);
React.useEffect(() => {
if (!layountRendered) return;
doSetPrimaryUri(uri);
return () => doSetPrimaryUri(null);
}, [doSetPrimaryUri, layountRendered, uri]);
return (
<Page
className="file-page scheduledLivestream-wrapper"
noFooter
livestream
chatDisabled={hideComments}
rightSide={
!hideComments &&
isInitialized && (
<React.Suspense fallback={null}>
<LivestreamChatLayout uri={uri} setLayountRendered={setLayountRendered} />
</React.Suspense>
)
}
>
{isInitialized && (
<LivestreamContext.Provider value={{ livestreamPage: true, layountRendered }}>
<LivestreamLayout
uri={uri}
hideComments={hideComments}
releaseTimeMs={releaseTime.unix() * 1000}
isCurrentClaimLive={isCurrentClaimLive}
showLivestream={showLivestream}
showScheduledInfo={showScheduledInfo}
activeStreamUri={activeStreamUri}
/>
</LivestreamContext.Provider>
)}
</Page>
);
}