Able to watch already downloaded videos
This commit is contained in:
parent
31513cc1a7
commit
674fafd31a
10 changed files with 300 additions and 28 deletions
|
@ -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'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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({
|
||||||
|
|
|
@ -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) => ({
|
||||||
|
|
|
@ -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') {
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
|
|
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