Livestream category improvements #7115
7 changed files with 144 additions and 55 deletions
12
flow-typed/livestream.js
vendored
12
flow-typed/livestream.js
vendored
|
@ -24,4 +24,16 @@ declare type LivestreamReplayData = Array<LivestreamReplayItem>;
|
|||
declare type LivestreamState = {
|
||||
fetchingById: {},
|
||||
viewersById: {},
|
||||
fetchingActiveLivestreams: boolean,
|
||||
activeLivestreams: ?LivestreamInfo,
|
||||
}
|
||||
|
||||
declare type LivestreamInfo = {
|
||||
[/* creatorId */ string]: {
|
||||
live: boolean,
|
||||
viewCount: number,
|
||||
creatorId: string,
|
||||
latestClaimId: string,
|
||||
latestClaimUri: string,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -352,3 +352,6 @@ export const FETCH_NO_SOURCE_CLAIMS_STARTED = 'FETCH_NO_SOURCE_CLAIMS_STARTED';
|
|||
export const FETCH_NO_SOURCE_CLAIMS_COMPLETED = 'FETCH_NO_SOURCE_CLAIMS_COMPLETED';
|
||||
export const FETCH_NO_SOURCE_CLAIMS_FAILED = 'FETCH_NO_SOURCE_CLAIMS_FAILED';
|
||||
export const VIEWERS_RECEIVED = 'VIEWERS_RECEIVED';
|
||||
export const FETCH_ACTIVE_LIVESTREAMS_STARTED = 'FETCH_ACTIVE_LIVESTREAMS_STARTED';
|
||||
export const FETCH_ACTIVE_LIVESTREAMS_FAILED = 'FETCH_ACTIVE_LIVESTREAMS_FAILED';
|
||||
export const FETCH_ACTIVE_LIVESTREAMS_COMPLETED = 'FETCH_ACTIVE_LIVESTREAMS_COMPLETED';
|
||||
|
|
|
@ -1,55 +0,0 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import { LIVESTREAM_LIVE_API } from 'constants/livestream';
|
||||
|
||||
/**
|
||||
* Gets latest livestream info list. Returns null (instead of a blank object)
|
||||
* when there are no active livestreams.
|
||||
*
|
||||
* @param minViewers
|
||||
* @param refreshMs
|
||||
* @returns {{livestreamMap: null, loading: boolean}}
|
||||
*/
|
||||
export default function useGetLivestreams(minViewers: number = 0, refreshMs: number = 0) {
|
||||
const [loading, setLoading] = React.useState(true);
|
||||
const [livestreamMap, setLivestreamMap] = React.useState(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
function checkCurrentLivestreams() {
|
||||
fetch(LIVESTREAM_LIVE_API)
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
setLoading(false);
|
||||
if (!res.data) {
|
||||
setLivestreamMap(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const livestreamMap = res.data.reduce((acc, curr) => {
|
||||
if (curr.viewCount >= minViewers) {
|
||||
acc[curr.claimId] = curr;
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
setLivestreamMap(livestreamMap);
|
||||
})
|
||||
.catch((err) => {
|
||||
setLoading(false);
|
||||
});
|
||||
}
|
||||
|
||||
checkCurrentLivestreams();
|
||||
|
||||
if (refreshMs > 0) {
|
||||
let fetchInterval = setInterval(checkCurrentLivestreams, refreshMs);
|
||||
return () => {
|
||||
if (fetchInterval) {
|
||||
clearInterval(fetchInterval);
|
||||
}
|
||||
};
|
||||
}
|
||||
}, []);
|
||||
|
||||
return { livestreamMap, loading };
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
// @flow
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
import { doClaimSearch } from 'lbry-redux';
|
||||
import { LIVESTREAM_LIVE_API } from 'constants/livestream';
|
||||
|
||||
export const doFetchNoSourceClaims = (channelId: string) => async (dispatch: Dispatch, getState: GetState) => {
|
||||
dispatch({
|
||||
|
@ -31,3 +32,75 @@ export const doFetchNoSourceClaims = (channelId: string) => async (dispatch: Dis
|
|||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const doFetchActiveLivestreams = () => {
|
||||
return async (dispatch: Dispatch) => {
|
||||
dispatch({
|
||||
type: ACTIONS.FETCH_ACTIVE_LIVESTREAMS_STARTED,
|
||||
});
|
||||
|
||||
fetch(LIVESTREAM_LIVE_API)
|
||||
.then((res) => res.json())
|
||||
.then((res) => {
|
||||
if (!res.data) {
|
||||
dispatch({
|
||||
type: ACTIONS.FETCH_ACTIVE_LIVESTREAMS_FAILED,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const activeLivestreams: LivestreamInfo = res.data.reduce((acc, curr) => {
|
||||
acc[curr.claimId] = {
|
||||
live: curr.live,
|
||||
viewCount: curr.viewCount,
|
||||
creatorId: curr.claimId,
|
||||
};
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
dispatch(
|
||||
// ** Creators can have multiple livestream claims (each with unique
|
||||
// chat), and all of them will play the same stream when creator goes
|
||||
// live. The UI usually just wants to report the latest claim, so we
|
||||
// query that store it in `latestClaimUri`.
|
||||
doClaimSearch({
|
||||
page_size: 50,
|
||||
has_no_source: true,
|
||||
channel_ids: Object.keys(activeLivestreams),
|
||||
claim_type: ['stream'],
|
||||
order_by: ['release_time'], // **
|
||||
limit_claims_per_channel: 1, // **
|
||||
no_totals: true,
|
||||
})
|
||||
)
|
||||
.then((resolveInfo) => {
|
||||
Object.values(resolveInfo).forEach((x) => {
|
||||
// $FlowFixMe
|
||||
const channelId = x.stream.signing_channel.claim_id;
|
||||
activeLivestreams[channelId] = {
|
||||
...activeLivestreams[channelId],
|
||||
// $FlowFixMe
|
||||
latestClaimId: x.stream.claim_id,
|
||||
// $FlowFixMe
|
||||
latestClaimUri: x.stream.canonical_url,
|
||||
};
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: ACTIONS.FETCH_ACTIVE_LIVESTREAMS_COMPLETED,
|
||||
data: activeLivestreams,
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
dispatch({
|
||||
type: ACTIONS.FETCH_ACTIVE_LIVESTREAMS_FAILED,
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
dispatch({
|
||||
type: ACTIONS.FETCH_ACTIVE_LIVESTREAMS_FAILED,
|
||||
});
|
||||
});
|
||||
};
|
||||
};
|
||||
|
|
|
@ -5,6 +5,8 @@ import { handleActions } from 'util/redux-utils';
|
|||
const defaultState: LivestreamState = {
|
||||
fetchingById: {},
|
||||
viewersById: {},
|
||||
fetchingActiveLivestreams: false,
|
||||
activeLivestreams: null,
|
||||
};
|
||||
|
||||
export default handleActions(
|
||||
|
@ -36,6 +38,16 @@ export default handleActions(
|
|||
newViewersById[claimId] = connected;
|
||||
return { ...state, viewersById: newViewersById };
|
||||
},
|
||||
[ACTIONS.FETCH_ACTIVE_LIVESTREAMS_STARTED]: (state: LivestreamState) => {
|
||||
return { ...state, fetchingActiveLivestreams: true };
|
||||
},
|
||||
[ACTIONS.FETCH_ACTIVE_LIVESTREAMS_FAILED]: (state: LivestreamState) => {
|
||||
return { ...state, fetchingActiveLivestreams: false };
|
||||
},
|
||||
[ACTIONS.FETCH_ACTIVE_LIVESTREAMS_COMPLETED]: (state: LivestreamState, action: any) => {
|
||||
const activeLivestreams: LivestreamInfo = action.data;
|
||||
return { ...state, fetchingActiveLivestreams: false, activeLivestreams };
|
||||
},
|
||||
},
|
||||
defaultState
|
||||
);
|
||||
|
|
|
@ -40,3 +40,23 @@ export const makeSelectPendingLivestreamsForChannelId = (channelId: string) =>
|
|||
claim.signing_channel.claim_id === channelId
|
||||
);
|
||||
});
|
||||
|
||||
export const selectActiveLivestreams = createSelector(selectState, (state) => state.activeLivestreams);
|
||||
|
||||
export const makeSelectIsActiveLivestream = (uri: string) =>
|
||||
createSelector(selectState, (state) => {
|
||||
const activeLivestreamValues = (state.activeLivestreams && Object.values(state.activeLivestreams)) || [];
|
||||
// $FlowFixMe
|
||||
return Boolean(activeLivestreamValues.find((v) => v.latestClaimUri === uri));
|
||||
});
|
||||
|
||||
export const makeSelectActiveLivestreamUris = (uri: string) =>
|
||||
createSelector(selectState, (state) => {
|
||||
const activeLivestreamValues = (state.activeLivestreams && Object.values(state.activeLivestreams)) || [];
|
||||
const uris = [];
|
||||
activeLivestreamValues.forEach((v) => {
|
||||
// $FlowFixMe
|
||||
if (v.latestClaimUri) uris.push(v.latestClaimUri);
|
||||
});
|
||||
return uris;
|
||||
});
|
||||
|
|
24
ui/util/livestream.js
Normal file
24
ui/util/livestream.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
// @flow
|
||||
|
||||
/**
|
||||
* Helper to extract livestream claim uris from the output of
|
||||
* `selectActiveLivestreams`.
|
||||
*
|
||||
* @param activeLivestreams Object obtained from `selectActiveLivestreams`.
|
||||
* @param channelIds List of channel IDs to filter the results with.
|
||||
* @returns {[]|Array<*>}
|
||||
*/
|
||||
export function getLivestreamUris(activeLivestreams: ?LivestreamInfo, channelIds: ?Array<string>) {
|
||||
let values = (activeLivestreams && Object.values(activeLivestreams)) || [];
|
||||
|
||||
if (channelIds && channelIds.length > 0) {
|
||||
// $FlowFixMe
|
||||
values = values.filter((v) => channelIds.includes(v.creatorId) && Boolean(v.latestClaimUri));
|
||||
} else {
|
||||
// $FlowFixMe
|
||||
values = values.filter((v) => Boolean(v.latestClaimUri));
|
||||
}
|
||||
|
||||
// $FlowFixMe
|
||||
return values.map((v) => v.latestClaimUri);
|
||||
}
|
Loading…
Reference in a new issue