fix isLivestream

channelContent - live

only poll streaming if has livestream claim

check channel for livestream claims every minute

update consts

poll livestream claims on setup page

do not poll livestream claims if live

smoother loading

unmerged redux bump
This commit is contained in:
zeppi 2021-03-25 20:46:15 -04:00
parent 2e87b2fd22
commit f092e8cb7b
10 changed files with 203 additions and 150 deletions

View file

@ -142,7 +142,7 @@
"imagesloaded": "^4.1.4",
"json-loader": "^0.5.4",
"lbry-format": "https://github.com/lbryio/lbry-format.git",
"lbry-redux": "lbryio/lbry-redux#c494c92505cd531048de20bc37dc4d192b6ace01",
"lbry-redux": "lbryio/lbry-redux#db27091f5add9a6bb91c38471a369fca144fc96f",
"lbryinc": "lbryio/lbryinc#7faea40d87b78ec91b901c62f501499dc4737025",
"lint-staged": "^7.0.2",
"localforage": "^1.7.1",

View file

@ -249,106 +249,110 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
'claim-preview__wrapper--small': type === 'small',
})}
>
{!hideRepostLabel && <ClaimRepostAuthor uri={uri} />}
<>
{!hideRepostLabel && <ClaimRepostAuthor uri={uri} />}
<div
className={classnames('claim-preview', {
'claim-preview--small': type === 'small' || type === 'tooltip',
'claim-preview--large': type === 'large',
'claim-preview--inline': type === 'inline',
'claim-preview--tooltip': type === 'tooltip',
'claim-preview--channel': isChannelUri,
'claim-preview--visited': !isChannelUri && !claimIsMine && hasVisitedUri,
'claim-preview--pending': pending,
})}
>
{isChannelUri && claim ? (
<UriIndicator uri={contentUri} link>
<ChannelThumbnail uri={contentUri} />
</UriIndicator>
) : (
<>
{!pending ? (
<NavLink {...navLinkProps}>
<FileThumbnail thumbnail={thumbnailUrl}>
{/* @if TARGET='app' */}
{claim && (
<div className="claim-preview__hover-actions">
<FileDownloadLink uri={canonicalUrl} hideOpenButton hideDownloadStatus />
</div>
)}
{/* @endif */}
{!isRepost && !isChannelUri && !isLivestream && (
<div className="claim-preview__file-property-overlay">
<FileProperties uri={contentUri} small />
</div>
)}
</FileThumbnail>
</NavLink>
) : (
<FileThumbnail thumbnail={thumbnailUrl} />
)}
</>
)}
<div className="claim-preview__text">
<div className="claim-preview-metadata">
<div className="claim-preview-info">
{pending ? (
<ClaimPreviewTitle uri={contentUri} />
) : (
<div
className={classnames('claim-preview', {
'claim-preview--small': type === 'small' || type === 'tooltip',
'claim-preview--large': type === 'large',
'claim-preview--inline': type === 'inline',
'claim-preview--tooltip': type === 'tooltip',
'claim-preview--channel': isChannelUri,
'claim-preview--visited': !isChannelUri && !claimIsMine && hasVisitedUri,
'claim-preview--pending': pending,
})}
>
{isChannelUri && claim ? (
<UriIndicator uri={contentUri} link>
<ChannelThumbnail uri={contentUri} />
</UriIndicator>
) : (
<>
{!pending ? (
<NavLink {...navLinkProps}>
<ClaimPreviewTitle uri={uri} />
<FileThumbnail thumbnail={thumbnailUrl}>
{/* @if TARGET='app' */}
{claim && (
<div className="claim-preview__hover-actions">
<FileDownloadLink uri={canonicalUrl} hideOpenButton hideDownloadStatus />
</div>
)}
{/* @endif */}
{!isRepost && !isChannelUri && !isLivestream && (
<div className="claim-preview__file-property-overlay">
<FileProperties uri={contentUri} small />
</div>
)}
</FileThumbnail>
</NavLink>
) : (
<FileThumbnail thumbnail={thumbnailUrl} />
)}
</>
)}
{/* {type !== 'small' && !isChannelUri && signingChannel && SIMPLE_SITE && (
<div className="claim-preview__text">
<div className="claim-preview-metadata">
<div className="claim-preview-info">
{pending ? (
<ClaimPreviewTitle uri={contentUri} />
) : (
<NavLink {...navLinkProps}>
<ClaimPreviewTitle uri={uri} />
</NavLink>
)}
{/* {type !== 'small' && !isChannelUri && signingChannel && SIMPLE_SITE && (
<ChannelThumbnail uri={signingChannel.permanent_url} />
)} */}
</div>
<ClaimPreviewSubtitle uri={uri} type={type} />
{(pending || !!reflectingProgress) && <PublishPending uri={uri} />}
</div>
<ClaimPreviewSubtitle uri={uri} type={type} />
{(pending || !!reflectingProgress) && <PublishPending uri={uri} />}
{type !== 'small' && (
<div className="claim-preview__actions">
{!pending && (
<>
{renderActions && claim && renderActions(claim)}
{shouldHideActions || renderActions ? null : actions !== undefined ? (
actions
) : (
<div className="claim-preview__primary-actions">
{!isChannelUri && signingChannel && (
<div className="claim-preview__channel-staked">
<ChannelThumbnail uri={signingChannel.permanent_url} />
</div>
)}
{isChannelUri && !channelIsBlocked && !claimIsMine && (
<SubscribeButton
uri={contentUri.startsWith('lbry://') ? contentUri : `lbry://${contentUri}`}
/>
)}
{includeSupportAction && <ClaimSupportButton uri={uri} />}
</div>
)}
</>
)}
{claim && (
<React.Fragment>
{typeof properties === 'function' ? (
properties(claim)
) : properties !== undefined ? (
properties
) : (
<ClaimTags uri={uri} type={type} />
)}
</React.Fragment>
)}
</div>
)}
</div>
{type !== 'small' && (
<div className="claim-preview__actions">
{!pending && (
<>
{renderActions && claim && renderActions(claim)}
{shouldHideActions || renderActions ? null : actions !== undefined ? (
actions
) : (
<div className="claim-preview__primary-actions">
{!isChannelUri && signingChannel && (
<div className="claim-preview__channel-staked">
<ChannelThumbnail uri={signingChannel.permanent_url} />
</div>
)}
{isChannelUri && !channelIsBlocked && !claimIsMine && (
<SubscribeButton uri={contentUri.startsWith('lbry://') ? contentUri : `lbry://${contentUri}`} />
)}
{includeSupportAction && <ClaimSupportButton uri={uri} />}
</div>
)}
</>
)}
{claim && (
<React.Fragment>
{typeof properties === 'function' ? (
properties(claim)
) : properties !== undefined ? (
properties
) : (
<ClaimTags uri={uri} type={type} />
)}
</React.Fragment>
)}
</div>
)}
</div>
</div>
{!hideMenu && <ClaimMenuList uri={uri} />}
{!hideMenu && <ClaimMenuList uri={uri} />}
</>
</WrapperElement>
);
});

View file

@ -8,7 +8,7 @@ import {
doFileGet,
makeSelectChannelForClaimUri,
makeSelectClaimIsNsfw,
makeSelectClaimHasSource,
makeSelectClaimIsStreamPlaceholder,
} from 'lbry-redux';
import { selectMutedChannels } from 'redux/selectors/blocked';
import { selectBlackListedOutpoints, selectFilteredOutpoints } from 'lbryinc';
@ -26,7 +26,7 @@ const select = (state, props) => ({
blockedChannelUris: selectMutedChannels(state),
showMature: selectShowMatureContent(state),
isMature: makeSelectClaimIsNsfw(props.uri)(state),
isLivestream: !makeSelectClaimHasSource(props.uri)(state),
isLivestream: makeSelectClaimIsStreamPlaceholder(props.uri)(state),
});
const perform = (dispatch) => ({

View file

@ -21,6 +21,7 @@ type Props = {
nag?: Node,
smallTitle?: boolean,
onClick?: () => void,
children?: any, // not sure how this works
};
export default function Card(props: Props) {
@ -39,6 +40,7 @@ export default function Card(props: Props) {
defaultExpand,
nag,
onClick,
children,
} = props;
const [expanded, setExpanded] = useState(defaultExpand);
const expandable = defaultExpand !== undefined;
@ -102,6 +104,7 @@ export default function Card(props: Props) {
</div>
)}
{actions && <div className="card__main-actions">{actions}</div>}
{children && <div className="card__main-actions">{children}</div>}
</>
)}

View file

@ -64,11 +64,12 @@ export default function LivestreamLink(props: Props) {
return null;
}
return (
<Card
className="livestream__channel-link"
title={__('Live stream in progress')}
actions={<ClaimPreview uri={livestreamClaim.canonical_url} livestream type="inline" />}
/>
// gonna pass the wrapper in so I don't have to rewrite the dmca/blocking logic in claimPreview.
const element = (props: { children: any }) => (
<Card className="livestream__channel-link" title={__('Live stream in progress')}>
{props.children}
</Card>
);
return <ClaimPreview uri={livestreamClaim.canonical_url} wrapperElement={element} type="inline" />;
}

View file

@ -49,7 +49,6 @@ import BuyPage from 'page/buy';
import NotificationsPage from 'page/notifications';
import SignInWalletPasswordPage from 'page/signInWalletPassword';
import YoutubeSyncPage from 'page/youtubeSync';
import LiveStreamPage from 'page/livestream';
import { LINKED_COMMENT_QUERY_PARAM } from 'constants/comment';
import { parseURI, isURIValid } from 'lbry-redux';
@ -286,8 +285,6 @@ function AppRouter(props: Props) {
<Route path={`/$/${PAGES.EMBED}/:claimName`} exact component={EmbedWrapperPage} />
<Route path={`/$/${PAGES.EMBED}/:claimName/:claimId`} exact component={EmbedWrapperPage} />
<Route path={`/$/${PAGES.LIVESTREAM}`} component={LiveStreamPage} />
{/* Below need to go at the end to make sure we don't match any of our pages first */}
<Route path="/:claimName" exact component={ShowPage} />
<Route path="/:claimName/:streamName" exact component={ShowPage} />

View file

@ -9,7 +9,7 @@ import {
SETTINGS,
makeSelectTagInClaimOrChannelForUri,
makeSelectClaimIsMine,
makeSelectClaimHasSource,
makeSelectClaimIsStreamPlaceholder,
} from 'lbry-redux';
import { makeSelectCostInfoForUri, doFetchCostInfoForUri } from 'lbryinc';
import { selectShowMatureContent, makeSelectClientSetting } from 'redux/selectors/settings';
@ -35,7 +35,7 @@ const select = (state, props) => {
videoTheaterMode: makeSelectClientSetting(SETTINGS.VIDEO_THEATER_MODE)(state),
commentsDisabled: makeSelectTagInClaimOrChannelForUri(props.uri, DISABLE_COMMENTS_TAG)(state),
claimIsMine: makeSelectClaimIsMine(props.uri)(state),
isLivestream: !makeSelectClaimHasSource(props.uri)(state),
isLivestream: makeSelectClaimIsStreamPlaceholder(props.uri)(state),
};
};

View file

@ -4,6 +4,7 @@ import React from 'react';
import Page from 'component/page';
import LivestreamLayout from 'component/livestreamLayout';
import analytics from 'analytics';
import { Lbry } from 'lbry-redux';
type Props = {
uri: string,
@ -19,8 +20,41 @@ export default function LivestreamPage(props: Props) {
const [activeViewers, setActiveViewers] = React.useState(0);
const [isLive, setIsLive] = React.useState(false);
const livestreamChannelId = channelClaim && channelClaim.signing_channel && channelClaim.signing_channel.claim_id;
const [hasLivestreamClaim, setHasLivestreamClaim] = React.useState(false);
const STREAMING_POLL_INTERVAL_IN_MS = 10000;
const LIVESTREAM_CLAIM_POLL_IN_MS = 60000;
// the component needs to check if the channel has published a new livestream, so we know if it should check
React.useEffect(() => {
let checkClaimsInterval;
function checkHasLivestreamClaim() {
Lbry.claim_search({
channel_ids: [livestreamChannelId],
has_no_source: true,
claim_type: ['stream'],
})
.then((res) => {
if (res && res.items && res.items.length > 0) {
setHasLivestreamClaim(true);
}
})
.catch(() => {});
}
if (livestreamChannelId && !isLive) {
if (!checkClaimsInterval) checkHasLivestreamClaim();
checkClaimsInterval = setInterval(checkHasLivestreamClaim, LIVESTREAM_CLAIM_POLL_IN_MS);
return () => {
if (checkClaimsInterval) {
clearInterval(checkClaimsInterval);
}
};
}
}, [livestreamChannelId, isLive]);
React.useEffect(() => {
let interval;
function checkIsLive() {
// $FlowFixMe Bitwave's API can handle garbage
fetch(`${BITWAVE_API}/${livestreamChannelId}`)
@ -38,19 +72,17 @@ export default function LivestreamPage(props: Props) {
}
});
}
let interval;
if (livestreamChannelId) {
if (livestreamChannelId && hasLivestreamClaim) {
if (!interval) checkIsLive();
interval = setInterval(checkIsLive, 10 * 1000);
}
interval = setInterval(checkIsLive, STREAMING_POLL_INTERVAL_IN_MS);
return () => {
if (interval) {
clearInterval(interval);
}
};
}, [livestreamChannelId]);
return () => {
if (interval) {
clearInterval(interval);
}
};
}
}, [livestreamChannelId, hasLivestreamClaim]);
const stringifiedClaim = JSON.stringify(claim);
React.useEffect(() => {

View file

@ -24,16 +24,34 @@ type Props = {
};
export default function LivestreamSetupPage(props: Props) {
const LIVESTREAM_CLAIM_POLL_IN_MS = 60000;
const { channels, fetchingChannels, activeChannelClaim, pendingClaims } = props;
const [sigData, setSigData] = React.useState({ signature: undefined, signing_ts: undefined });
const [showHelpTest, setShowHelpTest] = usePersistedState('livestream-help-seen', true);
const [spin, setSpin] = React.useState(true);
const [livestreamClaims, setLivestreamClaims] = React.useState([]);
const hasChannels = channels && channels.length > 0;
const activeChannelClaimStr = JSON.stringify(activeChannelClaim);
const streamKey = createStreamKey();
function createStreamKey() {
if (!activeChannelClaim || !sigData.signature || !sigData.signing_ts) return null;
return `${activeChannelClaim.claim_id}?d=${toHex(activeChannelClaim.name)}&s=${sigData.signature}&t=${
sigData.signing_ts
}`;
}
const streamKey = createStreamKey();
const pendingLiveStreamClaims = pendingClaims
? pendingClaims.filter(
(claim) =>
// $FlowFixMe
claim.value_type === 'stream' && !(claim.value && claim.value.source)
)
: [];
const pendingLength = pendingLiveStreamClaims.length;
const totalLivestreamClaims = pendingLiveStreamClaims.concat(livestreamClaims);
const helpText = (
<div className="section__subtitle">
<p>
@ -76,6 +94,7 @@ export default function LivestreamSetupPage(props: Props) {
<p>{__(`Click Save and you are done!`)}</p>
</div>
);
React.useEffect(() => {
if (activeChannelClaimStr) {
const channelClaim = JSON.parse(activeChannelClaimStr);
@ -96,47 +115,44 @@ export default function LivestreamSetupPage(props: Props) {
}
}, [activeChannelClaimStr, setSigData]);
function createStreamKey() {
if (!activeChannelClaim || !sigData.signature || !sigData.signing_ts) return null;
return `${activeChannelClaim.claim_id}?d=${toHex(activeChannelClaim.name)}&s=${sigData.signature}&t=${
sigData.signing_ts
}`;
}
const [livestreamClaims, setLivestreamClaims] = React.useState([]);
const pendingLiveStreamClaims =
// $FlowFixMe
pendingClaims ? pendingClaims.filter((claim) => !(claim && claim.value && claim.value.source)) : [];
const pendingLength = pendingLiveStreamClaims.length;
const totalLivestreamClaims = pendingLiveStreamClaims.concat(livestreamClaims);
React.useEffect(() => {
let checkClaimsInterval;
if (!activeChannelClaimStr) return;
const channelClaim = JSON.parse(activeChannelClaimStr);
Lbry.claim_search({
channel_ids: [channelClaim.claim_id],
has_no_source: true,
claim_type: ['stream'],
})
.then((res) => {
if (res && res.items && res.items.length > 0) {
setLivestreamClaims(res.items.reverse());
} else {
setLivestreamClaims([]);
}
setSpin(false);
function checkLivestreamClaims() {
Lbry.claim_search({
channel_ids: [channelClaim.claim_id],
has_no_source: true,
claim_type: ['stream'],
})
.catch(() => {
setLivestreamClaims([]);
setSpin(false);
});
.then((res) => {
if (res && res.items && res.items.length > 0) {
setLivestreamClaims(res.items.reverse());
} else {
setLivestreamClaims([]);
}
setSpin(false);
})
.catch(() => {
setLivestreamClaims([]);
setSpin(false);
});
}
if (!checkClaimsInterval) {
checkLivestreamClaims();
checkClaimsInterval = setInterval(checkLivestreamClaims, LIVESTREAM_CLAIM_POLL_IN_MS);
}
return () => {
if (checkClaimsInterval) {
clearInterval(checkClaimsInterval);
}
};
}, [activeChannelClaimStr, pendingLength, setSpin]);
return (
<Page>
{fetchingChannels && (
{(fetchingChannels || spin) && (
<div className="main--empty">
<Spinner delayed />
</div>

View file

@ -6950,9 +6950,9 @@ lazy-val@^1.0.4:
yargs "^13.2.2"
zstd-codec "^0.1.1"
lbry-redux@lbryio/lbry-redux#c494c92505cd531048de20bc37dc4d192b6ace01:
lbry-redux@lbryio/lbry-redux#db27091f5add9a6bb91c38471a369fca144fc96f:
version "0.0.1"
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/c494c92505cd531048de20bc37dc4d192b6ace01"
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/db27091f5add9a6bb91c38471a369fca144fc96f"
dependencies:
proxy-polyfill "0.1.6"
reselect "^3.0.0"