Able to watch already downloaded videos

This commit is contained in:
6ea86b96 2017-04-27 00:08:26 +07:00 committed by Jeremy Kauffman
parent 31513cc1a7
commit 674fafd31a
10 changed files with 300 additions and 28 deletions

View file

@ -4,20 +4,30 @@ import lbryio from 'lbryio'
import { import {
selectCurrentUri, selectCurrentUri,
} from 'selectors/app' } from 'selectors/app'
import {
selectBalance,
} from 'selectors/wallet'
import { import {
selectSearchTerm, selectSearchTerm,
selectCurrentUriCostInfo,
selectCurrentUriFileInfo,
} from 'selectors/content' } from 'selectors/content'
import { import {
selectCurrentResolvedUriClaimOutpoint, selectCurrentResolvedUriClaimOutpoint,
} from 'selectors/content' } from 'selectors/content'
import {
doOpenModal,
} from 'actions/app'
import batchActions from 'util/batchActions'
export function doResolveUri(dispatch, uri) { export function doResolveUri(uri) {
return function(dispatch, getState) {
dispatch({ dispatch({
type: types.RESOLVE_URI_STARTED, type: types.RESOLVE_URI_STARTED,
data: { uri } data: { uri }
}) })
lbry.resolve({uri: uri}).then((resolutionInfo) => { lbry.resolve({ uri }).then((resolutionInfo) => {
const { const {
claim, claim,
certificate, certificate,
@ -33,6 +43,7 @@ export function doResolveUri(dispatch, uri) {
}) })
}) })
} }
}
export function doFetchCurrentUriFileInfo() { export function doFetchCurrentUriFileInfo() {
return function(dispatch, getState) { return function(dispatch, getState) {
@ -40,8 +51,6 @@ export function doFetchCurrentUriFileInfo() {
const uri = selectCurrentUri(state) const uri = selectCurrentUri(state)
const outpoint = selectCurrentResolvedUriClaimOutpoint(state) const outpoint = selectCurrentResolvedUriClaimOutpoint(state)
console.log(outpoint)
dispatch({ dispatch({
type: types.FETCH_FILE_INFO_STARTED, type: types.FETCH_FILE_INFO_STARTED,
data: { data: {
@ -50,7 +59,7 @@ export function doFetchCurrentUriFileInfo() {
} }
}) })
lbry.file_list({ outpoint }).then(fileInfo => { lbry.file_list({ outpoint }).then(([fileInfo]) => {
dispatch({ dispatch({
type: types.FETCH_FILE_INFO_COMPLETED, type: types.FETCH_FILE_INFO_COMPLETED,
data: { data: {
@ -126,7 +135,7 @@ export function doFetchFeaturedContent() {
}) })
Object.keys(Uris).forEach((category) => { 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'))
}
}
}
}

View file

@ -32,7 +32,7 @@ export function doSearchContent(query) {
contentName: result.name, contentName: result.name,
claimId: result.channel_id || result.claim_id, claimId: result.channel_id || result.claim_id,
}) })
doResolveUri(dispatch, uri.split('://')[1]) dispatch(doResolveUri(uri.split('://')[1]))
}) })
dispatch({ dispatch({

View file

@ -8,7 +8,7 @@ import {
import FileTile from './view' import FileTile from './view'
const select = (state) => ({ const select = (state) => ({
resolvedUris: selectResolvedUris(state), resolvedUris: (uri) => selectResolvedUris(state)[uri],
}) })
const perform = (dispatch) => ({ const perform = (dispatch) => ({

View file

@ -13,6 +13,8 @@ class FileTile extends React.Component {
uri, uri,
claim, claim,
} = this.props } = this.props
const resolvedUri = this.props.resolvedUris(uri) || {}
const claimInfo = resolvedUri.claim
if(!claim) { if(!claim) {
if (displayStyle == 'card') { if (displayStyle == 'card') {

View file

@ -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_FILE_INFO_COMPLETED = 'FETCH_FILE_INFO_COMPLETED'
export const FETCH_COST_INFO_STARTED = 'FETCH_COST_INFO_STARTED' export const FETCH_COST_INFO_STARTED = 'FETCH_COST_INFO_STARTED'
export const FETCH_COST_INFO_COMPLETED = 'FETCH_COST_INFO_COMPLETED' 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 // Search
export const SEARCH_STARTED = 'SEARCH_STARTED' export const SEARCH_STARTED = 'SEARCH_STARTED'

View file

@ -129,7 +129,7 @@ reducers[types.FETCH_FILE_INFO_COMPLETED] = function(state, action) {
const fileInfos = Object.assign({}, state.fileInfos) const fileInfos = Object.assign({}, state.fileInfos)
const byUri = Object.assign({}, fileInfos.byUri) const byUri = Object.assign({}, fileInfos.byUri)
byUri[uri] = fileInfo byUri[uri] = Object.assign({}, fileInfo)
delete newFetchingFileInfos[uri] delete newFetchingFileInfos[uri]
const newFileInfos = Object.assign({}, fileInfos, { 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) { export default function reducer(state = defaultState, action) {
const handler = reducers[action.type]; const handler = reducers[action.type];
if (handler) return handler(state, action); if (handler) return handler(state, action);

View file

@ -84,12 +84,23 @@ export const selectFetchingFileInfos = createSelector(
(state) => state.fetchingFileInfos || {} (state) => state.fetchingFileInfos || {}
) )
export const selectCurrentUriFileReadyToPlay = createSelector(
selectCurrentUriFileInfo,
(fileInfo) => (fileInfo || {}).written_bytes > 0
)
export const selectIsFetchingCurrentUriFileInfo = createSelector( export const selectIsFetchingCurrentUriFileInfo = createSelector(
selectFetchingFileInfos, selectFetchingFileInfos,
selectCurrentUri, selectCurrentUri,
(fetching, uri) => !!fetching[uri] (fetching, uri) => !!fetching[uri]
) )
export const selectCurrentUriIsPlaying = createSelector(
_selectState,
selectCurrentUri,
(state, uri) => state.nowPlaying == uri
)
export const selectCostInfos = createSelector( export const selectCostInfos = createSelector(
_selectState, _selectState,
(state) => state.costInfos || {} (state) => state.costInfos || {}
@ -185,6 +196,22 @@ export const selectPublishedContent = createSelector(
(state) => state.publishedContent || {} (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( export const shouldFetchPublishedContent = createSelector(
selectDaemonReady, selectDaemonReady,
selectCurrentPage, selectCurrentPage,

View file

@ -19,6 +19,28 @@ function isNotFunction(object) {
return !isFunction(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({ const reducers = redux.combineReducers({
app: appReducer, app: appReducer,
content: contentReducer, content: contentReducer,
@ -27,7 +49,8 @@ const reducers = redux.combineReducers({
wallet: walletReducer, wallet: walletReducer,
}); });
var middleware = [thunk] const bulkThunk = createBulkThunkMiddleware()
const middleware = [thunk, bulkThunk]
if (env === 'development') { if (env === 'development') {
const logger = createLogger({ const logger = createLogger({
@ -40,6 +63,6 @@ const createStoreWithMiddleware = redux.compose(
redux.applyMiddleware(...middleware) redux.applyMiddleware(...middleware)
)(redux.createStore); )(redux.createStore);
const reduxStore = createStoreWithMiddleware(reducers); const reduxStore = createStoreWithMiddleware(enableBatching(reducers));
export default reduxStore; export default reduxStore;

View file

@ -0,0 +1,9 @@
// https://github.com/reactjs/redux/issues/911
function batchActions(...actions) {
return {
type: 'BATCH_ACTIONS',
actions: actions
};
}
export default batchActions