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

View file

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

View file

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

View file

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

View file

@ -20,9 +20,11 @@ type Props = {
claim: StreamClaim, claim: StreamClaim,
isAuthenticated: boolean, isAuthenticated: boolean,
uri: string, uri: string,
socketConnected: boolean, socketConnection: { connected: ?boolean },
isStreamPlaying: boolean,
doSetPrimaryUri: (uri: ?string) => void, 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, doFetchChannelLiveStatus: (string) => void,
doUserSetReferrer: (string) => void, doUserSetReferrer: (string) => void,
}; };
@ -38,15 +40,19 @@ export default function LivestreamPage(props: Props) {
claim, claim,
isAuthenticated, isAuthenticated,
uri, uri,
socketConnected, socketConnection,
isStreamPlaying,
doSetPrimaryUri, doSetPrimaryUri,
doCommentSocketConnect, doCommentSocketConnect,
doCommentSocketDisconnect,
doFetchChannelLiveStatus, doFetchChannelLiveStatus,
doUserSetReferrer, doUserSetReferrer,
} = props; } = props;
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const streamPlayingRef = React.useRef();
const [activeStreamUri, setActiveStreamUri] = React.useState(false); const [activeStreamUri, setActiveStreamUri] = React.useState(false);
const [showLivestream, setShowLivestream] = React.useState(false); const [showLivestream, setShowLivestream] = React.useState(false);
const [showScheduledInfo, setShowScheduledInfo] = 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. // On livestream page, only connect, fileRenderFloating will handle disconnect.
// (either by leaving page with floating player off, or by closing the player) // (either by leaving page with floating player off, or by closing the player)
React.useEffect(() => { React.useEffect(() => {
if (!claim || socketConnected) return;
const { claim_id: claimId, signing_channel: channelClaim } = claim; const { claim_id: claimId, signing_channel: channelClaim } = claim;
const channelName = channelClaim && formatLbryChannelName(channelUrl); const channelName = channelClaim && formatLbryChannelName(channelUrl);
if (claimId && channelName) { if (claimId && channelName && !socketConnection?.connected) {
doCommentSocketConnect(uri, channelName, claimId); doCommentSocketConnect(uri, channelName, claimId);
} }
// willAutoplay mount only
// only listen to socketConnected on initial mount
// eslint-disable-next-line react-hooks/exhaustive-deps // 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 = () => const claimReleaseStartingSoonStatic = () =>
releaseTime.isBetween(moment(), moment().add(LIVESTREAM_STARTS_SOON_BUFFER, 'minutes')); 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)); 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) => { export const doCommentSocketDisconnect = (claimId, channelName, subCategory) => (dispatch) => {
const url = getCommentSocketUrl(claimId, channelName); const url =
subCategory === COMMENT_WS_SUBCATEGORIES.COMMENTER
? getCommentSocketUrlForCommenter(claimId, channelName)
: getCommentSocketUrl(claimId, channelName);
dispatch(doSocketDisconnect(url)); 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)); dispatch(doCommentSocketConnect(uri, channelName, claimId, COMMENT_WS_SUBCATEGORIES.COMMENTER));
};
export const doCommentSocketDisconnectAsCommenter = (claimId, channelName) => (dispatch) => { export const doCommentSocketDisconnectAsCommenter = (claimId, channelName) => (dispatch) =>
const url = getCommentSocketUrlForCommenter(claimId, channelName); dispatch(doCommentSocketDisconnect(claimId, channelName, COMMENT_WS_SUBCATEGORIES.COMMENTER));
dispatch(doSocketDisconnect(url)); export const doSetSocketConnection = (connected, id, subCategory) => (dispatch) =>
};
export const doSetSocketConnected = (connected) => (dispatch) =>
dispatch({ dispatch({
type: ACTIONS.COMMENT_SOCKET_CONNECTED, type: ACTIONS.SOCKET_CONNECTED_BY_ID,
data: { connected }, data: { connected, sub_category: subCategory, id },
}); });

View file

@ -11,7 +11,7 @@ const defaultState: LivestreamState = {
activeLivestreamsLastFetchedDate: 0, activeLivestreamsLastFetchedDate: 0,
activeLivestreamsLastFetchedOptions: {}, activeLivestreamsLastFetchedOptions: {},
activeLivestreamInitialized: false, activeLivestreamInitialized: false,
commentSocketConnected: false, socketConnectionById: {},
}; };
function updateViewersById(activeLivestreams, originalState) { function updateViewersById(activeLivestreams, originalState) {
@ -90,10 +90,14 @@ 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) => ({ [ACTIONS.SOCKET_CONNECTED_BY_ID]: (state: LivestreamState, action: any) => {
...state, const { connected, sub_category, id: claimId } = action.data;
commentSocketConnected: action.data.connected,
}), const socketConnectionById = Object.assign({}, state.socketConnectionById);
socketConnectionById[claimId] = { connected, sub_category };
return { ...state, socketConnectionById };
},
}, },
defaultState defaultState
); );

View file

@ -30,8 +30,11 @@ export const selectListShuffle = (state: State) => selectState(state).shuffleLis
export const makeSelectIsPlaying = (uri: string) => export const makeSelectIsPlaying = (uri: string) =>
createSelector(selectPrimaryUri, (primaryUri) => primaryUri === uri); createSelector(selectPrimaryUri, (primaryUri) => primaryUri === uri);
export const makeSelectIsUriCurrentlyPlaying = (uri: string) => export const selectIsUriCurrentlyPlaying = createSelector(
createSelector(selectPlayingUri, (playingUri) => playingUri.uri === uri); (state, uri) => uri,
selectPlayingUri,
(uri, playingUri) => Boolean(playingUri.uri === uri)
);
export const makeSelectIsPlayerFloating = (location: UrlLocation) => export const makeSelectIsPlayerFloating = (location: UrlLocation) =>
createSelector(selectPrimaryUri, selectPlayingUri, (primaryUri, playingUri) => { 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 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; 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 // select non-pending claims without sources for given channel
export const makeSelectLivestreamsForChannelId = (channelId: string) => export const makeSelectLivestreamsForChannelId = (channelId: string) =>