Add stream start to active live streams, and refactor how data active claim is stored in redux
This commit is contained in:
parent
72c1a8ae25
commit
d382671616
18 changed files with 192 additions and 203 deletions
14
flow-typed/livestream.js
vendored
14
flow-typed/livestream.js
vendored
|
@ -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 = {
|
||||||
|
|
|
@ -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),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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] : []
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
import { createContext } from 'react';
|
|
||||||
|
|
||||||
const ClaimListDiscoverContext = createContext();
|
|
||||||
|
|
||||||
export default ClaimListDiscoverContext;
|
|
|
@ -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),
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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} />
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
14
ui/component/livestreamDateTime/index.js
Normal file
14
ui/component/livestreamDateTime/index.js
Normal 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);
|
37
ui/component/livestreamDateTime/view.jsx
Normal file
37
ui/component/livestreamDateTime/view.jsx
Normal 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;
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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));
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -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>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 } });
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue