Add stream start to active live streams, and refactor how data active claim is stored in redux

This commit is contained in:
Dan Peterson 2021-12-22 10:12:44 -06:00 committed by Thomas Zarebczan
parent 72c1a8ae25
commit d382671616
18 changed files with 192 additions and 203 deletions

View file

@ -21,17 +21,6 @@ declare type LivestreamReplayItem = {
} }
declare type LivestreamReplayData = Array<LivestreamReplayItem>; declare type LivestreamReplayData = Array<LivestreamReplayItem>;
declare type CurrentLiveClaim = {
claimId: string | null,
claimUri: string | null,
}
declare type LivestreamChannelStatus = {
channelId: null | string,
isBroadcasting: boolean,
liveClaim: CurrentLiveClaim,
}
declare type LivestreamState = { declare type LivestreamState = {
fetchingById: {}, fetchingById: {},
viewersById: {}, viewersById: {},
@ -39,8 +28,7 @@ declare type LivestreamState = {
activeLivestreams: ?LivestreamInfo, activeLivestreams: ?LivestreamInfo,
activeLivestreamsLastFetchedDate: number, activeLivestreamsLastFetchedDate: number,
activeLivestreamsLastFetchedOptions: {}, activeLivestreamsLastFetchedOptions: {},
activeLivestreamInitialized: boolean,
currentChannelStatus: LivestreamChannelStatus,
} }
declare type LivestreamInfo = { declare type LivestreamInfo = {

View file

@ -14,8 +14,8 @@ import { withRouter } from 'react-router';
import { selectUserVerifiedEmail } from 'redux/selectors/user'; import { selectUserVerifiedEmail } from 'redux/selectors/user';
import { selectClientSetting, selectShowMatureContent } from 'redux/selectors/settings'; import { selectClientSetting, selectShowMatureContent } from 'redux/selectors/settings';
import { doFetchActiveLivestream } from 'redux/actions/livestream'; import { doFetchActiveLivestream } from 'redux/actions/livestream';
import { selectCurrentChannelStatus } from 'redux/selectors/livestream'; import { selectActiveLivestreamForChannel, selectActiveLivestreamInitialized } from 'redux/selectors/livestream';
import { getChannelIdFromClaim } from 'util/claim';
import ChannelContent from './view'; import ChannelContent from './view';
const select = (state, props) => { const select = (state, props) => {
@ -23,6 +23,7 @@ const select = (state, props) => {
const urlParams = new URLSearchParams(search); const urlParams = new URLSearchParams(search);
const page = urlParams.get('page') || 0; const page = urlParams.get('page') || 0;
const claim = props.uri && selectClaimForUri(state, props.uri); const claim = props.uri && selectClaimForUri(state, props.uri);
const channelClaimId = getChannelIdFromClaim(claim);
return { return {
pageOfClaimsInChannel: makeSelectClaimsInChannelForPage(props.uri, page)(state), pageOfClaimsInChannel: makeSelectClaimsInChannelForPage(props.uri, page)(state),
@ -34,7 +35,8 @@ const select = (state, props) => {
isAuthenticated: selectUserVerifiedEmail(state), isAuthenticated: selectUserVerifiedEmail(state),
showMature: selectShowMatureContent(state), showMature: selectShowMatureContent(state),
tileLayout: selectClientSetting(state, SETTINGS.TILE_LAYOUT), tileLayout: selectClientSetting(state, SETTINGS.TILE_LAYOUT),
currentChannelStatus: selectCurrentChannelStatus(state), activeLivestreamForChannel: selectActiveLivestreamForChannel(state, channelClaimId),
activeLivestreamInitialized: selectActiveLivestreamInitialized(state),
}; };
}; };

View file

@ -38,7 +38,8 @@ type Props = {
claimType: string, claimType: string,
empty?: string, empty?: string,
doFetchActiveLivestream: (string) => void, doFetchActiveLivestream: (string) => void,
currentChannelStatus: LivestreamChannelStatus, activeLivestreamForChannel: any,
activeLivestreamInitialized: boolean,
}; };
function ChannelContent(props: Props) { function ChannelContent(props: Props) {
@ -59,7 +60,8 @@ function ChannelContent(props: Props) {
claimType, claimType,
empty, empty,
doFetchActiveLivestream, doFetchActiveLivestream,
currentChannelStatus, activeLivestreamForChannel,
activeLivestreamInitialized,
} = props; } = props;
// const claimsInChannel = (claim && claim.meta.claims_in_channel) || 0; // const claimsInChannel = (claim && claim.meta.claims_in_channel) || 0;
const claimsInChannel = 9999; const claimsInChannel = 9999;
@ -252,8 +254,8 @@ function ChannelContent(props: Props) {
setSearchResults(null); setSearchResults(null);
}, [url]); }, [url]);
const [isInitialized, setIsInitialized] = React.useState(false); const isInitialized = Boolean(activeLivestreamForChannel) || activeLivestreamInitialized;
const [isChannelBroadcasting, setIsChannelBroadcasting] = React.useState(false); const isChannelBroadcasting = Boolean(activeLivestreamForChannel);
// Find out current channels status + active live claim. // Find out current channels status + active live claim.
React.useEffect(() => { React.useEffect(() => {
@ -262,14 +264,6 @@ function ChannelContent(props: Props) {
return () => clearInterval(intervalId); return () => clearInterval(intervalId);
}, [claimId, doFetchActiveLivestream]); }, [claimId, doFetchActiveLivestream]);
React.useEffect(() => {
const initialized = currentChannelStatus.channelId === claimId;
setIsInitialized(initialized);
if (initialized) {
setIsChannelBroadcasting(currentChannelStatus.isBroadcasting);
}
}, [currentChannelStatus, claimId]);
const showScheduledLiveStreams = claimType !== 'collection'; // ie. not on the playlist page. const showScheduledLiveStreams = claimType !== 'collection'; // ie. not on the playlist page.
return ( return (
@ -279,7 +273,7 @@ function ChannelContent(props: Props) {
)} )}
{!fetching && isInitialized && isChannelBroadcasting && !isChannelEmpty && ( {!fetching && isInitialized && isChannelBroadcasting && !isChannelEmpty && (
<LivestreamLink claimUri={currentChannelStatus.liveClaim.claimUri} /> <LivestreamLink claimUri={activeLivestreamForChannel.claimUri} />
)} )}
{!fetching && showScheduledLiveStreams && ( {!fetching && showScheduledLiveStreams && (
@ -287,7 +281,7 @@ function ChannelContent(props: Props) {
channelIds={[claimId]} channelIds={[claimId]}
tileLayout={tileLayout} tileLayout={tileLayout}
liveUris={ liveUris={
isChannelBroadcasting && currentChannelStatus.liveClaim ? [currentChannelStatus.liveClaim.claimUri] : [] isChannelBroadcasting && activeLivestreamForChannel.claimUri ? [activeLivestreamForChannel.claimUri] : []
} }
/> />
)} )}

View file

@ -1,5 +0,0 @@
import { createContext } from 'react';
const ClaimListDiscoverContext = createContext();
export default ClaimListDiscoverContext;

View file

@ -5,7 +5,6 @@ import { doClearPublish, doPrepareEdit } from 'redux/actions/publish';
import { push } from 'connected-react-router'; import { push } from 'connected-react-router';
import ClaimPreviewSubtitle from './view'; import ClaimPreviewSubtitle from './view';
import { doFetchSubCount, selectSubCountForUri } from 'lbryinc'; import { doFetchSubCount, selectSubCountForUri } from 'lbryinc';
import { selectIsActiveLivestreamForUri } from 'redux/selectors/livestream';
const select = (state, props) => { const select = (state, props) => {
const claim = selectClaimForUri(state, props.uri); const claim = selectClaimForUri(state, props.uri);
@ -16,7 +15,6 @@ const select = (state, props) => {
pending: makeSelectClaimIsPending(props.uri)(state), pending: makeSelectClaimIsPending(props.uri)(state),
isLivestream, isLivestream,
subCount: isChannel ? selectSubCountForUri(state, props.uri) : 0, subCount: isChannel ? selectSubCountForUri(state, props.uri) : 0,
isLivestreamActive: isLivestream && selectIsActiveLivestreamForUri(state, props.uri),
}; };
}; };

View file

@ -1,13 +1,12 @@
// @flow // @flow
import { ENABLE_NO_SOURCE_CLAIMS } from 'config'; import { ENABLE_NO_SOURCE_CLAIMS } from 'config';
import React, { useContext } from 'react'; import React from 'react';
import UriIndicator from 'component/uriIndicator'; import UriIndicator from 'component/uriIndicator';
import DateTime from 'component/dateTime'; import DateTime from 'component/dateTime';
import LivestreamDateTime from 'component/livestreamDateTime';
import Button from 'component/button'; import Button from 'component/button';
import FileViewCountInline from 'component/fileViewCountInline'; import FileViewCountInline from 'component/fileViewCountInline';
import { parseURI } from 'util/lbryURI'; import { parseURI } from 'util/lbryURI';
import ClaimListDiscoverContext from 'component/claimListDiscover/context';
import moment from 'moment';
type Props = { type Props = {
uri: string, uri: string,
@ -18,12 +17,11 @@ type Props = {
isLivestream: boolean, isLivestream: boolean,
fetchSubCount: (string) => void, fetchSubCount: (string) => void,
subCount: number, subCount: number,
isLivestreamActive: boolean,
}; };
// previews used in channel overview and homepage (and other places?) // previews used in channel overview and homepage (and other places?)
function ClaimPreviewSubtitle(props: Props) { function ClaimPreviewSubtitle(props: Props) {
const { pending, uri, claim, type, beginPublish, isLivestream, isLivestreamActive, fetchSubCount, subCount } = props; const { pending, uri, claim, type, beginPublish, isLivestream, fetchSubCount, subCount } = props;
const isChannel = claim && claim.value_type === 'channel'; const isChannel = claim && claim.value_type === 'channel';
const claimsInChannel = (claim && claim.meta.claims_in_channel) || 0; const claimsInChannel = (claim && claim.meta.claims_in_channel) || 0;
@ -41,29 +39,6 @@ function ClaimPreviewSubtitle(props: Props) {
({ streamName: name } = parseURI(uri)); ({ streamName: name } = parseURI(uri));
} catch (e) {} } catch (e) {}
const { listingType } = useContext(ClaimListDiscoverContext) || {};
const LivestreamDateTimeLabel = () => {
// If showing in upcoming and in the past. (we allow x time in past to show here if not live yet)
if (listingType === 'UPCOMING') {
// $FlowFixMe
if (moment.unix(claim.value.release_time).isBefore()) {
return __('Starting Soon');
}
} else {
// If not in upcoming + live and in the future (started streaming a bit early)
// $FlowFixMe
if (isLivestreamActive && moment.unix(claim.value.release_time).isAfter()) {
return __('Streaming Now');
}
}
return (
<>
{__('Livestream')} <DateTime timeAgo uri={uri} />
</>
);
};
return ( return (
<div className="media__subtitle"> <div className="media__subtitle">
{claim ? ( {claim ? (
@ -82,7 +57,7 @@ function ClaimPreviewSubtitle(props: Props) {
{!isChannel && {!isChannel &&
(isLivestream && ENABLE_NO_SOURCE_CLAIMS ? ( (isLivestream && ENABLE_NO_SOURCE_CLAIMS ? (
<LivestreamDateTimeLabel /> <LivestreamDateTime uri={uri} />
) : ( ) : (
<> <>
<FileViewCountInline uri={uri} isLivestream={isLivestream} /> <FileViewCountInline uri={uri} isLivestream={isLivestream} />

View file

@ -1,11 +1,12 @@
// @flow // @flow
import React, { useContext } from 'react'; import React from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { NavLink, withRouter } from 'react-router-dom'; import { NavLink, withRouter } from 'react-router-dom';
import FileThumbnail from 'component/fileThumbnail'; import FileThumbnail from 'component/fileThumbnail';
import UriIndicator from 'component/uriIndicator'; import UriIndicator from 'component/uriIndicator';
import TruncatedText from 'component/common/truncated-text'; import TruncatedText from 'component/common/truncated-text';
import DateTime from 'component/dateTime'; import DateTime from 'component/dateTime';
import LivestreamDateTime from 'component/livestreamDateTime';
import ChannelThumbnail from 'component/channelThumbnail'; import ChannelThumbnail from 'component/channelThumbnail';
import FileViewCountInline from 'component/fileViewCountInline'; import FileViewCountInline from 'component/fileViewCountInline';
import SubscribeButton from 'component/subscribeButton'; import SubscribeButton from 'component/subscribeButton';
@ -19,8 +20,6 @@ import FileWatchLaterLink from 'component/fileWatchLaterLink';
import ClaimRepostAuthor from 'component/claimRepostAuthor'; import ClaimRepostAuthor from 'component/claimRepostAuthor';
import ClaimMenuList from 'component/claimMenuList'; import ClaimMenuList from 'component/claimMenuList';
import CollectionPreviewOverlay from 'component/collectionPreviewOverlay'; import CollectionPreviewOverlay from 'component/collectionPreviewOverlay';
import ClaimListDiscoverContext from 'component/claimListDiscover/context';
import moment from 'moment';
// $FlowFixMe cannot resolve ... // $FlowFixMe cannot resolve ...
import PlaceholderTx from 'static/img/placeholderTx.gif'; import PlaceholderTx from 'static/img/placeholderTx.gif';
@ -110,8 +109,6 @@ function ClaimPreviewTile(props: Props) {
} }
} }
const { listingType } = useContext(ClaimListDiscoverContext) || {};
const signingChannel = claim && claim.signing_channel; const signingChannel = claim && claim.signing_channel;
const isChannel = claim && claim.value_type === 'channel'; const isChannel = claim && claim.value_type === 'channel';
const channelUri = !isChannel ? signingChannel && signingChannel.permanent_url : claim && claim.permanent_url; const channelUri = !isChannel ? signingChannel && signingChannel.permanent_url : claim && claim.permanent_url;
@ -175,23 +172,6 @@ function ClaimPreviewTile(props: Props) {
liveProperty = (claim) => <>LIVE</>; liveProperty = (claim) => <>LIVE</>;
} }
const LivestreamDateTimeLabel = () => {
// If showing in upcoming and in the past. (we allow x time in past to show here if not live yet)
if (listingType === 'UPCOMING') {
// $FlowFixMe
if (moment.unix(claim.value.release_time).isBefore()) {
return __('Starting Soon');
}
} else {
// If not in upcoming + live and in the future (started streaming a bit early)
// $FlowFixMe
if (isLivestreamActive && moment.unix(claim.value.release_time).isAfter()) {
return __('Streaming Now');
}
}
return <DateTime timeAgo uri={uri} />;
};
return ( return (
<li <li
onClick={handleClick} onClick={handleClick}
@ -260,7 +240,7 @@ function ClaimPreviewTile(props: Props) {
<UriIndicator uri={uri} link /> <UriIndicator uri={uri} link />
<div className="claim-tile__about--counts"> <div className="claim-tile__about--counts">
<FileViewCountInline uri={uri} isLivestream={isLivestream} /> <FileViewCountInline uri={uri} isLivestream={isLivestream} />
{isLivestream && <LivestreamDateTimeLabel />} {isLivestream && <LivestreamDateTime uri={uri} />}
{!isLivestream && <DateTime timeAgo uri={uri} />} {!isLivestream && <DateTime timeAgo uri={uri} />}
</div> </div>
</div> </div>

View file

@ -4,6 +4,7 @@ import DateTime from 'component/dateTime';
import FileViewCount from 'component/fileViewCount'; import FileViewCount from 'component/fileViewCount';
import FileActions from 'component/fileActions'; import FileActions from 'component/fileActions';
import ClaimPreviewReset from 'component/claimPreviewReset'; import ClaimPreviewReset from 'component/claimPreviewReset';
import LivestreamDateTime from 'component/livestreamDateTime';
type Props = { type Props = {
uri: string, uri: string,
@ -13,13 +14,12 @@ type Props = {
function FileSubtitle(props: Props) { function FileSubtitle(props: Props) {
const { uri, livestream = false, isLive = false } = props; const { uri, livestream = false, isLive = false } = props;
const showDateTime = !livestream || (livestream && !isLive);
return ( return (
<> <>
<div className="media__subtitle--between"> <div className="media__subtitle--between">
<div className="file__viewdate"> <div className="file__viewdate">
{showDateTime && <DateTime uri={uri} show={DateTime.SHOW_DATE} />} {livestream && isLive && <LivestreamDateTime uri={uri} />}
{!livestream && <DateTime uri={uri} show={DateTime.SHOW_DATE} />}
<FileViewCount uri={uri} livestream={livestream} isLive={isLive} /> <FileViewCount uri={uri} livestream={livestream} isLive={isLive} />
</div> </div>

View file

@ -0,0 +1,14 @@
import { connect } from 'react-redux';
import { makeSelectClaimForUri } from 'redux/selectors/claims';
import LivestreamDateTime from './view';
import { selectActiveLivestreamForUri } from 'redux/selectors/livestream';
const select = (state, props) => {
const claim = props.uri && makeSelectClaimForUri(props.uri)(state);
return {
claim,
activeLivestream: selectActiveLivestreamForUri(state, props.uri),
};
};
export default connect(select)(LivestreamDateTime);

View file

@ -0,0 +1,37 @@
// @flow
import React from 'react';
import DateTime from 'component/dateTime';
import { LIVESTREAM_STARTED_RECENTLY_BUFFER } from 'constants/livestream';
import moment from 'moment';
type Props = {
uri: string,
claim: any,
activeLivestream: any,
};
const LivestreamDateTime = (props: Props) => {
const { uri, claim, activeLivestream } = props;
if (activeLivestream) {
return (
<span>
{__('Started')} <DateTime timeAgo date={activeLivestream.startedStreaming.toDate()} />
</span>
);
}
if (
moment
.unix(claim.value.release_time)
.isBetween(moment().subtract(LIVESTREAM_STARTED_RECENTLY_BUFFER, 'minutes'), moment())
) {
return __('Starting Soon');
}
return (
<span>
{__('Live')} <DateTime timeAgo uri={uri} />
</span>
);
};
export default LivestreamDateTime;

View file

@ -80,7 +80,12 @@ export default function LivestreamLayout(props: Props) {
</div> </div>
)} )}
{activeStreamUri && <LivestreamLink claimUri={activeStreamUri} />} {activeStreamUri && (
<LivestreamLink
title={__("Click here to access the stream that's currently active")}
claimUri={activeStreamUri}
/>
)}
<React.Suspense fallback={null}>{isMobile && !hideComments && <LivestreamComments uri={uri} />}</React.Suspense> <React.Suspense fallback={null}>{isMobile && !hideComments && <LivestreamComments uri={uri} />}</React.Suspense>

View file

@ -7,17 +7,18 @@ import { useHistory } from 'react-router';
import { formatLbryUrlForWeb } from 'util/url'; import { formatLbryUrlForWeb } from 'util/url';
type Props = { type Props = {
title?: string,
claimUri: string, claimUri: string,
}; };
export default function LivestreamLink(props: Props) { export default function LivestreamLink(props: Props) {
const { claimUri } = props; const { claimUri, title = null } = props;
const { push } = useHistory(); const { push } = useHistory();
const element = (props: { children: any }) => ( const element = (props: { children: any }) => (
<Card <Card
className="livestream__channel-link claim-preview__live" className="livestream__channel-link claim-preview__live"
title={__('Live stream in progress')} title={title || __('Live stream in progress')}
onClick={() => { onClick={() => {
push(formatLbryUrlForWeb(claimUri)); push(formatLbryUrlForWeb(claimUri));
}} }}

View file

@ -8,7 +8,6 @@ import { useIsMediumScreen, useIsLargeScreen } from 'effects/use-screensize';
import ClaimListDiscover from 'component/claimListDiscover'; import ClaimListDiscover from 'component/claimListDiscover';
import Button from 'component/button'; import Button from 'component/button';
import { LIVESTREAM_UPCOMING_BUFFER } from 'constants/livestream'; import { LIVESTREAM_UPCOMING_BUFFER } from 'constants/livestream';
import ClaimListDiscoverContext from 'component/claimListDiscover/context';
type Props = { type Props = {
channelIds: Array<string>, channelIds: Array<string>,
@ -39,7 +38,6 @@ const ScheduledStreams = (props: Props) => {
}; };
return ( return (
<ClaimListDiscoverContext.Provider value={{ listingType: 'UPCOMING' }}>
<div className={'mb-xl'} style={{ display: showUpcomingLivestreams ? 'block' : 'none' }}> <div className={'mb-xl'} style={{ display: showUpcomingLivestreams ? 'block' : 'none' }}>
<ClaimListDiscover <ClaimListDiscover
useSkeletonScreen={false} useSkeletonScreen={false}
@ -88,7 +86,6 @@ const ScheduledStreams = (props: Props) => {
</div> </div>
)} )}
</div> </div>
</ClaimListDiscoverContext.Provider>
); );
}; };

View file

@ -6,16 +6,20 @@ import { selectUserVerifiedEmail } from 'redux/selectors/user';
import { DISABLE_COMMENTS_TAG } from 'constants/tags'; import { DISABLE_COMMENTS_TAG } from 'constants/tags';
import { doCommentSocketConnect, doCommentSocketDisconnect } from 'redux/actions/websocket'; import { doCommentSocketConnect, doCommentSocketDisconnect } from 'redux/actions/websocket';
import { getChannelIdFromClaim } from 'util/claim'; import { getChannelIdFromClaim } from 'util/claim';
import { selectCurrentChannelStatus } from 'redux/selectors/livestream'; import { selectActiveLivestreamForChannel, selectActiveLivestreamInitialized } from 'redux/selectors/livestream';
import { doFetchActiveLivestream } from 'redux/actions/livestream'; import { doFetchActiveLivestream } from 'redux/actions/livestream';
import LivestreamPage from './view'; import LivestreamPage from './view';
const select = (state, props) => ({ const select = (state, props) => {
const channelClaimId = getChannelIdFromClaim(selectClaimForUri(state, props.uri));
return {
isAuthenticated: selectUserVerifiedEmail(state), isAuthenticated: selectUserVerifiedEmail(state),
channelClaimId: getChannelIdFromClaim(selectClaimForUri(state, props.uri)), channelClaimId,
chatDisabled: makeSelectTagInClaimOrChannelForUri(props.uri, DISABLE_COMMENTS_TAG)(state), chatDisabled: makeSelectTagInClaimOrChannelForUri(props.uri, DISABLE_COMMENTS_TAG)(state),
currentChannelStatus: selectCurrentChannelStatus(state), activeLivestreamForChannel: selectActiveLivestreamForChannel(state, channelClaimId),
}); activeLivestreamInitialized: selectActiveLivestreamInitialized(state),
};
};
const perform = { const perform = {
doSetPlayingUri, doSetPlayingUri,

View file

@ -20,7 +20,8 @@ type Props = {
doCommentSocketConnect: (string, string) => void, doCommentSocketConnect: (string, string) => void,
doCommentSocketDisconnect: (string) => void, doCommentSocketDisconnect: (string) => void,
doFetchActiveLivestream: (string) => void, doFetchActiveLivestream: (string) => void,
currentChannelStatus: LivestreamChannelStatus, activeLivestreamForChannel: any,
activeLivestreamInitialized: boolean,
}; };
export default function LivestreamPage(props: Props) { export default function LivestreamPage(props: Props) {
@ -35,7 +36,8 @@ export default function LivestreamPage(props: Props) {
doCommentSocketConnect, doCommentSocketConnect,
doCommentSocketDisconnect, doCommentSocketDisconnect,
doFetchActiveLivestream, doFetchActiveLivestream,
currentChannelStatus, activeLivestreamForChannel,
activeLivestreamInitialized,
} = props; } = props;
React.useEffect(() => { React.useEffect(() => {
@ -58,10 +60,9 @@ export default function LivestreamPage(props: Props) {
}; };
}, [claimId, uri, doCommentSocketConnect, doCommentSocketDisconnect]); }, [claimId, uri, doCommentSocketConnect, doCommentSocketDisconnect]);
const [isInitialized, setIsInitialized] = React.useState(false); const isInitialized = Boolean(activeLivestreamForChannel) || activeLivestreamInitialized;
const [isChannelBroadcasting, setIsChannelBroadcasting] = React.useState(false); const isChannelBroadcasting = Boolean(activeLivestreamForChannel);
const [isCurrentClaimLive, setIsCurrentClaimLive] = React.useState(false); const isCurrentClaimLive = isChannelBroadcasting && activeLivestreamForChannel.claimId === claimId;
const livestreamChannelId = channelClaimId || ''; const livestreamChannelId = channelClaimId || '';
// Find out current channels status + active live claim. // Find out current channels status + active live claim.
@ -71,19 +72,10 @@ export default function LivestreamPage(props: Props) {
return () => clearInterval(intervalId); return () => clearInterval(intervalId);
}, [livestreamChannelId, doFetchActiveLivestream]); }, [livestreamChannelId, doFetchActiveLivestream]);
React.useEffect(() => {
const initialized = currentChannelStatus.channelId === livestreamChannelId;
setIsInitialized(initialized);
if (initialized) {
setIsChannelBroadcasting(currentChannelStatus.isBroadcasting);
setIsCurrentClaimLive(currentChannelStatus.liveClaim.claimId === claimId);
}
}, [currentChannelStatus, livestreamChannelId, claimId]);
const [activeStreamUri, setActiveStreamUri] = React.useState(false); const [activeStreamUri, setActiveStreamUri] = React.useState(false);
React.useEffect(() => { React.useEffect(() => {
setActiveStreamUri(!isCurrentClaimLive && isChannelBroadcasting ? currentChannelStatus.liveClaim.claimUri : false); setActiveStreamUri(!isCurrentClaimLive && isChannelBroadcasting ? activeLivestreamForChannel.claimUri : false);
}, [isCurrentClaimLive, isChannelBroadcasting]); // eslint-disable-line react-hooks/exhaustive-deps }, [isCurrentClaimLive, isChannelBroadcasting]); // eslint-disable-line react-hooks/exhaustive-deps
// $FlowFixMe // $FlowFixMe

View file

@ -165,14 +165,18 @@ export const doFetchActiveLivestream = (channelId: string) => {
const liveChannel = await fetchLiveChannel(channelId); const liveChannel = await fetchLiveChannel(channelId);
const currentlyLiveClaims = await findActiveStreams([channelId], ['release_time'], liveChannel, dispatch); const currentlyLiveClaims = await findActiveStreams([channelId], ['release_time'], liveChannel, dispatch);
const liveClaim = currentlyLiveClaims[channelId]; const liveClaim = currentlyLiveClaims[channelId];
liveChannel[channelId].claimId = liveClaim.stream.claim_id;
liveChannel[channelId].claimUri = liveClaim.stream.canonical_url;
dispatch({ dispatch({
type: ACTIONS.FETCH_ACTIVE_LIVESTREAM_COMPLETED, type: ACTIONS.FETCH_ACTIVE_LIVESTREAM_COMPLETED,
data: { liveClaim: { claimId: liveClaim.stream.claim_id, claimUri: liveClaim.stream.canonical_url } }, data: {
...liveChannel,
},
}); });
} catch (err) { } catch (err) {
dispatch({ type: ACTIONS.FETCH_ACTIVE_LIVESTREAM_FAILED }); dispatch({ type: ACTIONS.FETCH_ACTIVE_LIVESTREAM_FAILED, data: { channelId } });
} finally {
dispatch({ type: ACTIONS.FETCH_ACTIVE_LIVESTREAM_FINISHED, data: { channelId } });
} }
}; };
}; };

View file

@ -3,15 +3,6 @@
import * as ACTIONS from 'constants/action_types'; import * as ACTIONS from 'constants/action_types';
import { handleActions } from 'util/redux-utils'; import { handleActions } from 'util/redux-utils';
const currentChannelStatus: LivestreamChannelStatus = {
channelId: null,
isBroadcasting: false,
liveClaim: {
claimId: null,
claimUri: null,
},
};
const defaultState: LivestreamState = { const defaultState: LivestreamState = {
fetchingById: {}, fetchingById: {},
viewersById: {}, viewersById: {},
@ -19,10 +10,7 @@ const defaultState: LivestreamState = {
activeLivestreams: null, activeLivestreams: null,
activeLivestreamsLastFetchedDate: 0, activeLivestreamsLastFetchedDate: 0,
activeLivestreamsLastFetchedOptions: {}, activeLivestreamsLastFetchedOptions: {},
activeLivestreamInitialized: false,
currentChannelStatus: {
...currentChannelStatus,
},
}; };
export default handleActions( export default handleActions(
@ -70,24 +58,14 @@ export default handleActions(
activeLivestreamsLastFetchedOptions, activeLivestreamsLastFetchedOptions,
}; };
}, },
[ACTIONS.FETCH_ACTIVE_LIVESTREAM_COMPLETED]: (state: LivestreamState, action: any) => { [ACTIONS.FETCH_ACTIVE_LIVESTREAM_COMPLETED]: (state: LivestreamState, action: any) => {
const currentChannelStatus = Object.assign({}, state.currentChannelStatus, { const activeLivestreams = Object.assign({}, state.activeLivestreams || {}, action.data);
isBroadcasting: true, return { ...state, activeLivestreams, activeLivestreamInitialized: true };
liveClaim: action.data.liveClaim,
});
return { ...state, currentChannelStatus };
}, },
[ACTIONS.FETCH_ACTIVE_LIVESTREAM_FAILED]: (state: LivestreamState) => { [ACTIONS.FETCH_ACTIVE_LIVESTREAM_FAILED]: (state: LivestreamState, action: any) => {
const currentChannelStatus = Object.assign({}, state.currentChannelStatus, { const activeLivestreams = state.activeLivestreams;
isBroadcasting: false, if (activeLivestreams) delete activeLivestreams[action.data.channelId];
liveClaim: { claimId: null, claimUri: null }, return { ...state, activeLivestreams: Object.assign({}, activeLivestreams), activeLivestreamInitialized: true };
});
return { ...state, currentChannelStatus };
},
[ACTIONS.FETCH_ACTIVE_LIVESTREAM_FINISHED]: (state: LivestreamState, action: any) => {
const currentChannelStatus = Object.assign({}, state.currentChannelStatus, { channelId: action.data.channelId });
return { ...state, currentChannelStatus };
}, },
}, },
defaultState defaultState

View file

@ -62,6 +62,31 @@ export const selectIsActiveLivestreamForUri = createCachedSelector(
} }
)((state, uri) => String(uri)); )((state, uri) => String(uri));
export const selectActiveLivestreamForUri = createCachedSelector(
(state, uri) => uri,
selectActiveLivestreams,
(uri, activeLivestreams) => {
if (!uri || !activeLivestreams) {
return null;
}
const activeLivestreamValues = Object.values(activeLivestreams);
// $FlowFixMe - unable to resolve claimUri
return activeLivestreamValues.find((v) => v.claimUri === uri) || null;
}
)((state, uri) => String(uri));
export const selectActiveLivestreamForChannel = createCachedSelector(
(state, channelId) => channelId,
selectActiveLivestreams,
(channelId, activeLivestreams) => {
if (!channelId || !activeLivestreams) {
return null;
}
return activeLivestreams[channelId] || null;
}
)((state, channelId) => String(channelId));
export const selectFetchingActiveLivestreams = (state: State) => selectState(state).fetchingActiveLivestreams; export const selectFetchingActiveLivestreams = (state: State) => selectState(state).fetchingActiveLivestreams;
export const selectCurrentChannelStatus = (state: State) => selectState(state).currentChannelStatus; export const selectActiveLivestreamInitialized = (state: State) => selectState(state).activeLivestreamInitialized;