Improve livestream claimLink embeds
- Remove embedPlayButton for fileRenderInitiator - getThumbnailFromClaim from utils function instead of redux - Improve playingUri
This commit is contained in:
parent
6dea79819d
commit
b096aad70e
35 changed files with 278 additions and 247 deletions
|
@ -197,7 +197,7 @@ const recsys = {
|
||||||
onPlayerDispose: function (claimId, isEmbedded) {
|
onPlayerDispose: function (claimId, isEmbedded) {
|
||||||
if (window && window.store) {
|
if (window && window.store) {
|
||||||
const state = window.store.getState();
|
const state = window.store.getState();
|
||||||
const playingUri = selectPlayingUri(state);
|
const { uri: playingUri } = selectPlayingUri(state);
|
||||||
const primaryUri = selectPrimaryUri(state);
|
const primaryUri = selectPrimaryUri(state);
|
||||||
const onFilePage = playingUri === primaryUri;
|
const onFilePage = playingUri === primaryUri;
|
||||||
if (!onFilePage || isEmbedded) {
|
if (!onFilePage || isEmbedded) {
|
||||||
|
@ -246,9 +246,8 @@ const recsys = {
|
||||||
onNavigate: function () {
|
onNavigate: function () {
|
||||||
if (window && window.store) {
|
if (window && window.store) {
|
||||||
const state = window.store.getState();
|
const state = window.store.getState();
|
||||||
const playingUri = selectPlayingUri(state);
|
const { uri: playingUri } = selectPlayingUri(state);
|
||||||
const actualPlayingUri = playingUri && playingUri.uri;
|
const claim = makeSelectClaimForUri(playingUri)(state);
|
||||||
const claim = makeSelectClaimForUri(actualPlayingUri)(state);
|
|
||||||
const playingClaimId = claim ? claim.claim_id : null;
|
const playingClaimId = claim ? claim.claim_id : null;
|
||||||
// const primaryUri = selectPrimaryUri(state);
|
// const primaryUri = selectPrimaryUri(state);
|
||||||
const floatingPlayer = selectClientSetting(state, SETTINGS.FLOATING_PLAYER);
|
const floatingPlayer = selectClientSetting(state, SETTINGS.FLOATING_PLAYER);
|
||||||
|
|
7
flow-typed/content.js
vendored
7
flow-typed/content.js
vendored
|
@ -1,9 +1,10 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
declare type PlayingUri = {
|
declare type PlayingUri = {
|
||||||
uri: string,
|
uri?: ?string,
|
||||||
primaryUri: string,
|
primaryUri?: string,
|
||||||
pathname: string,
|
pathname?: string,
|
||||||
commentId?: string,
|
commentId?: string,
|
||||||
|
collectionId?: ?string,
|
||||||
source?: string,
|
source?: string,
|
||||||
};
|
};
|
||||||
|
|
|
@ -94,6 +94,8 @@ type Props = {
|
||||||
fetchModAmIList: () => void,
|
fetchModAmIList: () => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const SocketContext = React.createContext<any>();
|
||||||
|
|
||||||
function App(props: Props) {
|
function App(props: Props) {
|
||||||
const {
|
const {
|
||||||
theme,
|
theme,
|
||||||
|
@ -137,6 +139,8 @@ function App(props: Props) {
|
||||||
const previousHasVerifiedEmail = usePrevious(hasVerifiedEmail);
|
const previousHasVerifiedEmail = usePrevious(hasVerifiedEmail);
|
||||||
const previousRewardApproved = usePrevious(isRewardApproved);
|
const previousRewardApproved = usePrevious(isRewardApproved);
|
||||||
|
|
||||||
|
const [socketConnected, setSocketConnection] = React.useState(false);
|
||||||
|
|
||||||
const [gdprRequired, setGdprRequired] = usePersistedState('gdprRequired');
|
const [gdprRequired, setGdprRequired] = usePersistedState('gdprRequired');
|
||||||
const [localeLangs, setLocaleLangs] = React.useState();
|
const [localeLangs, setLocaleLangs] = React.useState();
|
||||||
const [localeSwitchDismissed] = usePersistedState('locale-switch-dismissed', false);
|
const [localeSwitchDismissed] = usePersistedState('locale-switch-dismissed', false);
|
||||||
|
@ -563,10 +567,14 @@ function App(props: Props) {
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
|
<SocketContext.Provider value={socketConnected}>
|
||||||
<Router />
|
<Router />
|
||||||
|
</SocketContext.Provider>
|
||||||
<ModalRouter />
|
<ModalRouter />
|
||||||
<React.Suspense fallback={null}>{renderFiledrop && <FileDrop />}</React.Suspense>
|
<React.Suspense fallback={null}>{renderFiledrop && <FileDrop />}</React.Suspense>
|
||||||
<FileRenderFloating />
|
|
||||||
|
<FileRenderFloating setSocketConnection={setSocketConnection} />
|
||||||
|
|
||||||
<React.Suspense fallback={null}>
|
<React.Suspense fallback={null}>
|
||||||
{isEnhancedLayout && <Yrbl className="yrbl--enhanced" />}
|
{isEnhancedLayout && <Yrbl className="yrbl--enhanced" />}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { makeSelectClaimForUri, selectIsUriResolving } from 'redux/selectors/claims';
|
import { selectClaimForUri, selectIsUriResolving } from 'redux/selectors/claims';
|
||||||
import { doResolveUri } from 'redux/actions/claims';
|
import { doResolveUri } from 'redux/actions/claims';
|
||||||
import { doSetPlayingUri } from 'redux/actions/content';
|
import { doSetPlayingUri } from 'redux/actions/content';
|
||||||
import { punctuationMarks } from 'util/remark-lbry';
|
import { punctuationMarks } from 'util/remark-lbry';
|
||||||
|
@ -17,7 +17,7 @@ const select = (state, props) => {
|
||||||
function getValidClaim(testUri) {
|
function getValidClaim(testUri) {
|
||||||
if (testUri.replace('lbry://', '').length <= 1) return;
|
if (testUri.replace('lbry://', '').length <= 1) return;
|
||||||
|
|
||||||
claim = makeSelectClaimForUri(testUri)(state);
|
claim = selectClaimForUri(state, testUri);
|
||||||
if (claim === null && punctuationMarks.includes(testUri.charAt(testUri.length - 1))) {
|
if (claim === null && punctuationMarks.includes(testUri.charAt(testUri.length - 1))) {
|
||||||
getValidClaim(testUri.substring(0, testUri.length - 1));
|
getValidClaim(testUri.substring(0, testUri.length - 1));
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { INLINE_PLAYER_WRAPPER_CLASS } from 'component/fileRenderFloating/view';
|
import { INLINE_PLAYER_WRAPPER_CLASS } from 'component/fileRenderFloating/view';
|
||||||
import { SIMPLE_SITE } from 'config';
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import EmbedPlayButton from 'component/embedPlayButton';
|
import FileRenderInitiator from 'component/fileRenderInitiator';
|
||||||
import UriIndicator from 'component/uriIndicator';
|
import UriIndicator from 'component/uriIndicator';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -15,7 +14,7 @@ type Props = {
|
||||||
description: ?string,
|
description: ?string,
|
||||||
isResolvingUri: boolean,
|
isResolvingUri: boolean,
|
||||||
doResolveUri: (string, boolean) => void,
|
doResolveUri: (string, boolean) => void,
|
||||||
playingUri: ?PlayingUri,
|
playingUri: PlayingUri,
|
||||||
parentCommentId?: string,
|
parentCommentId?: string,
|
||||||
isMarkdownPost?: boolean,
|
isMarkdownPost?: boolean,
|
||||||
allowPreview: boolean,
|
allowPreview: boolean,
|
||||||
|
@ -61,7 +60,6 @@ class ClaimLink extends React.Component<Props> {
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const isUnresolved = (!isResolvingUri && !claim) || !claim;
|
const isUnresolved = (!isResolvingUri && !claim) || !claim;
|
||||||
const isPlayingInline =
|
const isPlayingInline =
|
||||||
playingUri &&
|
|
||||||
playingUri.uri === uri &&
|
playingUri.uri === uri &&
|
||||||
((playingUri.source === 'comment' && parentCommentId === playingUri.commentId) ||
|
((playingUri.source === 'comment' && parentCommentId === playingUri.commentId) ||
|
||||||
playingUri.source === 'markdown');
|
playingUri.source === 'markdown');
|
||||||
|
@ -73,26 +71,30 @@ class ClaimLink extends React.Component<Props> {
|
||||||
const { value_type: valueType } = claim;
|
const { value_type: valueType } = claim;
|
||||||
const isChannel = valueType === 'channel';
|
const isChannel = valueType === 'channel';
|
||||||
|
|
||||||
return isChannel ? (
|
if (isChannel) {
|
||||||
|
return (
|
||||||
<>
|
<>
|
||||||
<UriIndicator uri={uri} link showAtSign />
|
<UriIndicator uri={uri} link showAtSign />
|
||||||
<span>{fullUri.length > uri.length ? fullUri.substring(uri.length, fullUri.length) : ''}</span>
|
<span>{fullUri.length > uri.length ? fullUri.substring(uri.length, fullUri.length) : ''}</span>
|
||||||
</>
|
</>
|
||||||
) : allowPreview ? (
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allowPreview) {
|
||||||
|
return (
|
||||||
<div className={classnames('claim-link')}>
|
<div className={classnames('claim-link')}>
|
||||||
<div
|
<div className={isPlayingInline ? INLINE_PLAYER_WRAPPER_CLASS : undefined}>
|
||||||
className={classnames({
|
<FileRenderInitiator uri={uri} parentCommentId={parentCommentId} isMarkdownPost={isMarkdownPost} embedded />
|
||||||
[INLINE_PLAYER_WRAPPER_CLASS]: isPlayingInline,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<EmbedPlayButton uri={uri} parentCommentId={parentCommentId} isMarkdownPost={isMarkdownPost} />
|
|
||||||
</div>
|
</div>
|
||||||
<Button button="link" className="preview-link__url" label={uri} navigate={uri} />
|
<Button button="link" className="preview-link__url" label={uri} navigate={uri} />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
<Button
|
<Button
|
||||||
button="link"
|
button="link"
|
||||||
title={SIMPLE_SITE ? __("This channel isn't staking enough Credits for link previews.") : children}
|
title={__("This channel isn't staking enough Credits for link previews.")}
|
||||||
label={children}
|
label={children}
|
||||||
className="button--external-link"
|
className="button--external-link"
|
||||||
navigate={uri}
|
navigate={uri}
|
||||||
|
|
|
@ -2,7 +2,6 @@ import { connect } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
makeSelectClaimForUri,
|
makeSelectClaimForUri,
|
||||||
selectIsUriResolving,
|
selectIsUriResolving,
|
||||||
getThumbnailFromClaim,
|
|
||||||
selectTitleForUri,
|
selectTitleForUri,
|
||||||
selectDateForUri,
|
selectDateForUri,
|
||||||
} from 'redux/selectors/claims';
|
} from 'redux/selectors/claims';
|
||||||
|
@ -11,7 +10,7 @@ import { doResolveUri } from 'redux/actions/claims';
|
||||||
import { selectViewCountForUri, selectBanStateForUri } from 'lbryinc';
|
import { selectViewCountForUri, selectBanStateForUri } from 'lbryinc';
|
||||||
import { selectIsActiveLivestreamForUri } from 'redux/selectors/livestream';
|
import { selectIsActiveLivestreamForUri } from 'redux/selectors/livestream';
|
||||||
import { selectShowMatureContent } from 'redux/selectors/settings';
|
import { selectShowMatureContent } from 'redux/selectors/settings';
|
||||||
import { isClaimNsfw, isStreamPlaceholderClaim } from 'util/claim';
|
import { isClaimNsfw, isStreamPlaceholderClaim, getThumbnailFromClaim } from 'util/claim';
|
||||||
import ClaimPreviewTile from './view';
|
import ClaimPreviewTile from './view';
|
||||||
import formatMediaDuration from 'util/formatMediaDuration';
|
import formatMediaDuration from 'util/formatMediaDuration';
|
||||||
|
|
||||||
|
|
|
@ -12,10 +12,9 @@ import { doToggleLoopList, doToggleShuffleList } from 'redux/actions/content';
|
||||||
import { doCollectionEdit } from 'redux/actions/collections';
|
import { doCollectionEdit } from 'redux/actions/collections';
|
||||||
|
|
||||||
const select = (state, props) => {
|
const select = (state, props) => {
|
||||||
const playingUri = selectPlayingUri(state);
|
const { uri: playingUri } = selectPlayingUri(state);
|
||||||
const playingUrl = playingUri && playingUri.uri;
|
const { permanent_url: url } = selectClaimForUri(state, playingUri) || {};
|
||||||
const claim = selectClaimForUri(state, playingUrl);
|
|
||||||
const url = claim && claim.permanent_url;
|
|
||||||
const loopList = selectListLoop(state);
|
const loopList = selectListLoop(state);
|
||||||
const loop = loopList && loopList.collectionId === props.id && loopList.loop;
|
const loop = loopList && loopList.collectionId === props.id && loopList.loop;
|
||||||
const shuffleList = selectListShuffle(state);
|
const shuffleList = selectListShuffle(state);
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
selectIsUriResolving,
|
selectIsUriResolving,
|
||||||
getThumbnailFromClaim,
|
|
||||||
selectTitleForUri,
|
selectTitleForUri,
|
||||||
makeSelectChannelForClaimUri,
|
makeSelectChannelForClaimUri,
|
||||||
selectClaimIsNsfwForUri,
|
selectClaimIsNsfwForUri,
|
||||||
|
@ -21,6 +20,7 @@ import { doResolveUri } from 'redux/actions/claims';
|
||||||
import { selectMutedChannels } from 'redux/selectors/blocked';
|
import { selectMutedChannels } from 'redux/selectors/blocked';
|
||||||
import { selectBlackListedOutpoints, selectFilteredOutpoints } from 'lbryinc';
|
import { selectBlackListedOutpoints, selectFilteredOutpoints } from 'lbryinc';
|
||||||
import { selectShowMatureContent } from 'redux/selectors/settings';
|
import { selectShowMatureContent } from 'redux/selectors/settings';
|
||||||
|
import { getThumbnailFromClaim } from 'util/claim';
|
||||||
import CollectionPreviewTile from './view';
|
import CollectionPreviewTile from './view';
|
||||||
|
|
||||||
const select = (state, props) => {
|
const select = (state, props) => {
|
||||||
|
|
|
@ -60,7 +60,7 @@ type Props = {
|
||||||
},
|
},
|
||||||
commentIdentityChannel: any,
|
commentIdentityChannel: any,
|
||||||
activeChannelClaim: ?ChannelClaim,
|
activeChannelClaim: ?ChannelClaim,
|
||||||
playingUri: ?PlayingUri,
|
playingUri: PlayingUri,
|
||||||
stakedLevel: number,
|
stakedLevel: number,
|
||||||
supportDisabled: boolean,
|
supportDisabled: boolean,
|
||||||
setQuickReply: (any) => void,
|
setQuickReply: (any) => void,
|
||||||
|
@ -186,7 +186,7 @@ function CommentView(props: Props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleEditComment() {
|
function handleEditComment() {
|
||||||
if (playingUri && playingUri.source === 'comment') {
|
if (playingUri.source === 'comment') {
|
||||||
clearPlayingUri();
|
clearPlayingUri();
|
||||||
}
|
}
|
||||||
setEditing(true);
|
setEditing(true);
|
||||||
|
@ -259,7 +259,13 @@ function CommentView(props: Props) {
|
||||||
>
|
>
|
||||||
<div className="comment__thumbnail-wrapper">
|
<div className="comment__thumbnail-wrapper">
|
||||||
{authorUri ? (
|
{authorUri ? (
|
||||||
<ChannelThumbnail uri={authorUri} obscure={channelIsBlocked} xsmall className="comment__author-thumbnail" checkMembership={false} />
|
<ChannelThumbnail
|
||||||
|
uri={authorUri}
|
||||||
|
obscure={channelIsBlocked}
|
||||||
|
xsmall
|
||||||
|
className="comment__author-thumbnail"
|
||||||
|
checkMembership={false}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<ChannelThumbnail xsmall className="comment__author-thumbnail" checkMembership={false} />
|
<ChannelThumbnail xsmall className="comment__author-thumbnail" checkMembership={false} />
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -26,7 +26,7 @@ type Props = {
|
||||||
claim: ?Claim,
|
claim: ?Claim,
|
||||||
claimIsMine: boolean,
|
claimIsMine: boolean,
|
||||||
activeChannelClaim: ?ChannelClaim,
|
activeChannelClaim: ?ChannelClaim,
|
||||||
playingUri: ?PlayingUri,
|
playingUri: PlayingUri,
|
||||||
moderationDelegatorsById: { [string]: { global: boolean, delegators: { name: string, claimId: string } } },
|
moderationDelegatorsById: { [string]: { global: boolean, delegators: { name: string, claimId: string } } },
|
||||||
// --- perform ---
|
// --- perform ---
|
||||||
doToast: ({ message: string }) => void,
|
doToast: ({ message: string }) => void,
|
||||||
|
@ -87,7 +87,7 @@ function CommentMenuList(props: Props) {
|
||||||
Object.values(activeModeratorInfo.delegators).includes(contentChannelClaim.claim_id);
|
Object.values(activeModeratorInfo.delegators).includes(contentChannelClaim.claim_id);
|
||||||
|
|
||||||
function handleDeleteComment() {
|
function handleDeleteComment() {
|
||||||
if (playingUri && playingUri.source === 'comment') {
|
if (playingUri.source === 'comment') {
|
||||||
clearPlayingUri();
|
clearPlayingUri();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { selectThumbnailForUri, makeSelectClaimForUri } from 'redux/selectors/claims';
|
|
||||||
import { doResolveUri } from 'redux/actions/claims';
|
|
||||||
import * as SETTINGS from 'constants/settings';
|
|
||||||
import { doFetchCostInfoForUri, selectCostInfoForUri } from 'lbryinc';
|
|
||||||
import { doPlayUri, doSetPlayingUri } from 'redux/actions/content';
|
|
||||||
import { doAnaltyicsPurchaseEvent } from 'redux/actions/app';
|
|
||||||
import { selectClientSetting } from 'redux/selectors/settings';
|
|
||||||
import { makeSelectFileRenderModeForUri } from 'redux/selectors/content';
|
|
||||||
|
|
||||||
import ChannelThumbnail from './view';
|
|
||||||
|
|
||||||
const select = (state, props) => ({
|
|
||||||
thumbnail: selectThumbnailForUri(state, props.uri),
|
|
||||||
claim: makeSelectClaimForUri(props.uri)(state),
|
|
||||||
floatingPlayerEnabled: selectClientSetting(state, SETTINGS.FLOATING_PLAYER),
|
|
||||||
costInfo: selectCostInfoForUri(state, props.uri),
|
|
||||||
renderMode: makeSelectFileRenderModeForUri(props.uri)(state),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(select, {
|
|
||||||
doResolveUri,
|
|
||||||
doFetchCostInfoForUri,
|
|
||||||
doPlayUri,
|
|
||||||
doSetPlayingUri,
|
|
||||||
doAnaltyicsPurchaseEvent,
|
|
||||||
})(ChannelThumbnail);
|
|
|
@ -1,108 +0,0 @@
|
||||||
// @flow
|
|
||||||
import * as RENDER_MODES from 'constants/file_render_modes';
|
|
||||||
import React, { useEffect } from 'react';
|
|
||||||
import classnames from 'classnames';
|
|
||||||
import Button from 'component/button';
|
|
||||||
import FileViewerEmbeddedTitle from 'component/fileViewerEmbeddedTitle';
|
|
||||||
import { useHistory } from 'react-router-dom';
|
|
||||||
import { useIsMobile } from 'effects/use-screensize';
|
|
||||||
import { formatLbryUrlForWeb } from 'util/url';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
uri: string,
|
|
||||||
thumbnail: string,
|
|
||||||
claim: ?Claim,
|
|
||||||
doResolveUri: (string) => void,
|
|
||||||
doFetchCostInfoForUri: (string) => void,
|
|
||||||
costInfo: ?{ cost: number },
|
|
||||||
floatingPlayerEnabled: boolean,
|
|
||||||
doPlayUri: (string, ?boolean, ?boolean, (GetResponse) => void) => void,
|
|
||||||
doAnaltyicsPurchaseEvent: (GetResponse) => void,
|
|
||||||
parentCommentId?: string,
|
|
||||||
isMarkdownPost: boolean,
|
|
||||||
doSetPlayingUri: ({}) => void,
|
|
||||||
renderMode: string,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function EmbedPlayButton(props: Props) {
|
|
||||||
const {
|
|
||||||
uri,
|
|
||||||
thumbnail = '',
|
|
||||||
claim,
|
|
||||||
doResolveUri,
|
|
||||||
doFetchCostInfoForUri,
|
|
||||||
floatingPlayerEnabled,
|
|
||||||
doPlayUri,
|
|
||||||
doSetPlayingUri,
|
|
||||||
doAnaltyicsPurchaseEvent,
|
|
||||||
costInfo,
|
|
||||||
parentCommentId,
|
|
||||||
isMarkdownPost,
|
|
||||||
renderMode,
|
|
||||||
} = props;
|
|
||||||
const {
|
|
||||||
push,
|
|
||||||
location: { pathname },
|
|
||||||
} = useHistory();
|
|
||||||
const isMobile = useIsMobile();
|
|
||||||
const hasResolvedUri = claim !== undefined;
|
|
||||||
const hasCostInfo = costInfo !== undefined;
|
|
||||||
const disabled = !hasResolvedUri || !costInfo;
|
|
||||||
const canPlayInline = [RENDER_MODES.AUDIO, RENDER_MODES.VIDEO].includes(renderMode);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!hasResolvedUri) {
|
|
||||||
doResolveUri(uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasCostInfo) {
|
|
||||||
doFetchCostInfoForUri(uri);
|
|
||||||
}
|
|
||||||
}, [uri, doResolveUri, doFetchCostInfoForUri, hasCostInfo, hasResolvedUri]);
|
|
||||||
|
|
||||||
function handleClick() {
|
|
||||||
if (disabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isMobile || !floatingPlayerEnabled || !canPlayInline) {
|
|
||||||
const formattedUrl = formatLbryUrlForWeb(uri);
|
|
||||||
push(formattedUrl);
|
|
||||||
} else {
|
|
||||||
doPlayUri(uri, undefined, undefined, (fileInfo) => {
|
|
||||||
let playingOptions = { uri, pathname, source: undefined, commentId: undefined };
|
|
||||||
if (parentCommentId) {
|
|
||||||
playingOptions.source = 'comment';
|
|
||||||
playingOptions.commentId = parentCommentId;
|
|
||||||
} else if (isMarkdownPost) {
|
|
||||||
playingOptions.source = 'markdown';
|
|
||||||
}
|
|
||||||
|
|
||||||
doSetPlayingUri(playingOptions);
|
|
||||||
doAnaltyicsPurchaseEvent(fileInfo);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
disabled={disabled}
|
|
||||||
role="button"
|
|
||||||
className="embed__inline-button"
|
|
||||||
onClick={handleClick}
|
|
||||||
style={{ backgroundImage: `url('${thumbnail.replace(/'/g, "\\'")}')` }}
|
|
||||||
>
|
|
||||||
<FileViewerEmbeddedTitle uri={uri} isInApp />
|
|
||||||
<Button
|
|
||||||
onClick={handleClick}
|
|
||||||
iconSize={30}
|
|
||||||
title={__('Play')}
|
|
||||||
className={classnames('button--icon', {
|
|
||||||
'button--play': canPlayInline,
|
|
||||||
'button--view': !canPlayInline,
|
|
||||||
})}
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { selectTitleForUri, makeSelectClaimWasPurchased } from 'redux/selectors/claims';
|
import { selectClaimForUri, selectTitleForUri, makeSelectClaimWasPurchased } from 'redux/selectors/claims';
|
||||||
import { makeSelectStreamingUrlForUri } from 'redux/selectors/file_info';
|
import { makeSelectStreamingUrlForUri } from 'redux/selectors/file_info';
|
||||||
import {
|
import {
|
||||||
makeSelectNextUrlForCollectionAndUrl,
|
makeSelectNextUrlForCollectionAndUrl,
|
||||||
|
@ -18,8 +18,10 @@ import { doUriInitiatePlay, doSetPlayingUri } from 'redux/actions/content';
|
||||||
import { doFetchRecommendedContent } from 'redux/actions/search';
|
import { doFetchRecommendedContent } from 'redux/actions/search';
|
||||||
import { withRouter } from 'react-router';
|
import { withRouter } from 'react-router';
|
||||||
import { selectMobilePlayerDimensions } from 'redux/selectors/app';
|
import { selectMobilePlayerDimensions } from 'redux/selectors/app';
|
||||||
import { selectIsActiveLivestreamForUri } from 'redux/selectors/livestream';
|
import { selectIsActiveLivestreamForUri, selectCommentSocketConnected } from 'redux/selectors/livestream';
|
||||||
import { doSetMobilePlayerDimensions } from 'redux/actions/app';
|
import { doSetMobilePlayerDimensions } from 'redux/actions/app';
|
||||||
|
import { doCommentSocketConnect, doCommentSocketDisconnect } from 'redux/actions/websocket';
|
||||||
|
import { doSetSocketConnected } from 'redux/actions/livestream';
|
||||||
import FileRenderFloating from './view';
|
import FileRenderFloating from './view';
|
||||||
|
|
||||||
const select = (state, props) => {
|
const select = (state, props) => {
|
||||||
|
@ -28,7 +30,13 @@ const select = (state, props) => {
|
||||||
const playingUri = selectPlayingUri(state);
|
const playingUri = selectPlayingUri(state);
|
||||||
const { uri, collectionId } = playingUri || {};
|
const { uri, collectionId } = playingUri || {};
|
||||||
|
|
||||||
|
const claim = uri && selectClaimForUri(state, uri);
|
||||||
|
const { claim_id: claimId, signing_channel: channelClaim } = claim || {};
|
||||||
|
const { canonical_url: channelClaimUrl } = channelClaim || {};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
claimId,
|
||||||
|
channelClaimUrl,
|
||||||
uri,
|
uri,
|
||||||
playingUri,
|
playingUri,
|
||||||
primaryUri: selectPrimaryUri(state),
|
primaryUri: selectPrimaryUri(state),
|
||||||
|
@ -45,6 +53,7 @@ const select = (state, props) => {
|
||||||
collectionId,
|
collectionId,
|
||||||
isCurrentClaimLive: selectIsActiveLivestreamForUri(state, uri),
|
isCurrentClaimLive: selectIsActiveLivestreamForUri(state, uri),
|
||||||
mobilePlayerDimensions: selectMobilePlayerDimensions(state),
|
mobilePlayerDimensions: selectMobilePlayerDimensions(state),
|
||||||
|
socketConnected: selectCommentSocketConnected(state),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -53,6 +62,9 @@ const perform = {
|
||||||
doUriInitiatePlay,
|
doUriInitiatePlay,
|
||||||
doSetPlayingUri,
|
doSetPlayingUri,
|
||||||
doSetMobilePlayerDimensions,
|
doSetMobilePlayerDimensions,
|
||||||
|
doCommentSocketConnect,
|
||||||
|
doCommentSocketDisconnect,
|
||||||
|
doSetSocketConnected,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withRouter(connect(select, perform)(FileRenderFloating));
|
export default withRouter(connect(select, perform)(FileRenderFloating));
|
||||||
|
|
|
@ -11,7 +11,7 @@ import usePersistedState from 'effects/use-persisted-state';
|
||||||
import { PRIMARY_PLAYER_WRAPPER_CLASS } from 'page/file/view';
|
import { PRIMARY_PLAYER_WRAPPER_CLASS } from 'page/file/view';
|
||||||
import Draggable from 'react-draggable';
|
import Draggable from 'react-draggable';
|
||||||
import { onFullscreenChange } from 'util/full-screen';
|
import { onFullscreenChange } from 'util/full-screen';
|
||||||
import { generateListSearchUrlParams } from 'util/url';
|
import { generateListSearchUrlParams, formatLbryChannelName } from 'util/url';
|
||||||
import { useIsMobile } from 'effects/use-screensize';
|
import { useIsMobile } from 'effects/use-screensize';
|
||||||
import debounce from 'util/debounce';
|
import debounce from 'util/debounce';
|
||||||
import { useHistory } from 'react-router';
|
import { useHistory } from 'react-router';
|
||||||
|
@ -35,13 +35,15 @@ export const FLOATING_PLAYER_CLASS = 'content__viewer--floating';
|
||||||
// ****************************************************************************
|
// ****************************************************************************
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
claimId: ?string,
|
||||||
|
channelClaimUrl: ?string,
|
||||||
isFloating: boolean,
|
isFloating: boolean,
|
||||||
uri: string,
|
uri: string,
|
||||||
streamingUrl?: string,
|
streamingUrl?: string,
|
||||||
title: ?string,
|
title: ?string,
|
||||||
floatingPlayerEnabled: boolean,
|
floatingPlayerEnabled: boolean,
|
||||||
renderMode: string,
|
renderMode: string,
|
||||||
playingUri: ?PlayingUri,
|
playingUri: PlayingUri,
|
||||||
primaryUri: ?string,
|
primaryUri: ?string,
|
||||||
videoTheaterMode: boolean,
|
videoTheaterMode: boolean,
|
||||||
collectionId: string,
|
collectionId: string,
|
||||||
|
@ -50,15 +52,21 @@ type Props = {
|
||||||
nextListUri: string,
|
nextListUri: string,
|
||||||
previousListUri: string,
|
previousListUri: string,
|
||||||
doFetchRecommendedContent: (uri: string) => void,
|
doFetchRecommendedContent: (uri: string) => void,
|
||||||
doUriInitiatePlay: (uri: string, collectionId: ?string, isPlayable: ?boolean, isFloating: ?boolean) => void,
|
doUriInitiatePlay: (playingOptions: PlayingUri, isPlayable: ?boolean, isFloating: ?boolean) => void,
|
||||||
doSetPlayingUri: ({ uri?: ?string }) => void,
|
doSetPlayingUri: ({ uri?: ?string }) => void,
|
||||||
isCurrentClaimLive?: boolean,
|
isCurrentClaimLive?: boolean,
|
||||||
mobilePlayerDimensions?: any,
|
mobilePlayerDimensions?: any,
|
||||||
|
socketConnected: boolean,
|
||||||
doSetMobilePlayerDimensions: ({ height?: ?number, width?: ?number }) => void,
|
doSetMobilePlayerDimensions: ({ height?: ?number, width?: ?number }) => void,
|
||||||
|
doCommentSocketConnect: (string, string, string) => void,
|
||||||
|
doCommentSocketDisconnect: (string, string) => void,
|
||||||
|
doSetSocketConnected: (connected: boolean) => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function FileRenderFloating(props: Props) {
|
export default function FileRenderFloating(props: Props) {
|
||||||
const {
|
const {
|
||||||
|
claimId,
|
||||||
|
channelClaimUrl,
|
||||||
uri,
|
uri,
|
||||||
streamingUrl,
|
streamingUrl,
|
||||||
title,
|
title,
|
||||||
|
@ -73,12 +81,16 @@ export default function FileRenderFloating(props: Props) {
|
||||||
claimWasPurchased,
|
claimWasPurchased,
|
||||||
nextListUri,
|
nextListUri,
|
||||||
previousListUri,
|
previousListUri,
|
||||||
|
socketConnected,
|
||||||
doFetchRecommendedContent,
|
doFetchRecommendedContent,
|
||||||
doUriInitiatePlay,
|
doUriInitiatePlay,
|
||||||
doSetPlayingUri,
|
doSetPlayingUri,
|
||||||
isCurrentClaimLive,
|
isCurrentClaimLive,
|
||||||
mobilePlayerDimensions,
|
mobilePlayerDimensions,
|
||||||
doSetMobilePlayerDimensions,
|
doSetMobilePlayerDimensions,
|
||||||
|
doCommentSocketConnect,
|
||||||
|
doCommentSocketDisconnect,
|
||||||
|
doSetSocketConnected,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
@ -88,7 +100,7 @@ export default function FileRenderFloating(props: Props) {
|
||||||
} = useHistory();
|
} = useHistory();
|
||||||
const hideFloatingPlayer = state && state.hideFloatingPlayer;
|
const hideFloatingPlayer = state && state.hideFloatingPlayer;
|
||||||
|
|
||||||
const playingUriSource = playingUri && playingUri.source;
|
const { uri: playingUrl, source: playingUriSource, primaryUri: playingPrimaryUri } = playingUri;
|
||||||
const isComment = playingUriSource === 'comment';
|
const isComment = playingUriSource === 'comment';
|
||||||
const mainFilePlaying = (!isFloating || !isMobile) && primaryUri && isURIEqual(uri, primaryUri);
|
const mainFilePlaying = (!isFloating || !isMobile) && primaryUri && isURIEqual(uri, primaryUri);
|
||||||
const noFloatingPlayer = !isFloating || isMobile || !floatingPlayerEnabled || hideFloatingPlayer;
|
const noFloatingPlayer = !isFloating || isMobile || !floatingPlayerEnabled || hideFloatingPlayer;
|
||||||
|
@ -105,8 +117,7 @@ export default function FileRenderFloating(props: Props) {
|
||||||
const relativePosRef = React.useRef({ x: 0, y: 0 });
|
const relativePosRef = React.useRef({ x: 0, y: 0 });
|
||||||
|
|
||||||
const navigateUrl =
|
const navigateUrl =
|
||||||
playingUri &&
|
(playingPrimaryUri || playingUrl || '') + (collectionId ? generateListSearchUrlParams(collectionId) : '');
|
||||||
(playingUri.primaryUri || playingUri.uri) + (collectionId ? generateListSearchUrlParams(collectionId) : '');
|
|
||||||
|
|
||||||
const isFree = costInfo && costInfo.cost === 0;
|
const isFree = costInfo && costInfo.cost === 0;
|
||||||
const canViewFile = isFree || claimWasPurchased;
|
const canViewFile = isFree || claimWasPurchased;
|
||||||
|
@ -184,11 +195,37 @@ export default function FileRenderFloating(props: Props) {
|
||||||
resetState
|
resetState
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Establish web socket connection for viewer count.
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (playingUri && (playingUri.primaryUri || playingUri.uri)) {
|
if (!claimId || !channelClaimUrl || !isCurrentClaimLive || socketConnected) return;
|
||||||
|
|
||||||
|
const channelName = formatLbryChannelName(channelClaimUrl);
|
||||||
|
|
||||||
|
doCommentSocketConnect(uri, channelName, claimId);
|
||||||
|
doSetSocketConnected(true);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
doCommentSocketDisconnect(claimId, channelName);
|
||||||
|
doSetSocketConnected(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
// only listen to socketConnected on initial mount
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [
|
||||||
|
channelClaimUrl,
|
||||||
|
claimId,
|
||||||
|
doCommentSocketConnect,
|
||||||
|
doCommentSocketDisconnect,
|
||||||
|
doSetSocketConnected,
|
||||||
|
isCurrentClaimLive,
|
||||||
|
uri,
|
||||||
|
]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (playingPrimaryUri || playingUrl) {
|
||||||
handleResize();
|
handleResize();
|
||||||
}
|
}
|
||||||
}, [handleResize, playingUri, videoTheaterMode]);
|
}, [handleResize, playingPrimaryUri, videoTheaterMode, playingUrl]);
|
||||||
|
|
||||||
// Listen to main-window resizing and adjust the floating player position accordingly:
|
// Listen to main-window resizing and adjust the floating player position accordingly:
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
@ -336,7 +373,7 @@ export default function FileRenderFloating(props: Props) {
|
||||||
<AutoplayCountdown
|
<AutoplayCountdown
|
||||||
nextRecommendedUri={nextListUri}
|
nextRecommendedUri={nextListUri}
|
||||||
doNavigate={() => setDoNavigate(true)}
|
doNavigate={() => setDoNavigate(true)}
|
||||||
doReplay={() => doUriInitiatePlay(uri, collectionId, false, isFloating)}
|
doReplay={() => doUriInitiatePlay({ uri, collectionId }, false, isFloating)}
|
||||||
doPrevious={() => {
|
doPrevious={() => {
|
||||||
setPlayNext(false);
|
setPlayNext(false);
|
||||||
setDoNavigate(true);
|
setDoNavigate(true);
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { doUriInitiatePlay } from 'redux/actions/content';
|
import { doUriInitiatePlay } from 'redux/actions/content';
|
||||||
import {
|
import { makeSelectClaimWasPurchased, selectClaimForUri } from 'redux/selectors/claims';
|
||||||
selectThumbnailForUri,
|
|
||||||
makeSelectClaimWasPurchased,
|
|
||||||
selectIsLivestreamClaimForUri,
|
|
||||||
} from 'redux/selectors/claims';
|
|
||||||
import { makeSelectFileInfoForUri } from 'redux/selectors/file_info';
|
import { makeSelectFileInfoForUri } from 'redux/selectors/file_info';
|
||||||
import * as SETTINGS from 'constants/settings';
|
import * as SETTINGS from 'constants/settings';
|
||||||
import { selectCostInfoForUri } from 'lbryinc';
|
import { selectCostInfoForUri } from 'lbryinc';
|
||||||
|
@ -19,12 +15,20 @@ import {
|
||||||
} from 'redux/selectors/content';
|
} from 'redux/selectors/content';
|
||||||
import FileRenderInitiator from './view';
|
import FileRenderInitiator from './view';
|
||||||
import { selectIsActiveLivestreamForUri } from 'redux/selectors/livestream';
|
import { selectIsActiveLivestreamForUri } from 'redux/selectors/livestream';
|
||||||
|
import { getThumbnailFromClaim, isStreamPlaceholderClaim } from 'util/claim';
|
||||||
|
import { doFetchChannelLiveStatus } from 'redux/actions/livestream';
|
||||||
|
|
||||||
const select = (state, props) => {
|
const select = (state, props) => {
|
||||||
const { uri } = props;
|
const { uri } = props;
|
||||||
|
|
||||||
|
const claim = selectClaimForUri(state, uri);
|
||||||
|
const { claim_id: claimId, signing_channel: channelClaim } = claim || {};
|
||||||
|
const { claim_id: channelClaimId } = channelClaim || {};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
claimThumbnail: selectThumbnailForUri(state, uri),
|
claimId,
|
||||||
|
channelClaimId,
|
||||||
|
claimThumbnail: getThumbnailFromClaim(claim),
|
||||||
fileInfo: makeSelectFileInfoForUri(uri)(state),
|
fileInfo: makeSelectFileInfoForUri(uri)(state),
|
||||||
obscurePreview: selectShouldObscurePreviewForUri(state, uri),
|
obscurePreview: selectShouldObscurePreviewForUri(state, uri),
|
||||||
isPlaying: makeSelectIsPlaying(uri)(state),
|
isPlaying: makeSelectIsPlaying(uri)(state),
|
||||||
|
@ -35,12 +39,13 @@ const select = (state, props) => {
|
||||||
claimWasPurchased: makeSelectClaimWasPurchased(uri)(state),
|
claimWasPurchased: makeSelectClaimWasPurchased(uri)(state),
|
||||||
authenticated: selectUserVerifiedEmail(state),
|
authenticated: selectUserVerifiedEmail(state),
|
||||||
isCurrentClaimLive: selectIsActiveLivestreamForUri(state, uri),
|
isCurrentClaimLive: selectIsActiveLivestreamForUri(state, uri),
|
||||||
isLivestreamClaim: selectIsLivestreamClaimForUri(state, uri),
|
isLivestreamClaim: isStreamPlaceholderClaim(claim),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const perform = {
|
const perform = {
|
||||||
doUriInitiatePlay,
|
doUriInitiatePlay,
|
||||||
|
doFetchChannelLiveStatus,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withRouter(connect(select, perform)(FileRenderInitiator));
|
export default withRouter(connect(select, perform)(FileRenderInitiator));
|
||||||
|
|
|
@ -15,8 +15,12 @@ import Nag from 'component/common/nag';
|
||||||
import FileRenderPlaceholder from 'static/img/fileRenderPlaceholder.png';
|
import FileRenderPlaceholder from 'static/img/fileRenderPlaceholder.png';
|
||||||
import * as COLLECTIONS_CONSTS from 'constants/collections';
|
import * as COLLECTIONS_CONSTS from 'constants/collections';
|
||||||
import { LayoutRenderContext } from 'page/livestream/view';
|
import { LayoutRenderContext } from 'page/livestream/view';
|
||||||
|
import { formatLbryUrlForWeb } from 'util/url';
|
||||||
|
import { LIVESTREAM_STATUS_CHECK_INTERVAL } from 'constants/livestream';
|
||||||
|
import FileViewerEmbeddedTitle from 'component/fileViewerEmbeddedTitle';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
channelClaimId: ?string,
|
||||||
isPlaying: boolean,
|
isPlaying: boolean,
|
||||||
fileInfo: FileListItem,
|
fileInfo: FileListItem,
|
||||||
uri: string,
|
uri: string,
|
||||||
|
@ -33,13 +37,18 @@ type Props = {
|
||||||
authenticated: boolean,
|
authenticated: boolean,
|
||||||
videoTheaterMode: boolean,
|
videoTheaterMode: boolean,
|
||||||
isCurrentClaimLive?: boolean,
|
isCurrentClaimLive?: boolean,
|
||||||
doUriInitiatePlay: (uri: string, collectionId: ?string, isPlayable: boolean) => void,
|
|
||||||
isLivestreamClaim: boolean,
|
isLivestreamClaim: boolean,
|
||||||
customAction?: any,
|
customAction?: any,
|
||||||
|
embedded?: boolean,
|
||||||
|
parentCommentId?: string,
|
||||||
|
isMarkdownPost?: boolean,
|
||||||
|
doUriInitiatePlay: (playingOptions: PlayingUri, isPlayable: boolean) => void,
|
||||||
|
doFetchChannelLiveStatus: (string) => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function FileRenderInitiator(props: Props) {
|
export default function FileRenderInitiator(props: Props) {
|
||||||
const {
|
const {
|
||||||
|
channelClaimId,
|
||||||
isPlaying,
|
isPlaying,
|
||||||
fileInfo,
|
fileInfo,
|
||||||
uri,
|
uri,
|
||||||
|
@ -57,7 +66,11 @@ export default function FileRenderInitiator(props: Props) {
|
||||||
isCurrentClaimLive,
|
isCurrentClaimLive,
|
||||||
isLivestreamClaim,
|
isLivestreamClaim,
|
||||||
customAction,
|
customAction,
|
||||||
|
embedded,
|
||||||
|
parentCommentId,
|
||||||
|
isMarkdownPost,
|
||||||
doUriInitiatePlay,
|
doUriInitiatePlay,
|
||||||
|
doFetchChannelLiveStatus,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const layountRendered = React.useContext(LayoutRenderContext);
|
const layountRendered = React.useContext(LayoutRenderContext);
|
||||||
|
@ -67,14 +80,15 @@ export default function FileRenderInitiator(props: Props) {
|
||||||
const containerRef = React.useRef<any>();
|
const containerRef = React.useRef<any>();
|
||||||
const [thumbnail, setThumbnail] = React.useState(FileRenderPlaceholder);
|
const [thumbnail, setThumbnail] = React.useState(FileRenderPlaceholder);
|
||||||
|
|
||||||
const { search, href, state: locationState } = location;
|
const { search, href, state: locationState, pathname } = location;
|
||||||
const urlParams = search && new URLSearchParams(search);
|
const urlParams = search && new URLSearchParams(search);
|
||||||
const collectionId = urlParams && urlParams.get(COLLECTIONS_CONSTS.COLLECTION_ID);
|
const collectionId = urlParams && urlParams.get(COLLECTIONS_CONSTS.COLLECTION_ID);
|
||||||
|
|
||||||
// check if there is a time or autoplay parameter, if so force autoplay
|
// check if there is a time or autoplay parameter, if so force autoplay
|
||||||
const urlTimeParam = href && href.indexOf('t=') > -1;
|
const urlTimeParam = href && href.indexOf('t=') > -1;
|
||||||
const forceAutoplayParam = locationState && locationState.forceAutoplay;
|
const forceAutoplayParam = locationState && locationState.forceAutoplay;
|
||||||
const shouldAutoplay = forceAutoplayParam || urlTimeParam || autoplay;
|
const shouldAutoplay = !embedded && (forceAutoplayParam || urlTimeParam || autoplay);
|
||||||
|
|
||||||
const isFree = costInfo && costInfo.cost === 0;
|
const isFree = costInfo && costInfo.cost === 0;
|
||||||
const canViewFile = isLivestreamClaim
|
const canViewFile = isLivestreamClaim
|
||||||
? (layountRendered || isMobile) && isCurrentClaimLive
|
? (layountRendered || isMobile) && isCurrentClaimLive
|
||||||
|
@ -90,9 +104,20 @@ export default function FileRenderInitiator(props: Props) {
|
||||||
const shouldRedirect = !authenticated && !isFree;
|
const shouldRedirect = !authenticated && !isFree;
|
||||||
|
|
||||||
function doAuthRedirect() {
|
function doAuthRedirect() {
|
||||||
history.push(`/$/${PAGES.AUTH}?redirect=${encodeURIComponent(location.pathname)}`);
|
history.push(`/$/${PAGES.AUTH}?redirect=${encodeURIComponent(pathname)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Find out current channels status + active live claim
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!channelClaimId || !isLivestreamClaim) return;
|
||||||
|
|
||||||
|
const fetch = () => doFetchChannelLiveStatus(channelClaimId);
|
||||||
|
|
||||||
|
const intervalId = setInterval(fetch, LIVESTREAM_STATUS_CHECK_INTERVAL);
|
||||||
|
|
||||||
|
return () => clearInterval(intervalId);
|
||||||
|
}, [channelClaimId, doFetchChannelLiveStatus, isLivestreamClaim]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!claimThumbnail) return;
|
if (!claimThumbnail) return;
|
||||||
|
|
||||||
|
@ -114,11 +139,29 @@ export default function FileRenderInitiator(props: Props) {
|
||||||
}, 200);
|
}, 200);
|
||||||
}, [claimThumbnail, thumbnail]);
|
}, [claimThumbnail, thumbnail]);
|
||||||
|
|
||||||
|
function handleClick() {
|
||||||
|
if (embedded && !isPlayable) {
|
||||||
|
const formattedUrl = formatLbryUrlForWeb(uri);
|
||||||
|
history.push(formattedUrl);
|
||||||
|
} else {
|
||||||
|
viewFile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Wrap this in useCallback because we need to use it to the view effect
|
// Wrap this in useCallback because we need to use it to the view effect
|
||||||
// If we don't a new instance will be created for every render and react will think the dependencies have changed, which will add/remove the listener for every render
|
// If we don't a new instance will be created for every render and react will think the dependencies have changed, which will add/remove the listener for every render
|
||||||
const viewFile = React.useCallback(() => {
|
const viewFile = React.useCallback(() => {
|
||||||
doUriInitiatePlay(uri, collectionId, isPlayable);
|
const playingOptions = { uri, collectionId, pathname, source: undefined, commentId: undefined };
|
||||||
}, [collectionId, doUriInitiatePlay, isPlayable, uri]);
|
|
||||||
|
if (parentCommentId) {
|
||||||
|
playingOptions.source = 'comment';
|
||||||
|
playingOptions.commentId = parentCommentId;
|
||||||
|
} else if (isMarkdownPost) {
|
||||||
|
playingOptions.source = 'markdown';
|
||||||
|
}
|
||||||
|
|
||||||
|
doUriInitiatePlay(playingOptions, isPlayable);
|
||||||
|
}, [collectionId, doUriInitiatePlay, isMarkdownPost, isPlayable, parentCommentId, pathname, uri]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const videoOnPage = document.querySelector('video');
|
const videoOnPage = document.querySelector('video');
|
||||||
|
@ -143,15 +186,21 @@ export default function FileRenderInitiator(props: Props) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
onClick={disabled ? undefined : shouldRedirect ? doAuthRedirect : viewFile}
|
onClick={disabled ? undefined : shouldRedirect ? doAuthRedirect : handleClick}
|
||||||
style={thumbnail && !obscurePreview ? { backgroundImage: `url("${thumbnail}")` } : {}}
|
style={thumbnail && !obscurePreview ? { backgroundImage: `url("${thumbnail}")` } : {}}
|
||||||
className={classnames('content__cover', {
|
className={
|
||||||
|
embedded
|
||||||
|
? 'embed__inline-button'
|
||||||
|
: classnames('content__cover', {
|
||||||
'content__cover--disabled': disabled,
|
'content__cover--disabled': disabled,
|
||||||
'content__cover--theater-mode': videoTheaterMode && !isMobile,
|
'content__cover--theater-mode': videoTheaterMode && !isMobile,
|
||||||
'content__cover--text': isText,
|
'content__cover--text': isText,
|
||||||
'card__media--nsfw': obscurePreview,
|
'card__media--nsfw': obscurePreview,
|
||||||
})}
|
})
|
||||||
|
}
|
||||||
>
|
>
|
||||||
|
{embedded && <FileViewerEmbeddedTitle uri={uri} isInApp />}
|
||||||
|
|
||||||
{renderUnsupported ? (
|
{renderUnsupported ? (
|
||||||
<Nag
|
<Nag
|
||||||
type="helpful"
|
type="helpful"
|
||||||
|
@ -173,10 +222,10 @@ export default function FileRenderInitiator(props: Props) {
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!disabled && (
|
{(!disabled || (embedded && isLivestreamClaim)) && (
|
||||||
<Button
|
<Button
|
||||||
requiresAuth={shouldRedirect}
|
requiresAuth={shouldRedirect}
|
||||||
onClick={viewFile}
|
onClick={handleClick}
|
||||||
iconSize={30}
|
iconSize={30}
|
||||||
title={isPlayable ? __('Play') : __('View')}
|
title={isPlayable ? __('Play') : __('View')}
|
||||||
className={classnames('button--icon', {
|
className={classnames('button--icon', {
|
||||||
|
|
|
@ -2,13 +2,13 @@ import { connect } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
selectClaimIsMine,
|
selectClaimIsMine,
|
||||||
selectTitleForUri,
|
selectTitleForUri,
|
||||||
getThumbnailFromClaim,
|
|
||||||
selectClaimForUri,
|
selectClaimForUri,
|
||||||
selectIsUriResolving,
|
selectIsUriResolving,
|
||||||
makeSelectMetadataItemForUri,
|
makeSelectMetadataItemForUri,
|
||||||
} from 'redux/selectors/claims';
|
} from 'redux/selectors/claims';
|
||||||
import { doResolveUri } from 'redux/actions/claims';
|
import { doResolveUri } from 'redux/actions/claims';
|
||||||
import { selectBlackListedOutpoints } from 'lbryinc';
|
import { selectBlackListedOutpoints } from 'lbryinc';
|
||||||
|
import { getThumbnailFromClaim } from 'util/claim';
|
||||||
import PreviewLink from './view';
|
import PreviewLink from './view';
|
||||||
|
|
||||||
const select = (state, props) => {
|
const select = (state, props) => {
|
||||||
|
|
|
@ -39,8 +39,8 @@ const select = (state, props) => {
|
||||||
const userId = selectUser(state) && selectUser(state).id;
|
const userId = selectUser(state) && selectUser(state).id;
|
||||||
const internalFeature = selectUser(state) && selectUser(state).internal_feature;
|
const internalFeature = selectUser(state) && selectUser(state).internal_feature;
|
||||||
const playingUri = selectPlayingUri(state);
|
const playingUri = selectPlayingUri(state);
|
||||||
const collectionId = urlParams.get(COLLECTIONS_CONSTS.COLLECTION_ID) || (playingUri && playingUri.collectionId);
|
const collectionId = urlParams.get(COLLECTIONS_CONSTS.COLLECTION_ID) || playingUri.collectionId;
|
||||||
const isMarkdownOrComment = playingUri && (playingUri.source === 'markdown' || playingUri.source === 'comment');
|
const isMarkdownOrComment = playingUri.source === 'markdown' || playingUri.source === 'comment';
|
||||||
|
|
||||||
let nextRecommendedUri;
|
let nextRecommendedUri;
|
||||||
let previousListUri;
|
let previousListUri;
|
||||||
|
|
|
@ -426,6 +426,7 @@ export const COMMENT_FETCH_SETTINGS_COMPLETED = 'COMMENT_FETCH_SETTINGS_COMPLETE
|
||||||
export const COMMENT_FETCH_BLOCKED_WORDS_STARTED = 'COMMENT_FETCH_BLOCKED_WORDS_STARTED';
|
export const COMMENT_FETCH_BLOCKED_WORDS_STARTED = 'COMMENT_FETCH_BLOCKED_WORDS_STARTED';
|
||||||
export const COMMENT_FETCH_BLOCKED_WORDS_FAILED = 'COMMENT_FETCH_BLOCKED_WORDS_FAILED';
|
export const COMMENT_FETCH_BLOCKED_WORDS_FAILED = 'COMMENT_FETCH_BLOCKED_WORDS_FAILED';
|
||||||
export const COMMENT_FETCH_BLOCKED_WORDS_COMPLETED = 'COMMENT_FETCH_BLOCKED_WORDS_COMPLETED';
|
export const COMMENT_FETCH_BLOCKED_WORDS_COMPLETED = 'COMMENT_FETCH_BLOCKED_WORDS_COMPLETED';
|
||||||
|
export const COMMENT_SOCKET_CONNECTED = 'COMMENT_SOCKET_CONNECTED';
|
||||||
export const COMMENT_RECEIVED = 'COMMENT_RECEIVED';
|
export const COMMENT_RECEIVED = 'COMMENT_RECEIVED';
|
||||||
export const COMMENT_SUPER_CHAT_LIST_STARTED = 'COMMENT_SUPER_CHAT_LIST_STARTED';
|
export const COMMENT_SUPER_CHAT_LIST_STARTED = 'COMMENT_SUPER_CHAT_LIST_STARTED';
|
||||||
export const COMMENT_SUPER_CHAT_LIST_COMPLETED = 'COMMENT_SUPER_CHAT_LIST_COMPLETED';
|
export const COMMENT_SUPER_CHAT_LIST_COMPLETED = 'COMMENT_SUPER_CHAT_LIST_COMPLETED';
|
||||||
|
|
|
@ -15,6 +15,7 @@ export const LIVESTREAM_KILL = 'https://api.stream.odysee.com/stream/kill';
|
||||||
|
|
||||||
export const MAX_LIVESTREAM_COMMENTS = 50;
|
export const MAX_LIVESTREAM_COMMENTS = 50;
|
||||||
|
|
||||||
|
export const LIVESTREAM_STATUS_CHECK_INTERVAL = 30 * 1000;
|
||||||
export const LIVESTREAM_STARTS_SOON_BUFFER = 15;
|
export const LIVESTREAM_STARTS_SOON_BUFFER = 15;
|
||||||
export const LIVESTREAM_STARTED_RECENTLY_BUFFER = 15;
|
export const LIVESTREAM_STARTED_RECENTLY_BUFFER = 15;
|
||||||
export const LIVESTREAM_UPCOMING_BUFFER = 35;
|
export const LIVESTREAM_UPCOMING_BUFFER = 35;
|
||||||
|
|
|
@ -11,7 +11,7 @@ export default function usePlayNext(
|
||||||
nextListUri: ?string,
|
nextListUri: ?string,
|
||||||
previousListUri: ?string,
|
previousListUri: ?string,
|
||||||
doNavigate: boolean,
|
doNavigate: boolean,
|
||||||
doUriInitiatePlay: (uri: string, collectionId: ?string, isPlayable: ?boolean, isFloating: ?boolean) => void,
|
doUriInitiatePlay: (playingOptions: PlayingUri, isPlayable: ?boolean, isFloating: ?boolean) => void,
|
||||||
resetState: () => void
|
resetState: () => void
|
||||||
) {
|
) {
|
||||||
const { push } = useHistory();
|
const { push } = useHistory();
|
||||||
|
@ -27,7 +27,7 @@ export default function usePlayNext(
|
||||||
state: { collectionId, forceAutoplay: true, hideFloatingPlayer: true },
|
state: { collectionId, forceAutoplay: true, hideFloatingPlayer: true },
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
doUriInitiatePlay(playUri, collectionId, true, isFloating);
|
doUriInitiatePlay({ uri: playUri, collectionId }, true, isFloating);
|
||||||
}
|
}
|
||||||
|
|
||||||
resetState();
|
resetState();
|
||||||
|
|
|
@ -18,7 +18,7 @@ type Props = {
|
||||||
cancelPurchase: () => void,
|
cancelPurchase: () => void,
|
||||||
metadata: StreamMetadata,
|
metadata: StreamMetadata,
|
||||||
analyticsPurchaseEvent: (GetResponse) => void,
|
analyticsPurchaseEvent: (GetResponse) => void,
|
||||||
playingUri: ?PlayingUri,
|
playingUri: PlayingUri,
|
||||||
setPlayingUri: (?string) => void,
|
setPlayingUri: (?string) => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -43,14 +43,14 @@ function ModalAffirmPurchase(props: Props) {
|
||||||
setSuccess(true);
|
setSuccess(true);
|
||||||
analyticsPurchaseEvent(fileInfo);
|
analyticsPurchaseEvent(fileInfo);
|
||||||
|
|
||||||
if (!playingUri || playingUri.uri !== uri) {
|
if (playingUri.uri !== uri) {
|
||||||
setPlayingUri(uri);
|
setPlayingUri(uri);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function cancelPurchase() {
|
function cancelPurchase() {
|
||||||
if (playingUri && isURIEqual(uri, playingUri.uri)) {
|
if (playingUri.uri && isURIEqual(uri, playingUri.uri)) {
|
||||||
setPlayingUri(null);
|
setPlayingUri(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@ import { connect } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
selectClaimIsMine,
|
selectClaimIsMine,
|
||||||
selectTitleForUri,
|
selectTitleForUri,
|
||||||
getThumbnailFromClaim,
|
|
||||||
makeSelectCoverForUri,
|
makeSelectCoverForUri,
|
||||||
selectCurrentChannelPage,
|
selectCurrentChannelPage,
|
||||||
selectClaimForUri,
|
selectClaimForUri,
|
||||||
|
@ -16,6 +15,7 @@ import { selectModerationBlockList } from 'redux/selectors/comments';
|
||||||
import { selectMutedChannels } from 'redux/selectors/blocked';
|
import { selectMutedChannels } from 'redux/selectors/blocked';
|
||||||
import { doOpenModal } from 'redux/actions/app';
|
import { doOpenModal } from 'redux/actions/app';
|
||||||
import { selectLanguage } from 'redux/selectors/settings';
|
import { selectLanguage } from 'redux/selectors/settings';
|
||||||
|
import { getThumbnailFromClaim } from 'util/claim';
|
||||||
import ChannelPage from './view';
|
import ChannelPage from './view';
|
||||||
|
|
||||||
const select = (state, props) => {
|
const select = (state, props) => {
|
||||||
|
|
|
@ -4,7 +4,6 @@ import { withRouter } from 'react-router-dom';
|
||||||
import CollectionPage from './view';
|
import CollectionPage from './view';
|
||||||
import {
|
import {
|
||||||
selectTitleForUri,
|
selectTitleForUri,
|
||||||
getThumbnailFromClaim,
|
|
||||||
selectClaimIsMine,
|
selectClaimIsMine,
|
||||||
makeSelectClaimIsPending,
|
makeSelectClaimIsPending,
|
||||||
makeSelectClaimForClaimId,
|
makeSelectClaimForClaimId,
|
||||||
|
@ -20,6 +19,7 @@ import {
|
||||||
makeSelectEditedCollectionForId,
|
makeSelectEditedCollectionForId,
|
||||||
} from 'redux/selectors/collections';
|
} from 'redux/selectors/collections';
|
||||||
|
|
||||||
|
import { getThumbnailFromClaim } from 'util/claim';
|
||||||
import { doFetchItemsInCollection, doCollectionDelete, doCollectionEdit } from 'redux/actions/collections';
|
import { doFetchItemsInCollection, doCollectionDelete, doCollectionEdit } from 'redux/actions/collections';
|
||||||
import { selectUser } from 'redux/selectors/user';
|
import { selectUser } from 'redux/selectors/user';
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,12 @@ 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 { selectActiveLivestreamForChannel, selectActiveLivestreamInitialized } from 'redux/selectors/livestream';
|
import {
|
||||||
import { doFetchChannelLiveStatus } from 'redux/actions/livestream';
|
selectActiveLivestreamForChannel,
|
||||||
|
selectActiveLivestreamInitialized,
|
||||||
|
selectCommentSocketConnected,
|
||||||
|
} from 'redux/selectors/livestream';
|
||||||
|
import { doFetchChannelLiveStatus, doSetSocketConnected } from 'redux/actions/livestream';
|
||||||
import LivestreamPage from './view';
|
import LivestreamPage from './view';
|
||||||
|
|
||||||
const select = (state, props) => {
|
const select = (state, props) => {
|
||||||
|
@ -20,6 +24,7 @@ const select = (state, props) => {
|
||||||
chatDisabled: makeSelectTagInClaimOrChannelForUri(uri, DISABLE_COMMENTS_TAG)(state),
|
chatDisabled: makeSelectTagInClaimOrChannelForUri(uri, DISABLE_COMMENTS_TAG)(state),
|
||||||
activeLivestreamForChannel: selectActiveLivestreamForChannel(state, channelClaimId),
|
activeLivestreamForChannel: selectActiveLivestreamForChannel(state, channelClaimId),
|
||||||
activeLivestreamInitialized: selectActiveLivestreamInitialized(state),
|
activeLivestreamInitialized: selectActiveLivestreamInitialized(state),
|
||||||
|
socketConnected: selectCommentSocketConnected(state),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -29,6 +34,7 @@ const perform = {
|
||||||
doCommentSocketConnect,
|
doCommentSocketConnect,
|
||||||
doCommentSocketDisconnect,
|
doCommentSocketDisconnect,
|
||||||
doFetchChannelLiveStatus,
|
doFetchChannelLiveStatus,
|
||||||
|
doSetSocketConnected,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(select, perform)(LivestreamPage);
|
export default connect(select, perform)(LivestreamPage);
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { formatLbryChannelName } from 'util/url';
|
import { formatLbryChannelName } from 'util/url';
|
||||||
import { lazyImport } from 'util/lazyImport';
|
import { lazyImport } from 'util/lazyImport';
|
||||||
import { LIVESTREAM_STARTS_SOON_BUFFER, LIVESTREAM_STARTED_RECENTLY_BUFFER } from 'constants/livestream';
|
import {
|
||||||
|
LIVESTREAM_STATUS_CHECK_INTERVAL,
|
||||||
|
LIVESTREAM_STARTS_SOON_BUFFER,
|
||||||
|
LIVESTREAM_STARTED_RECENTLY_BUFFER,
|
||||||
|
} from 'constants/livestream';
|
||||||
import analytics from 'analytics';
|
import analytics from 'analytics';
|
||||||
import LivestreamLayout from 'component/livestreamLayout';
|
import LivestreamLayout from 'component/livestreamLayout';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
@ -9,7 +13,6 @@ import Page from 'component/page';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const LivestreamChatLayout = lazyImport(() => import('component/livestreamChatLayout' /* webpackChunkName: "chat" */));
|
const LivestreamChatLayout = lazyImport(() => import('component/livestreamChatLayout' /* webpackChunkName: "chat" */));
|
||||||
const LIVESTREAM_STATUS_CHECK_INTERVAL = 30000;
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
activeLivestreamForChannel: any,
|
activeLivestreamForChannel: any,
|
||||||
|
@ -19,11 +22,13 @@ type Props = {
|
||||||
claim: StreamClaim,
|
claim: StreamClaim,
|
||||||
isAuthenticated: boolean,
|
isAuthenticated: boolean,
|
||||||
uri: string,
|
uri: string,
|
||||||
|
socketConnected: boolean,
|
||||||
doSetPrimaryUri: (uri: ?string) => void,
|
doSetPrimaryUri: (uri: ?string) => void,
|
||||||
doCommentSocketConnect: (string, string, string) => void,
|
doCommentSocketConnect: (string, string, string) => void,
|
||||||
doCommentSocketDisconnect: (string, string) => void,
|
doCommentSocketDisconnect: (string, string) => void,
|
||||||
doFetchChannelLiveStatus: (string) => void,
|
doFetchChannelLiveStatus: (string) => void,
|
||||||
doUserSetReferrer: (string) => void,
|
doUserSetReferrer: (string) => void,
|
||||||
|
doSetSocketConnected: (connected: boolean) => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LayoutRenderContext = React.createContext<any>();
|
export const LayoutRenderContext = React.createContext<any>();
|
||||||
|
@ -37,11 +42,13 @@ export default function LivestreamPage(props: Props) {
|
||||||
claim,
|
claim,
|
||||||
isAuthenticated,
|
isAuthenticated,
|
||||||
uri,
|
uri,
|
||||||
|
socketConnected,
|
||||||
doSetPrimaryUri,
|
doSetPrimaryUri,
|
||||||
doCommentSocketConnect,
|
doCommentSocketConnect,
|
||||||
doCommentSocketDisconnect,
|
doCommentSocketDisconnect,
|
||||||
doFetchChannelLiveStatus,
|
doFetchChannelLiveStatus,
|
||||||
doUserSetReferrer,
|
doUserSetReferrer,
|
||||||
|
doSetSocketConnected,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const [activeStreamUri, setActiveStreamUri] = React.useState(false);
|
const [activeStreamUri, setActiveStreamUri] = React.useState(false);
|
||||||
|
@ -67,20 +74,25 @@ export default function LivestreamPage(props: Props) {
|
||||||
|
|
||||||
// Establish web socket connection for viewer count.
|
// Establish web socket connection for viewer count.
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!claim) return;
|
if (!claim || socketConnected) return;
|
||||||
|
|
||||||
const { claim_id: claimId, signing_channel: channelClaim } = claim;
|
const { claim_id: claimId, signing_channel: channelClaim } = claim;
|
||||||
const channelName = channelClaim && formatLbryChannelName(channelClaim.canonical_url);
|
const channelName = channelClaim && formatLbryChannelName(channelClaim.canonical_url);
|
||||||
|
|
||||||
if (claimId && channelName) {
|
if (claimId && channelName) {
|
||||||
doCommentSocketConnect(uri, channelName, claimId);
|
doCommentSocketConnect(uri, channelName, claimId);
|
||||||
|
doSetSocketConnected(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (claimId && channelName) {
|
if (claimId && channelName) {
|
||||||
doCommentSocketDisconnect(claimId, channelName);
|
doCommentSocketDisconnect(claimId, channelName);
|
||||||
|
doSetSocketConnected(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// only listen to socketConnected on initial mount
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [claim, uri, doCommentSocketConnect, doCommentSocketDisconnect]);
|
}, [claim, uri, doCommentSocketConnect, doCommentSocketDisconnect]);
|
||||||
|
|
||||||
// Find out current channels status + active live claim every 30 seconds
|
// Find out current channels status + active live claim every 30 seconds
|
||||||
|
@ -149,10 +161,12 @@ export default function LivestreamPage(props: Props) {
|
||||||
}, [uri, stringifiedClaim, isAuthenticated, doUserSetReferrer]);
|
}, [uri, stringifiedClaim, isAuthenticated, doUserSetReferrer]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
if (!layountRendered) return;
|
||||||
|
|
||||||
doSetPrimaryUri(uri);
|
doSetPrimaryUri(uri);
|
||||||
|
|
||||||
return () => doSetPrimaryUri(null);
|
return () => doSetPrimaryUri(null);
|
||||||
}, [doSetPrimaryUri, uri]);
|
}, [doSetPrimaryUri, layountRendered, uri]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page
|
<Page
|
||||||
|
|
|
@ -119,7 +119,7 @@ export function doSetPlayingUri({
|
||||||
source?: string,
|
source?: string,
|
||||||
commentId?: string,
|
commentId?: string,
|
||||||
pathname?: string,
|
pathname?: string,
|
||||||
collectionId?: string,
|
collectionId?: ?string,
|
||||||
}) {
|
}) {
|
||||||
return (dispatch: Dispatch) => {
|
return (dispatch: Dispatch) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
|
@ -149,16 +149,18 @@ export function doDownloadUri(uri: string) {
|
||||||
return (dispatch: Dispatch) => dispatch(doPlayUri(uri, false, true, () => dispatch(doAnalyticsView(uri))));
|
return (dispatch: Dispatch) => dispatch(doPlayUri(uri, false, true, () => dispatch(doAnalyticsView(uri))));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doUriInitiatePlay(uri: string, collectionId?: string, isPlayable?: boolean, isFloating?: boolean) {
|
export function doUriInitiatePlay(playingOptions: PlayingUri, isPlayable?: boolean, isFloating?: boolean) {
|
||||||
return (dispatch: Dispatch, getState: () => any) => {
|
return (dispatch: Dispatch, getState: () => any) => {
|
||||||
|
const { uri, source } = playingOptions;
|
||||||
|
|
||||||
|
if (!uri) return;
|
||||||
|
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const isLive = selectIsActiveLivestreamForUri(state, uri);
|
const isLive = selectIsActiveLivestreamForUri(state, uri);
|
||||||
|
|
||||||
if (!isFloating) dispatch(doSetPrimaryUri(uri));
|
if (!isFloating && !source) dispatch(doSetPrimaryUri(uri));
|
||||||
|
|
||||||
if (isPlayable) {
|
if (isPlayable) dispatch(doSetPlayingUri(playingOptions));
|
||||||
dispatch(doSetPlayingUri({ uri, collectionId }));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isLive) dispatch(doPlayUri(uri, false, true, (fileInfo) => dispatch(doAnaltyicsPurchaseEvent(fileInfo))));
|
if (!isLive) dispatch(doPlayUri(uri, false, true, (fileInfo) => dispatch(doAnaltyicsPurchaseEvent(fileInfo))));
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,7 +5,7 @@ import * as ABANDON_STATES from 'constants/abandon_states';
|
||||||
import { shell } from 'electron';
|
import { shell } from 'electron';
|
||||||
// @endif
|
// @endif
|
||||||
import Lbry from 'lbry';
|
import Lbry from 'lbry';
|
||||||
import { makeSelectClaimForUri } from 'redux/selectors/claims';
|
import { selectClaimForUri } from 'redux/selectors/claims';
|
||||||
import { doAbandonClaim } from 'redux/actions/claims';
|
import { doAbandonClaim } from 'redux/actions/claims';
|
||||||
import { batchActions } from 'util/batch-actions';
|
import { batchActions } from 'util/batch-actions';
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ import {
|
||||||
selectDownloadingByOutpoint,
|
selectDownloadingByOutpoint,
|
||||||
makeSelectStreamingUrlForUri,
|
makeSelectStreamingUrlForUri,
|
||||||
} from 'redux/selectors/file_info';
|
} from 'redux/selectors/file_info';
|
||||||
|
import { isStreamPlaceholderClaim } from 'util/claim';
|
||||||
|
|
||||||
type Dispatch = (action: any) => any;
|
type Dispatch = (action: any) => any;
|
||||||
type GetState = () => { claims: any, file: FileState, content: any, user: User };
|
type GetState = () => { claims: any, file: FileState, content: any, user: User };
|
||||||
|
@ -77,7 +78,7 @@ export function doDeleteFileAndMaybeGoBack(
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const playingUri = selectPlayingUri(state);
|
const playingUri = selectPlayingUri(state);
|
||||||
const { outpoint } = makeSelectFileInfoForUri(uri)(state) || '';
|
const { outpoint } = makeSelectFileInfoForUri(uri)(state) || '';
|
||||||
const { nout, txid } = makeSelectClaimForUri(uri)(state);
|
const { nout, txid } = selectClaimForUri(state, uri);
|
||||||
const claimOutpoint = `${txid}:${nout}`;
|
const claimOutpoint = `${txid}:${nout}`;
|
||||||
const actions = [];
|
const actions = [];
|
||||||
|
|
||||||
|
@ -104,7 +105,7 @@ export function doDeleteFileAndMaybeGoBack(
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (playingUri && playingUri.uri === uri) {
|
if (playingUri.uri === uri) {
|
||||||
actions.push(doSetPlayingUri({ uri: null }));
|
actions.push(doSetPlayingUri({ uri: null }));
|
||||||
}
|
}
|
||||||
// it would be nice to stay on the claim if you just want to delete it
|
// it would be nice to stay on the claim if you just want to delete it
|
||||||
|
@ -117,7 +118,9 @@ export function doDeleteFileAndMaybeGoBack(
|
||||||
export function doFileGet(uri: string, saveFile: boolean = true, onSuccess?: (GetResponse) => any) {
|
export function doFileGet(uri: string, saveFile: boolean = true, onSuccess?: (GetResponse) => any) {
|
||||||
return (dispatch: Dispatch, getState: () => any) => {
|
return (dispatch: Dispatch, getState: () => any) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const { nout, txid } = makeSelectClaimForUri(uri)(state);
|
const claim = selectClaimForUri(state, uri);
|
||||||
|
const isLivestreamClaim = isStreamPlaceholderClaim(claim);
|
||||||
|
const { nout, txid } = claim;
|
||||||
const outpoint = `${txid}:${nout}`;
|
const outpoint = `${txid}:${nout}`;
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
|
@ -169,6 +172,10 @@ export function doFileGet(uri: string, saveFile: boolean = true, onSuccess?: (Ge
|
||||||
data: { outpoint },
|
data: { outpoint },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO: probably a better way to address this
|
||||||
|
// supress no source error if it's a livestream
|
||||||
|
if (isLivestreamClaim && error.message === "stream doesn't have source data") return;
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
doToast({
|
doToast({
|
||||||
message: `Failed to view ${uri}, please try again. If this problem persists, visit https://odysee.com/@OdyseeHelp:b?view=about for support.`,
|
message: `Failed to view ${uri}, please try again. If this problem persists, visit https://odysee.com/@OdyseeHelp:b?view=about for support.`,
|
||||||
|
|
|
@ -189,3 +189,9 @@ export const doFetchActiveLivestreams = (
|
||||||
dispatch({ type: ACTIONS.FETCH_ACTIVE_LIVESTREAMS_FAILED });
|
dispatch({ type: ACTIONS.FETCH_ACTIVE_LIVESTREAMS_FAILED });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const doSetSocketConnected = (connected: boolean) => (dispatch: Dispatch) =>
|
||||||
|
dispatch({
|
||||||
|
type: ACTIONS.COMMENT_SOCKET_CONNECTED,
|
||||||
|
data: { connected },
|
||||||
|
});
|
||||||
|
|
|
@ -3,7 +3,7 @@ import * as ACTIONS from 'constants/action_types';
|
||||||
const reducers = {};
|
const reducers = {};
|
||||||
const defaultState = {
|
const defaultState = {
|
||||||
primaryUri: null, // Top level content uri triggered from the file page
|
primaryUri: null, // Top level content uri triggered from the file page
|
||||||
playingUri: null,
|
playingUri: {},
|
||||||
channelClaimCounts: {},
|
channelClaimCounts: {},
|
||||||
positions: {},
|
positions: {},
|
||||||
history: [],
|
history: [],
|
||||||
|
|
|
@ -11,6 +11,7 @@ const defaultState: LivestreamState = {
|
||||||
activeLivestreamsLastFetchedDate: 0,
|
activeLivestreamsLastFetchedDate: 0,
|
||||||
activeLivestreamsLastFetchedOptions: {},
|
activeLivestreamsLastFetchedOptions: {},
|
||||||
activeLivestreamInitialized: false,
|
activeLivestreamInitialized: false,
|
||||||
|
commentSocketConnected: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default handleActions(
|
export default handleActions(
|
||||||
|
@ -67,6 +68,10 @@ export default handleActions(
|
||||||
if (activeLivestreams) delete activeLivestreams[action.data.channelId];
|
if (activeLivestreams) delete activeLivestreams[action.data.channelId];
|
||||||
return { ...state, activeLivestreams: Object.assign({}, activeLivestreams), activeLivestreamInitialized: true };
|
return { ...state, activeLivestreams: Object.assign({}, activeLivestreams), activeLivestreamInitialized: true };
|
||||||
},
|
},
|
||||||
|
[ACTIONS.COMMENT_SOCKET_CONNECTED]: (state: CommentsState, action: any) => ({
|
||||||
|
...state,
|
||||||
|
commentSocketConnected: action.data.connected,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
defaultState
|
defaultState
|
||||||
);
|
);
|
||||||
|
|
|
@ -6,7 +6,13 @@ import { selectYoutubeChannels } from 'redux/selectors/user';
|
||||||
import { selectSupportsByOutpoint } from 'redux/selectors/wallet';
|
import { selectSupportsByOutpoint } from 'redux/selectors/wallet';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { createCachedSelector } from 're-reselect';
|
import { createCachedSelector } from 're-reselect';
|
||||||
import { isClaimNsfw, filterClaims, getChannelIdFromClaim, isStreamPlaceholderClaim } from 'util/claim';
|
import {
|
||||||
|
isClaimNsfw,
|
||||||
|
filterClaims,
|
||||||
|
getChannelIdFromClaim,
|
||||||
|
isStreamPlaceholderClaim,
|
||||||
|
getThumbnailFromClaim,
|
||||||
|
} from 'util/claim';
|
||||||
import * as CLAIM from 'constants/claim';
|
import * as CLAIM from 'constants/claim';
|
||||||
import * as SETTINGS from 'constants/settings';
|
import * as SETTINGS from 'constants/settings';
|
||||||
import { INTERNAL_TAGS } from 'constants/tags';
|
import { INTERNAL_TAGS } from 'constants/tags';
|
||||||
|
@ -393,11 +399,6 @@ export const makeSelectContentTypeForUri = (uri: string) =>
|
||||||
return source ? source.media_type : undefined;
|
return source ? source.media_type : undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
export const getThumbnailFromClaim = (claim: Claim) => {
|
|
||||||
const thumbnail = claim && claim.value && claim.value.thumbnail;
|
|
||||||
return thumbnail && thumbnail.url ? thumbnail.url.trim().replace(/^http:\/\//i, 'https://') : undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const selectThumbnailForUri = createCachedSelector(selectClaimForUri, (claim) => {
|
export const selectThumbnailForUri = createCachedSelector(selectClaimForUri, (claim) => {
|
||||||
return getThumbnailFromClaim(claim);
|
return getThumbnailFromClaim(claim);
|
||||||
})((state, uri) => String(uri));
|
})((state, uri) => String(uri));
|
||||||
|
|
|
@ -31,11 +31,11 @@ export const makeSelectIsPlaying = (uri: string) =>
|
||||||
createSelector(selectPrimaryUri, (primaryUri) => primaryUri === uri);
|
createSelector(selectPrimaryUri, (primaryUri) => primaryUri === uri);
|
||||||
|
|
||||||
export const makeSelectIsUriCurrentlyPlaying = (uri: string) =>
|
export const makeSelectIsUriCurrentlyPlaying = (uri: string) =>
|
||||||
createSelector(selectPlayingUri, (playingUri) => playingUri && playingUri.uri === uri);
|
createSelector(selectPlayingUri, (playingUri) => playingUri.uri === uri);
|
||||||
|
|
||||||
export const makeSelectIsPlayerFloating = (location: UrlLocation) =>
|
export const makeSelectIsPlayerFloating = (location: UrlLocation) =>
|
||||||
createSelector(selectPrimaryUri, selectPlayingUri, (primaryUri, playingUri) => {
|
createSelector(selectPrimaryUri, selectPlayingUri, (primaryUri, playingUri) => {
|
||||||
if (!playingUri) return false;
|
if (!playingUri.uri) return false;
|
||||||
|
|
||||||
const { pathname, search } = location;
|
const { pathname, search } = location;
|
||||||
const hasSecondarySource = Boolean(playingUri.source);
|
const hasSecondarySource = Boolean(playingUri.source);
|
||||||
|
|
|
@ -12,6 +12,7 @@ export const selectViewersById = (state: State) => selectState(state).viewersByI
|
||||||
export const selectActiveLivestreams = (state: State) => selectState(state).activeLivestreams;
|
export const selectActiveLivestreams = (state: State) => selectState(state).activeLivestreams;
|
||||||
export const selectFetchingActiveLivestreams = (state: State) => selectState(state).fetchingActiveLivestreams;
|
export const selectFetchingActiveLivestreams = (state: State) => selectState(state).fetchingActiveLivestreams;
|
||||||
export const selectActiveLivestreamInitialized = (state: State) => selectState(state).activeLivestreamInitialized;
|
export const selectActiveLivestreamInitialized = (state: State) => selectState(state).activeLivestreamInitialized;
|
||||||
|
export const selectCommentSocketConnected = (state: State) => selectState(state).commentSocketConnected;
|
||||||
|
|
||||||
// select non-pending claims without sources for given channel
|
// select non-pending claims without sources for given channel
|
||||||
export const makeSelectLivestreamsForChannelId = (channelId: string) =>
|
export const makeSelectLivestreamsForChannelId = (channelId: string) =>
|
||||||
|
|
|
@ -127,3 +127,8 @@ export function getClaimTitle(claim: ?Claim) {
|
||||||
export const isStreamPlaceholderClaim = (claim: ?StreamClaim) => {
|
export const isStreamPlaceholderClaim = (claim: ?StreamClaim) => {
|
||||||
return claim ? Boolean(claim.value_type === 'stream' && !claim.value.source) : false;
|
return claim ? Boolean(claim.value_type === 'stream' && !claim.value.source) : false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getThumbnailFromClaim = (claim: ?Claim) => {
|
||||||
|
const thumbnail = claim && claim.value && claim.value.thumbnail;
|
||||||
|
return thumbnail && thumbnail.url ? thumbnail.url.trim().replace(/^http:\/\//i, 'https://') : undefined;
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in a new issue