Able to watch already downloaded videos
This commit is contained in:
parent
99e34343d6
commit
4754ba3da2
10 changed files with 300 additions and 28 deletions
|
@ -4,34 +4,45 @@ import lbryio from 'lbryio'
|
|||
import {
|
||||
selectCurrentUri,
|
||||
} from 'selectors/app'
|
||||
import {
|
||||
selectBalance,
|
||||
} from 'selectors/wallet'
|
||||
import {
|
||||
selectSearchTerm,
|
||||
selectCurrentUriCostInfo,
|
||||
selectCurrentUriFileInfo,
|
||||
} from 'selectors/content'
|
||||
import {
|
||||
selectCurrentResolvedUriClaimOutpoint,
|
||||
} from 'selectors/content'
|
||||
import {
|
||||
doOpenModal,
|
||||
} from 'actions/app'
|
||||
import batchActions from 'util/batchActions'
|
||||
|
||||
export function doResolveUri(dispatch, uri) {
|
||||
dispatch({
|
||||
type: types.RESOLVE_URI_STARTED,
|
||||
data: { uri }
|
||||
})
|
||||
|
||||
lbry.resolve({uri: uri}).then((resolutionInfo) => {
|
||||
const {
|
||||
claim,
|
||||
certificate,
|
||||
} = resolutionInfo
|
||||
|
||||
export function doResolveUri(uri) {
|
||||
return function(dispatch, getState) {
|
||||
dispatch({
|
||||
type: types.RESOLVE_URI_COMPLETED,
|
||||
data: {
|
||||
uri,
|
||||
type: types.RESOLVE_URI_STARTED,
|
||||
data: { uri }
|
||||
})
|
||||
|
||||
lbry.resolve({ uri }).then((resolutionInfo) => {
|
||||
const {
|
||||
claim,
|
||||
certificate,
|
||||
}
|
||||
} = resolutionInfo
|
||||
|
||||
dispatch({
|
||||
type: types.RESOLVE_URI_COMPLETED,
|
||||
data: {
|
||||
uri,
|
||||
claim,
|
||||
certificate,
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function doFetchCurrentUriFileInfo() {
|
||||
|
@ -40,8 +51,6 @@ export function doFetchCurrentUriFileInfo() {
|
|||
const uri = selectCurrentUri(state)
|
||||
const outpoint = selectCurrentResolvedUriClaimOutpoint(state)
|
||||
|
||||
console.log(outpoint)
|
||||
|
||||
dispatch({
|
||||
type: types.FETCH_FILE_INFO_STARTED,
|
||||
data: {
|
||||
|
@ -50,7 +59,7 @@ export function doFetchCurrentUriFileInfo() {
|
|||
}
|
||||
})
|
||||
|
||||
lbry.file_list({ outpoint }).then(fileInfo => {
|
||||
lbry.file_list({ outpoint }).then(([fileInfo]) => {
|
||||
dispatch({
|
||||
type: types.FETCH_FILE_INFO_COMPLETED,
|
||||
data: {
|
||||
|
@ -126,7 +135,7 @@ export function doFetchFeaturedContent() {
|
|||
})
|
||||
|
||||
Object.keys(Uris).forEach((category) => {
|
||||
Uris[category].forEach((uri) => doResolveUri(dispatch, uri))
|
||||
Uris[category].forEach((uri) => dispatch(doResolveUri(uri)))
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -161,3 +170,125 @@ export function doFetchCurrentUriCostInfo() {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function doUpdateLoadStatus(uri, outpoint) {
|
||||
return function(dispatch, getState) {
|
||||
const state = getState()
|
||||
|
||||
lbry.file_list({
|
||||
outpoint: outpoint,
|
||||
full_status: true,
|
||||
}).then(([fileInfo]) => {
|
||||
if(!fileInfo || fileInfo.written_bytes == 0) {
|
||||
// download hasn't started yet
|
||||
setTimeout(() => { dispatch(doUpdateLoadStatus(uri, outpoint)) }, 250)
|
||||
} else if (fileInfo.completed) {
|
||||
dispatch({
|
||||
type: types.DOWNLOADING_COMPLETED,
|
||||
data: {
|
||||
uri,
|
||||
fileInfo,
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// ready to play
|
||||
const {
|
||||
total_bytes,
|
||||
written_bytes,
|
||||
} = fileInfo
|
||||
const progress = (written_bytes / total_bytes) * 100
|
||||
|
||||
dispatch({
|
||||
type: types.DOWNLOADING_PROGRESSED,
|
||||
data: {
|
||||
uri,
|
||||
fileInfo,
|
||||
progress,
|
||||
}
|
||||
})
|
||||
setTimeout(() => { dispatch(doUpdateLoadStatus(uri, outpoint)) }, 250)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function doPlayVideo(uri) {
|
||||
return {
|
||||
type: types.PLAY_VIDEO_STARTED,
|
||||
data: { uri }
|
||||
}
|
||||
}
|
||||
|
||||
export function doDownloadFile(uri, streamInfo) {
|
||||
return function(dispatch, getState) {
|
||||
const state = getState()
|
||||
|
||||
dispatch({
|
||||
type: types.DOWNLOADING_STARTED,
|
||||
data: {
|
||||
uri,
|
||||
}
|
||||
})
|
||||
|
||||
lbryio.call('file', 'view', {
|
||||
uri: uri,
|
||||
outpoint: streamInfo.outpoint,
|
||||
claimId: streamInfo.claim_id,
|
||||
}).catch(() => {})
|
||||
dispatch(doUpdateLoadStatus(uri, streamInfo.outpoint))
|
||||
}
|
||||
}
|
||||
|
||||
export function doLoadVideo() {
|
||||
return function(dispatch, getState) {
|
||||
const state = getState()
|
||||
const uri = selectCurrentUri(state)
|
||||
|
||||
dispatch({
|
||||
type: types.LOADING_VIDEO_STARTED,
|
||||
data: {
|
||||
uri
|
||||
}
|
||||
})
|
||||
|
||||
lbry.get({ uri }).then(streamInfo => {
|
||||
if (streamInfo === null || typeof streamInfo !== 'object') {
|
||||
dispatch({
|
||||
type: types.LOADING_VIDEO_FAILED,
|
||||
data: { uri }
|
||||
})
|
||||
dispatch(doOpenModal('timedOut'))
|
||||
} else {
|
||||
dispatch(doDownloadFile(uri, streamInfo))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function doWatchVideo() {
|
||||
return function(dispatch, getState) {
|
||||
const state = getState()
|
||||
const uri = selectCurrentUri(state)
|
||||
const balance = selectBalance(state)
|
||||
const fileInfo = selectCurrentUriFileInfo(state)
|
||||
const costInfo = selectCurrentUriCostInfo(state)
|
||||
const { cost } = costInfo
|
||||
|
||||
// TODO does > 0 mean the file is downloaded? We don't have the total_bytes
|
||||
console.log(fileInfo)
|
||||
console.log(fileInfo.written_bytes)
|
||||
console.log('wtf')
|
||||
if (fileInfo.written_bytes > 0) {
|
||||
console.debug('this file is already downloaded')
|
||||
dispatch(doPlayVideo(uri))
|
||||
} else {
|
||||
if (cost > balance) {
|
||||
dispatch(doOpenModal('notEnoughCredits'))
|
||||
} else if (cost <= 0.01) {
|
||||
dispatch(doLoadVideo())
|
||||
} else {
|
||||
dispatch(doOpenModal('affirmPurchase'))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ export function doSearchContent(query) {
|
|||
contentName: result.name,
|
||||
claimId: result.channel_id || result.claim_id,
|
||||
})
|
||||
doResolveUri(dispatch, uri.split('://')[1])
|
||||
dispatch(doResolveUri(uri.split('://')[1]))
|
||||
})
|
||||
|
||||
dispatch({
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
import FileTile from './view'
|
||||
|
||||
const select = (state) => ({
|
||||
resolvedUris: selectResolvedUris(state),
|
||||
resolvedUris: (uri) => selectResolvedUris(state)[uri],
|
||||
})
|
||||
|
||||
const perform = (dispatch) => ({
|
||||
|
|
|
@ -13,7 +13,9 @@ class FileTile extends React.Component {
|
|||
uri,
|
||||
claim,
|
||||
} = this.props
|
||||
|
||||
const resolvedUri = this.props.resolvedUris(uri) || {}
|
||||
const claimInfo = resolvedUri.claim
|
||||
|
||||
if(!claim) {
|
||||
if (displayStyle == 'card') {
|
||||
return <FileCardStream uri={uri} />
|
||||
|
|
|
@ -47,6 +47,13 @@ export const FETCH_FILE_INFO_STARTED = 'FETCH_FILE_INFO_STARTED'
|
|||
export const FETCH_FILE_INFO_COMPLETED = 'FETCH_FILE_INFO_COMPLETED'
|
||||
export const FETCH_COST_INFO_STARTED = 'FETCH_COST_INFO_STARTED'
|
||||
export const FETCH_COST_INFO_COMPLETED = 'FETCH_COST_INFO_COMPLETED'
|
||||
export const LOADING_VIDEO_STARTED = 'LOADING_VIDEO_STARTED'
|
||||
export const LOADING_VIDEO_COMPLETED = 'LOADING_VIDEO_COMPLETED'
|
||||
export const LOADING_VIDEO_FAILED = 'LOADING_VIDEO_FAILED'
|
||||
export const DOWNLOADING_STARTED = 'DOWNLOADING_STARTED'
|
||||
export const DOWNLOADING_PROGRESSED = 'DOWNLOADING_PROGRESSED'
|
||||
export const DOWNLOADING_COMPLETED = 'DOWNLOADING_COMPLETED'
|
||||
export const PLAY_VIDEO_STARTED = 'PLAY_VIDEO_STARTED'
|
||||
|
||||
// Search
|
||||
export const SEARCH_STARTED = 'SEARCH_STARTED'
|
||||
|
|
|
@ -62,4 +62,4 @@ let DiscoverPage = React.createClass({
|
|||
}
|
||||
})
|
||||
|
||||
export default DiscoverPage;
|
||||
export default DiscoverPage;
|
|
@ -129,7 +129,7 @@ reducers[types.FETCH_FILE_INFO_COMPLETED] = function(state, action) {
|
|||
const fileInfos = Object.assign({}, state.fileInfos)
|
||||
const byUri = Object.assign({}, fileInfos.byUri)
|
||||
|
||||
byUri[uri] = fileInfo
|
||||
byUri[uri] = Object.assign({}, fileInfo)
|
||||
delete newFetchingFileInfos[uri]
|
||||
|
||||
const newFileInfos = Object.assign({}, fileInfos, {
|
||||
|
@ -178,6 +178,79 @@ reducers[types.FETCH_COST_INFO_COMPLETED] = function(state, action) {
|
|||
})
|
||||
}
|
||||
|
||||
reducers[types.LOADING_VIDEO_STARTED] = function(state, action) {
|
||||
const {
|
||||
uri,
|
||||
} = action.data
|
||||
const newLoading = Object.assign({}, state.loading)
|
||||
const newByUri = Object.assign({}, newLoading.byUri)
|
||||
|
||||
newByUri[uri] = true
|
||||
newLoading.byUri = newByUri
|
||||
|
||||
return Object.assign({}, state, {
|
||||
loading: newLoading,
|
||||
})
|
||||
}
|
||||
|
||||
reducers[types.LOADING_VIDEO_FAILED] = function(state, action) {
|
||||
const {
|
||||
uri,
|
||||
} = action.data
|
||||
const newLoading = Object.assign({}, state.loading)
|
||||
const newByUri = Object.assign({}, newLoading.byUri)
|
||||
|
||||
delete newByUri[uri]
|
||||
newLoading.byUri = newByUri
|
||||
|
||||
return Object.assign({}, state, {
|
||||
loading: newLoading,
|
||||
})
|
||||
}
|
||||
|
||||
reducers[types.DOWNLOADING_STARTED] = function(state, action) {
|
||||
const {
|
||||
uri,
|
||||
} = action.data
|
||||
const newDownloading = Object.assign({}, state.downloading)
|
||||
const newByUri = Object.assign({}, newDownloading.byUri)
|
||||
|
||||
newByUri[uri] = true
|
||||
newDownloading.byUri = newByUri
|
||||
|
||||
return Object.assign({}, state, {
|
||||
downloading: newDownloading,
|
||||
})
|
||||
}
|
||||
|
||||
reducers[types.DOWNLOADING_COMPLETED] =
|
||||
reducers[types.DOWNLOADING_PROGRESSED] = function(state, action) {
|
||||
const {
|
||||
uri,
|
||||
fileInfo,
|
||||
} = action.data
|
||||
const fileInfos = Object.assign({}, state.fileInfos)
|
||||
const byUri = Object.assign({}, fileInfos.byUri)
|
||||
|
||||
byUri[uri] = fileInfo
|
||||
fileInfos.byUri = byUri
|
||||
|
||||
return Object.assign({}, state, {
|
||||
fileInfos: fileInfos,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
reducers[types.PLAY_VIDEO_STARTED] = function(state, action) {
|
||||
const {
|
||||
uri,
|
||||
} = action.data
|
||||
|
||||
return Object.assign({}, state, {
|
||||
nowPlaying: uri,
|
||||
})
|
||||
}
|
||||
|
||||
export default function reducer(state = defaultState, action) {
|
||||
const handler = reducers[action.type];
|
||||
if (handler) return handler(state, action);
|
||||
|
|
|
@ -84,12 +84,23 @@ export const selectFetchingFileInfos = createSelector(
|
|||
(state) => state.fetchingFileInfos || {}
|
||||
)
|
||||
|
||||
export const selectCurrentUriFileReadyToPlay = createSelector(
|
||||
selectCurrentUriFileInfo,
|
||||
(fileInfo) => (fileInfo || {}).written_bytes > 0
|
||||
)
|
||||
|
||||
export const selectIsFetchingCurrentUriFileInfo = createSelector(
|
||||
selectFetchingFileInfos,
|
||||
selectCurrentUri,
|
||||
(fetching, uri) => !!fetching[uri]
|
||||
)
|
||||
|
||||
export const selectCurrentUriIsPlaying = createSelector(
|
||||
_selectState,
|
||||
selectCurrentUri,
|
||||
(state, uri) => state.nowPlaying == uri
|
||||
)
|
||||
|
||||
export const selectCostInfos = createSelector(
|
||||
_selectState,
|
||||
(state) => state.costInfos || {}
|
||||
|
@ -185,6 +196,22 @@ export const selectPublishedContent = createSelector(
|
|||
(state) => state.publishedContent || {}
|
||||
)
|
||||
|
||||
export const selectLoading = createSelector(
|
||||
_selectState,
|
||||
(state) => state.loading || {}
|
||||
)
|
||||
|
||||
export const selectLoadingByUri = createSelector(
|
||||
selectLoading,
|
||||
(loading) => loading.byUri || {}
|
||||
)
|
||||
|
||||
export const selectLoadingCurrentUri = createSelector(
|
||||
selectLoadingByUri,
|
||||
selectCurrentUri,
|
||||
(byUri, uri) => byUri[uri]
|
||||
)
|
||||
|
||||
export const shouldFetchPublishedContent = createSelector(
|
||||
selectDaemonReady,
|
||||
selectCurrentPage,
|
||||
|
|
|
@ -19,6 +19,28 @@ function isNotFunction(object) {
|
|||
return !isFunction(object);
|
||||
}
|
||||
|
||||
function createBulkThunkMiddleware() {
|
||||
return ({ dispatch, getState }) => next => (action) => {
|
||||
if (action.type === 'BATCH_ACTIONS') {
|
||||
action.actions.filter(isFunction).map(actionFn =>
|
||||
actionFn(dispatch, getState)
|
||||
)
|
||||
}
|
||||
return next(action)
|
||||
}
|
||||
}
|
||||
|
||||
function enableBatching(reducer) {
|
||||
return function batchingReducer(state, action) {
|
||||
switch (action.type) {
|
||||
case 'BATCH_ACTIONS':
|
||||
return action.actions.filter(isNotFunction).reduce(batchingReducer, state)
|
||||
default:
|
||||
return reducer(state, action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const reducers = redux.combineReducers({
|
||||
app: appReducer,
|
||||
content: contentReducer,
|
||||
|
@ -27,7 +49,8 @@ const reducers = redux.combineReducers({
|
|||
wallet: walletReducer,
|
||||
});
|
||||
|
||||
var middleware = [thunk]
|
||||
const bulkThunk = createBulkThunkMiddleware()
|
||||
const middleware = [thunk, bulkThunk]
|
||||
|
||||
if (env === 'development') {
|
||||
const logger = createLogger({
|
||||
|
@ -40,6 +63,6 @@ const createStoreWithMiddleware = redux.compose(
|
|||
redux.applyMiddleware(...middleware)
|
||||
)(redux.createStore);
|
||||
|
||||
const reduxStore = createStoreWithMiddleware(reducers);
|
||||
const reduxStore = createStoreWithMiddleware(enableBatching(reducers));
|
||||
|
||||
export default reduxStore;
|
||||
|
|
9
ui/js/util/batchActions.js
Normal file
9
ui/js/util/batchActions.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
// https://github.com/reactjs/redux/issues/911
|
||||
function batchActions(...actions) {
|
||||
return {
|
||||
type: 'BATCH_ACTIONS',
|
||||
actions: actions
|
||||
};
|
||||
}
|
||||
|
||||
export default batchActions
|
Loading…
Reference in a new issue