Add persistent watch time setting. #7547
6 changed files with 63 additions and 14 deletions
|
@ -2327,5 +2327,7 @@
|
||||||
"* Note that as\n peer-to-peer software, your IP address and potentially other system information can be sent to other\n users, though this information is not stored permanently.": "* Note that as\n peer-to-peer software, your IP address and potentially other system information can be sent to other\n users, though this information is not stored permanently.",
|
"* Note that as\n peer-to-peer software, your IP address and potentially other system information can be sent to other\n users, though this information is not stored permanently.": "* Note that as\n peer-to-peer software, your IP address and potentially other system information can be sent to other\n users, though this information is not stored permanently.",
|
||||||
"Persist watch time": "Persist watch time",
|
"Persist watch time": "Persist watch time",
|
||||||
"Persist the watch time of the videos you have watched.": "Persist the watch time of the videos you have watched.",
|
"Persist the watch time of the videos you have watched.": "Persist the watch time of the videos you have watched.",
|
||||||
|
"Clearing...": "Clearing...",
|
||||||
|
"Cache cleared": "Cache cleared",
|
||||||
"--end--": "--end--"
|
"--end--": "--end--"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { doResolveUri } from 'redux/actions/claims';
|
import { doResolveUri } from 'redux/actions/claims';
|
||||||
import { makeSelectClaimForUri } from 'redux/selectors/claims';
|
import { makeSelectClaimForUri } from 'redux/selectors/claims';
|
||||||
import { makeSelectContentPositionForUri } from 'redux/selectors/content';
|
import { makeSelectContentWatchedPercentageForUri } from 'redux/selectors/content';
|
||||||
import CardMedia from './view';
|
import CardMedia from './view';
|
||||||
|
|
||||||
const select = (state, props) => {
|
const select = (state, props) => {
|
||||||
return {
|
return {
|
||||||
position: makeSelectContentPositionForUri(props.uri)(state),
|
watchedPercentage: makeSelectContentWatchedPercentageForUri(props.uri)(state),
|
||||||
claim: makeSelectClaimForUri(props.uri)(state),
|
claim: makeSelectClaimForUri(props.uri)(state),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -16,11 +16,20 @@ type Props = {
|
||||||
claim: ?StreamClaim,
|
claim: ?StreamClaim,
|
||||||
doResolveUri: (string) => void,
|
doResolveUri: (string) => void,
|
||||||
className?: string,
|
className?: string,
|
||||||
position: number | null,
|
watchedPercentage: number,
|
||||||
};
|
};
|
||||||
|
|
||||||
function FileThumbnail(props: Props) {
|
function FileThumbnail(props: Props) {
|
||||||
const { claim, uri, doResolveUri, thumbnail: rawThumbnail, children, allowGifs = false, className, position } = props;
|
const {
|
||||||
|
claim,
|
||||||
|
uri,
|
||||||
|
doResolveUri,
|
||||||
|
thumbnail: rawThumbnail,
|
||||||
|
children,
|
||||||
|
allowGifs = false,
|
||||||
|
className,
|
||||||
|
watchedPercentage,
|
||||||
|
} = props;
|
||||||
|
|
||||||
const passedThumbnail = rawThumbnail && rawThumbnail.trim().replace(/^http:\/\//i, 'https://');
|
const passedThumbnail = rawThumbnail && rawThumbnail.trim().replace(/^http:\/\//i, 'https://');
|
||||||
const thumbnailFromClaim =
|
const thumbnailFromClaim =
|
||||||
|
@ -30,12 +39,7 @@ function FileThumbnail(props: Props) {
|
||||||
const hasResolvedClaim = claim !== undefined;
|
const hasResolvedClaim = claim !== undefined;
|
||||||
const isGif = thumbnail && thumbnail.endsWith('gif');
|
const isGif = thumbnail && thumbnail.endsWith('gif');
|
||||||
|
|
||||||
const media = claim && claim.value && (claim.value.video || claim.value.audio);
|
const viewedBar = watchedPercentage && (
|
||||||
const duration = media && media.duration;
|
|
||||||
// When the position is -1, it means the user has watched the entire
|
|
||||||
// video and he/she is using the persist watch setting.
|
|
||||||
const watchedPercentage = position === -1 ? 100 : ((position || 0) / (duration || 1)) * 100 || 0;
|
|
||||||
const viewedBar = position && (
|
|
||||||
<div className="file-thumbnail__viewed-bar">
|
<div className="file-thumbnail__viewed-bar">
|
||||||
<div className="file-thumbnail__viewed-bar-progress" style={{ width: `${watchedPercentage}%` }} />
|
<div className="file-thumbnail__viewed-bar-progress" style={{ width: `${watchedPercentage}%` }} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -52,6 +52,18 @@ export default function SettingContent(props: Props) {
|
||||||
clearPlayingUri,
|
clearPlayingUri,
|
||||||
clearContentCache,
|
clearContentCache,
|
||||||
} = props;
|
} = props;
|
||||||
|
const [contentCacheCleared, setContentCacheCleared] = React.useState(false);
|
||||||
|
const [clearingContentCache, setClearingContentCache] = React.useState(false);
|
||||||
|
const onClearContentCache = React.useCallback(() => {
|
||||||
|
setClearingContentCache(true);
|
||||||
|
clearContentCache();
|
||||||
|
// Just a small timer to give the user a visual effect
|
||||||
|
// that the content is being cleared.
|
||||||
|
setTimeout(() => {
|
||||||
|
setClearingContentCache(false);
|
||||||
|
setContentCacheCleared(true);
|
||||||
|
}, 2000);
|
||||||
|
}, [setClearingContentCache, clearContentCache, setContentCacheCleared]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -119,9 +131,15 @@ export default function SettingContent(props: Props) {
|
||||||
<Button
|
<Button
|
||||||
button="primary"
|
button="primary"
|
||||||
icon={ICONS.ALERT}
|
icon={ICONS.ALERT}
|
||||||
label="Clear Cache"
|
label={
|
||||||
onClick={clearContentCache}
|
contentCacheCleared
|
||||||
disabled={false}
|
? __('Cache cleared')
|
||||||
|
: clearingContentCache
|
||||||
|
? __('Clearing...')
|
||||||
|
: __('Clear Cache')
|
||||||
|
}
|
||||||
|
onClick={onClearContentCache}
|
||||||
|
disabled={clearingContentCache || contentCacheCleared}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</SettingsRow>
|
</SettingsRow>
|
||||||
|
|
|
@ -246,7 +246,7 @@ export function clearPosition(uri: string) {
|
||||||
if (persistWatchTime) {
|
if (persistWatchTime) {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ACTIONS.SET_CONTENT_POSITION,
|
type: ACTIONS.SET_CONTENT_POSITION,
|
||||||
data: { claimId, outpoint, position: -1 },
|
data: { claimId, outpoint, position: null },
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,6 +63,31 @@ export const makeSelectContentPositionForUri = (uri: string) =>
|
||||||
return state.positions[id] ? state.positions[id][outpoint] : null;
|
return state.positions[id] ? state.positions[id][outpoint] : null;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const makeSelectContentWatchedPercentageForUri = (uri: string) =>
|
||||||
|
createSelector(selectState, makeSelectClaimForUri(uri), (state, claim) => {
|
||||||
|
if (!claim) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const media = claim.value && (claim.value.video || claim.value.audio);
|
||||||
|
if (!media) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const id = claim.claim_id;
|
||||||
|
if (!state.positions[id]) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const outpoint = `${claim.txid}:${claim.nout}`;
|
||||||
|
const watched = state.positions[id][outpoint];
|
||||||
|
// If the user turns on the persist watch setting,
|
||||||
|
// clearing the position will set it to null,
|
||||||
|
// which means the entire video has been watched.
|
||||||
|
if (watched === null) {
|
||||||
|
return 100;
|
||||||
|
}
|
||||||
|
const duration = media.duration;
|
||||||
|
return (watched / duration) * 100;
|
||||||
|
});
|
||||||
|
|
||||||
export const selectHistory = createSelector(selectState, (state) => state.history || []);
|
export const selectHistory = createSelector(selectState, (state) => state.history || []);
|
||||||
|
|
||||||
export const selectHistoryPageCount = createSelector(selectHistory, (history) =>
|
export const selectHistoryPageCount = createSelector(selectHistory, (history) =>
|
||||||
|
|
Loading…
Add table
Reference in a new issue