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 = {
|
declare type LivestreamState = {
|
||||||
fetchingById: {},
|
fetchingById: {},
|
||||||
viewersById: {},
|
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_COMPLETED = 'FETCH_NO_SOURCE_CLAIMS_COMPLETED';
|
||||||
export const FETCH_NO_SOURCE_CLAIMS_FAILED = 'FETCH_NO_SOURCE_CLAIMS_FAILED';
|
export const FETCH_NO_SOURCE_CLAIMS_FAILED = 'FETCH_NO_SOURCE_CLAIMS_FAILED';
|
||||||
export const VIEWERS_RECEIVED = 'VIEWERS_RECEIVED';
|
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
|
// @flow
|
||||||
import * as ACTIONS from 'constants/action_types';
|
import * as ACTIONS from 'constants/action_types';
|
||||||
import { doClaimSearch } from 'lbry-redux';
|
import { doClaimSearch } from 'lbry-redux';
|
||||||
|
import { LIVESTREAM_LIVE_API } from 'constants/livestream';
|
||||||
|
|
||||||
export const doFetchNoSourceClaims = (channelId: string) => async (dispatch: Dispatch, getState: GetState) => {
|
export const doFetchNoSourceClaims = (channelId: string) => async (dispatch: Dispatch, getState: GetState) => {
|
||||||
dispatch({
|
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 = {
|
const defaultState: LivestreamState = {
|
||||||
fetchingById: {},
|
fetchingById: {},
|
||||||
viewersById: {},
|
viewersById: {},
|
||||||
|
fetchingActiveLivestreams: false,
|
||||||
|
activeLivestreams: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default handleActions(
|
export default handleActions(
|
||||||
|
@ -36,6 +38,16 @@ export default handleActions(
|
||||||
newViewersById[claimId] = connected;
|
newViewersById[claimId] = connected;
|
||||||
return { ...state, viewersById: newViewersById };
|
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
|
defaultState
|
||||||
);
|
);
|
||||||
|
|
|
@ -40,3 +40,23 @@ export const makeSelectPendingLivestreamsForChannelId = (channelId: string) =>
|
||||||
claim.signing_channel.claim_id === channelId
|
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