Refactor and fix websocket connection behavior

This commit is contained in:
Rafael 2022-04-29 10:54:14 -03:00 committed by Thomas Zarebczan
parent 05ec4e5fbe
commit 129a5819d9
10 changed files with 109 additions and 52 deletions

View file

@ -18,7 +18,7 @@ declare type LivestreamReplayItem = {
uploadedAt: string, // Date?
},
id: string,
}
};
declare type LivestreamReplayData = Array<LivestreamReplayItem>;
declare type LivestreamState = {
@ -29,7 +29,8 @@ declare type LivestreamState = {
activeLivestreamsLastFetchedDate: number,
activeLivestreamsLastFetchedOptions: {},
activeLivestreamInitialized: boolean,
}
socketConnectionById: { [id: string]: { connected: ?boolean, sub_category: ?string } },
};
declare type LivestreamInfo = {
[/* creatorId */ string]: {
@ -38,5 +39,5 @@ declare type LivestreamInfo = {
creatorId: string,
claimId: string,
claimUri: string,
}
}
},
};

View file

@ -19,11 +19,11 @@ import {
} from 'redux/selectors/content';
import { selectClientSetting } from 'redux/selectors/settings';
import { selectCostInfoForUri } from 'lbryinc';
import { doUriInitiatePlay, doSetPlayingUri } from 'redux/actions/content';
import { doUriInitiatePlay, doSetPlayingUri, doClearPlayingUri } from 'redux/actions/content';
import { doFetchRecommendedContent } from 'redux/actions/search';
import { withRouter } from 'react-router';
import { selectAppDrawerOpen } from 'redux/selectors/app';
import { selectIsActiveLivestreamForUri, selectCommentSocketConnected } from 'redux/selectors/livestream';
import { selectIsActiveLivestreamForUri, selectSocketConnectionForId } from 'redux/selectors/livestream';
import { doCommentSocketConnect, doCommentSocketDisconnect } from 'redux/actions/websocket';
import { isStreamPlaceholderClaim, getVideoClaimAspectRatio } from 'util/claim';
import FileRenderFloating from './view';
@ -57,7 +57,7 @@ const select = (state, props) => {
collectionId,
isCurrentClaimLive: selectIsActiveLivestreamForUri(state, uri),
videoAspectRatio: getVideoClaimAspectRatio(claim),
socketConnected: selectCommentSocketConnected(state),
socketConnection: selectSocketConnectionForId(state, claimId),
isLivestreamClaim: isStreamPlaceholderClaim(claim),
geoRestriction: selectGeoRestrictionForUri(state, uri),
appDrawerOpen: selectAppDrawerOpen(state),
@ -70,6 +70,7 @@ const perform = {
doSetPlayingUri,
doCommentSocketConnect,
doCommentSocketDisconnect,
doClearPlayingUri,
};
export default withRouter(connect(select, perform)(FileRenderFloating));

View file

@ -71,12 +71,13 @@ type Props = {
doSetPlayingUri: ({ uri?: ?string }) => void,
isCurrentClaimLive?: boolean,
videoAspectRatio: number,
socketConnected: boolean,
socketConnection: { connected: ?boolean },
isLivestreamClaim: boolean,
geoRestriction: ?GeoRestriction,
appDrawerOpen: boolean,
doCommentSocketConnect: (string, string, string) => void,
doCommentSocketDisconnect: (string, string) => void,
doClearPlayingUri: () => void,
};
export default function FileRenderFloating(props: Props) {
@ -97,7 +98,7 @@ export default function FileRenderFloating(props: Props) {
claimWasPurchased,
nextListUri,
previousListUri,
socketConnected,
socketConnection,
isLivestreamClaim,
doFetchRecommendedContent,
doUriInitiatePlay,
@ -108,6 +109,7 @@ export default function FileRenderFloating(props: Props) {
appDrawerOpen,
doCommentSocketConnect,
doCommentSocketDisconnect,
doClearPlayingUri,
} = props;
const isMobile = useIsMobile();
@ -235,14 +237,25 @@ export default function FileRenderFloating(props: Props) {
// Only connect if not yet connected, so for example clicked on an embed instead of accessing
// from the Livestream page
if (!socketConnected) doCommentSocketConnect(uri, channelName, claimId);
if (!socketConnection?.connected) {
doCommentSocketConnect(uri, channelName, claimId);
}
// This will be used to disconnect for every case, since this is the main player component
return () => doCommentSocketDisconnect(claimId, channelName);
// only listen to socketConnected on initial mount
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [channelUrl, claimId, doCommentSocketConnect, doCommentSocketDisconnect, isCurrentClaimLive, uri]);
return () => {
if (socketConnection?.connected) {
doCommentSocketDisconnect(claimId, channelName);
}
};
}, [
channelUrl,
claimId,
doCommentSocketConnect,
doCommentSocketDisconnect,
isCurrentClaimLive,
socketConnection,
uri,
]);
React.useEffect(() => {
if (playingPrimaryUri || playingUrl || noPlayerHeight) {
@ -310,6 +323,12 @@ export default function FileRenderFloating(props: Props) {
};
}, [playingUrl]);
React.useEffect(() => {
if (!primaryUri && !floatingPlayerEnabled && playingUrl && !playingUriSource) {
doClearPlayingUri();
}
}, [doClearPlayingUri, floatingPlayerEnabled, playingUriSource, playingUrl, primaryUri]);
if (
geoRestriction ||
!isPlayable ||

View file

@ -427,7 +427,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_FAILED = 'COMMENT_FETCH_BLOCKED_WORDS_FAILED';
export const COMMENT_FETCH_BLOCKED_WORDS_COMPLETED = 'COMMENT_FETCH_BLOCKED_WORDS_COMPLETED';
export const COMMENT_SOCKET_CONNECTED = 'COMMENT_SOCKET_CONNECTED';
export const SOCKET_CONNECTED_BY_ID = 'SOCKET_CONNECTED_BY_ID';
export const COMMENT_RECEIVED = 'COMMENT_RECEIVED';
export const COMMENT_SUPER_CHAT_LIST_STARTED = 'COMMENT_SUPER_CHAT_LIST_STARTED';
export const COMMENT_SUPER_CHAT_LIST_COMPLETED = 'COMMENT_SUPER_CHAT_LIST_COMPLETED';

View file

@ -4,13 +4,14 @@ import { doSetPrimaryUri } from 'redux/actions/content';
import { doUserSetReferrer } from 'redux/actions/user';
import { selectUserVerifiedEmail } from 'redux/selectors/user';
import { DISABLE_COMMENTS_TAG } from 'constants/tags';
import { doCommentSocketConnect } from 'redux/actions/websocket';
import { doCommentSocketConnect, doCommentSocketDisconnect } from 'redux/actions/websocket';
import { getChannelIdFromClaim } from 'util/claim';
import {
selectActiveLivestreamForChannel,
selectActiveLivestreamInitialized,
selectCommentSocketConnected,
selectSocketConnectionForId,
} from 'redux/selectors/livestream';
import { selectIsUriCurrentlyPlaying } from 'redux/selectors/content';
import { doFetchChannelLiveStatus } from 'redux/actions/livestream';
import LivestreamPage from './view';
@ -18,7 +19,7 @@ const select = (state, props) => {
const { uri } = props;
const claim = selectClaimForUri(state, uri);
const { canonical_url } = claim || {};
const { claim_id: claimId, canonical_url } = claim || {};
const channelClaimId = getChannelIdFromClaim(claim);
return {
@ -28,7 +29,8 @@ const select = (state, props) => {
chatDisabled: makeSelectTagInClaimOrChannelForUri(uri, DISABLE_COMMENTS_TAG)(state),
activeLivestreamForChannel: selectActiveLivestreamForChannel(state, channelClaimId),
activeLivestreamInitialized: selectActiveLivestreamInitialized(state),
socketConnected: selectCommentSocketConnected(state),
socketConnection: selectSocketConnectionForId(state, claimId),
isStreamPlaying: selectIsUriCurrentlyPlaying(state, uri),
};
};
@ -36,6 +38,7 @@ const perform = {
doSetPrimaryUri,
doUserSetReferrer,
doCommentSocketConnect,
doCommentSocketDisconnect,
doFetchChannelLiveStatus,
};

View file

@ -20,9 +20,11 @@ type Props = {
claim: StreamClaim,
isAuthenticated: boolean,
uri: string,
socketConnected: boolean,
socketConnection: { connected: ?boolean },
isStreamPlaying: boolean,
doSetPrimaryUri: (uri: ?string) => void,
doCommentSocketConnect: (string, string, string) => void,
doCommentSocketConnect: (uri: string, channelName: string, claimId: string) => void,
doCommentSocketDisconnect: (claimId: string, channelName: string) => void,
doFetchChannelLiveStatus: (string) => void,
doUserSetReferrer: (string) => void,
};
@ -38,15 +40,19 @@ export default function LivestreamPage(props: Props) {
claim,
isAuthenticated,
uri,
socketConnected,
socketConnection,
isStreamPlaying,
doSetPrimaryUri,
doCommentSocketConnect,
doCommentSocketDisconnect,
doFetchChannelLiveStatus,
doUserSetReferrer,
} = props;
const isMobile = useIsMobile();
const streamPlayingRef = React.useRef();
const [activeStreamUri, setActiveStreamUri] = React.useState(false);
const [showLivestream, setShowLivestream] = React.useState(false);
const [showScheduledInfo, setShowScheduledInfo] = React.useState(false);
@ -73,18 +79,33 @@ export default function LivestreamPage(props: Props) {
// On livestream page, only connect, fileRenderFloating will handle disconnect.
// (either by leaving page with floating player off, or by closing the player)
React.useEffect(() => {
if (!claim || socketConnected) return;
const { claim_id: claimId, signing_channel: channelClaim } = claim;
const channelName = channelClaim && formatLbryChannelName(channelUrl);
if (claimId && channelName) {
if (claimId && channelName && !socketConnection?.connected) {
doCommentSocketConnect(uri, channelName, claimId);
}
// only listen to socketConnected on initial mount
// willAutoplay mount only
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [channelUrl, claim, doCommentSocketConnect, uri]);
}, [channelUrl, claim, doCommentSocketConnect, doCommentSocketDisconnect, socketConnection, uri]);
React.useEffect(() => {
// use for unmount case without triggering render
streamPlayingRef.current = isStreamPlaying;
}, [isStreamPlaying]);
React.useEffect(() => {
return () => {
if (!streamPlayingRef.current) {
const { claim_id: claimId, signing_channel: channelClaim } = claim;
const channelName = channelClaim && formatLbryChannelName(channelUrl);
if (claimId && channelName) doCommentSocketDisconnect(claimId, channelName);
}
};
// only on unmount -> leave page
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const claimReleaseStartingSoonStatic = () =>
releaseTime.isBetween(moment(), moment().add(LIVESTREAM_STARTS_SOON_BUFFER, 'minutes'));

View file

@ -154,31 +154,30 @@ export const doCommentSocketConnect = (uri, channelName, claimId, subCategory) =
dispatch(doFetchChannelLiveStatus(channel_id));
}
},
'comment'
`${subCategory || COMMENT_WS_SUBCATEGORIES.VIEWER} comment`
);
dispatch(doSetSocketConnected(true));
dispatch(doSetSocketConnection(true, claimId, subCategory || COMMENT_WS_SUBCATEGORIES.VIEWER));
};
export const doCommentSocketDisconnect = (claimId, channelName) => (dispatch) => {
const url = getCommentSocketUrl(claimId, channelName);
export const doCommentSocketDisconnect = (claimId, channelName, subCategory) => (dispatch) => {
const url =
subCategory === COMMENT_WS_SUBCATEGORIES.COMMENTER
? getCommentSocketUrlForCommenter(claimId, channelName)
: getCommentSocketUrl(claimId, channelName);
dispatch(doSocketDisconnect(url));
dispatch(doSetSocketConnected(false));
dispatch(doSetSocketConnection(false, claimId, subCategory));
};
export const doCommentSocketConnectAsCommenter = (uri, channelName, claimId) => (dispatch) => {
export const doCommentSocketConnectAsCommenter = (uri, channelName, claimId) => (dispatch) =>
dispatch(doCommentSocketConnect(uri, channelName, claimId, COMMENT_WS_SUBCATEGORIES.COMMENTER));
};
export const doCommentSocketDisconnectAsCommenter = (claimId, channelName) => (dispatch) => {
const url = getCommentSocketUrlForCommenter(claimId, channelName);
export const doCommentSocketDisconnectAsCommenter = (claimId, channelName) => (dispatch) =>
dispatch(doCommentSocketDisconnect(claimId, channelName, COMMENT_WS_SUBCATEGORIES.COMMENTER));
dispatch(doSocketDisconnect(url));
};
export const doSetSocketConnected = (connected) => (dispatch) =>
export const doSetSocketConnection = (connected, id, subCategory) => (dispatch) =>
dispatch({
type: ACTIONS.COMMENT_SOCKET_CONNECTED,
data: { connected },
type: ACTIONS.SOCKET_CONNECTED_BY_ID,
data: { connected, sub_category: subCategory, id },
});

View file

@ -11,7 +11,7 @@ const defaultState: LivestreamState = {
activeLivestreamsLastFetchedDate: 0,
activeLivestreamsLastFetchedOptions: {},
activeLivestreamInitialized: false,
commentSocketConnected: false,
socketConnectionById: {},
};
function updateViewersById(activeLivestreams, originalState) {
@ -90,10 +90,14 @@ export default handleActions(
if (activeLivestreams) delete activeLivestreams[action.data.channelId];
return { ...state, activeLivestreams: Object.assign({}, activeLivestreams), activeLivestreamInitialized: true };
},
[ACTIONS.COMMENT_SOCKET_CONNECTED]: (state: CommentsState, action: any) => ({
...state,
commentSocketConnected: action.data.connected,
}),
[ACTIONS.SOCKET_CONNECTED_BY_ID]: (state: LivestreamState, action: any) => {
const { connected, sub_category, id: claimId } = action.data;
const socketConnectionById = Object.assign({}, state.socketConnectionById);
socketConnectionById[claimId] = { connected, sub_category };
return { ...state, socketConnectionById };
},
},
defaultState
);

View file

@ -30,8 +30,11 @@ export const selectListShuffle = (state: State) => selectState(state).shuffleLis
export const makeSelectIsPlaying = (uri: string) =>
createSelector(selectPrimaryUri, (primaryUri) => primaryUri === uri);
export const makeSelectIsUriCurrentlyPlaying = (uri: string) =>
createSelector(selectPlayingUri, (playingUri) => playingUri.uri === uri);
export const selectIsUriCurrentlyPlaying = createSelector(
(state, uri) => uri,
selectPlayingUri,
(uri, playingUri) => Boolean(playingUri.uri === uri)
);
export const makeSelectIsPlayerFloating = (location: UrlLocation) =>
createSelector(selectPrimaryUri, selectPlayingUri, (primaryUri, playingUri) => {

View file

@ -12,7 +12,13 @@ export const selectViewersById = (state: State) => selectState(state).viewersByI
export const selectActiveLivestreams = (state: State) => selectState(state).activeLivestreams;
export const selectFetchingActiveLivestreams = (state: State) => selectState(state).fetchingActiveLivestreams;
export const selectActiveLivestreamInitialized = (state: State) => selectState(state).activeLivestreamInitialized;
export const selectCommentSocketConnected = (state: State) => selectState(state).commentSocketConnected;
export const selectSocketConnectionById = (state: State) => selectState(state).socketConnectionById;
export const selectSocketConnectionForId = createSelector(
(state, claimId) => claimId,
selectSocketConnectionById,
(claimId, byId) => claimId && byId[claimId]
);
// select non-pending claims without sources for given channel
export const makeSelectLivestreamsForChannelId = (channelId: string) =>