Merge pull request #890 from daovist/video-state

track and manage video state
This commit is contained in:
Liam Cardenas 2018-01-05 16:23:55 -08:00 committed by GitHub
commit eed68aae91
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 165 additions and 33 deletions

View file

@ -87,7 +87,8 @@ class FileCard extends React.PureComponent {
</div>
<div className="card__subtitle">
<span className="card__indicators card--file-subtitle">
<FilePrice uri={uri} /> {isRewardContent && <Icon icon={icons.FEATURED} leftPad />}{' '}
<FilePrice uri={uri} />{' '}
{isRewardContent && <Icon icon={icons.FEATURED} leftPad />}{' '}
{fileInfo && <Icon icon={icons.LOCAL} leftPad />}
</span>
<span className="card--file-subtitle">

View file

@ -9,7 +9,7 @@ import { makeSelectCostInfoForUri } from 'redux/selectors/cost_info';
import { doFetchAvailability } from 'redux/actions/availability';
import { doOpenFileInShell } from 'redux/actions/file_info';
import { doPurchaseUri, doStartDownload } from 'redux/actions/content';
import { setVideoPause } from 'redux/actions/video';
import { doPause } from 'redux/actions/media';
import FileDownloadLink from './view';
const select = (state, props) => ({
@ -25,7 +25,7 @@ const perform = dispatch => ({
openInShell: path => dispatch(doOpenFileInShell(path)),
purchaseUri: uri => dispatch(doPurchaseUri(uri)),
restartDownload: (uri, outpoint) => dispatch(doStartDownload(uri, outpoint)),
setVideoPause: val => dispatch(setVideoPause(val)),
doPause: () => dispatch(doPause()),
});
export default connect(select, perform)(FileDownloadLink);

View file

@ -43,12 +43,12 @@ class FileDownloadLink extends React.PureComponent {
purchaseUri,
costInfo,
loading,
setVideoPause,
doPause,
} = this.props;
const openFile = () => {
openInShell(fileInfo.download_path);
setVideoPause(true);
doPause();
};
if (loading || downloading) {

View file

@ -1,22 +1,30 @@
import React from 'react';
import { connect } from 'react-redux';
import { doChangeVolume } from 'redux/actions/app';
import { selectVolume } from 'redux/selectors/app';
import { doPlayUri, doSetPlayingUri } from 'redux/actions/content';
import { makeSelectMetadataForUri, makeSelectContentTypeForUri } from 'redux/selectors/claims';
import { setVideoPause } from 'redux/actions/video';
import React from "react";
import { connect } from "react-redux";
import { doChangeVolume } from "redux/actions/app";
import { selectVolume } from "redux/selectors/app";
import { doPlayUri, doSetPlayingUri } from "redux/actions/content";
import { doPlay, doPause, savePosition } from "redux/actions/media";
import {
makeSelectMetadataForUri,
makeSelectContentTypeForUri,
} from "redux/selectors/claims";
import {
makeSelectFileInfoForUri,
makeSelectLoadingForUri,
makeSelectDownloadingForUri,
} from 'redux/selectors/file_info';
import { makeSelectCostInfoForUri } from 'redux/selectors/cost_info';
import { selectShowNsfw } from 'redux/selectors/settings';
import { selectVideoPause } from 'redux/selectors/video';
import Video from './view';
import { selectPlayingUri } from 'redux/selectors/content';
} from "redux/selectors/file_info";
import { makeSelectCostInfoForUri } from "redux/selectors/cost_info";
import { selectShowNsfw } from "redux/selectors/settings";
import {
selectMediaPaused,
makeSelectMediaPositionForUri,
} from "redux/selectors/media";
import Video from "./view";
import { selectPlayingUri } from "redux/selectors/content";
import { makeSelectClaimForUri } from "redux/selectors/claims";
const select = (state, props) => ({
claim: makeSelectClaimForUri(props.uri)(state),
costInfo: makeSelectCostInfoForUri(props.uri)(state),
fileInfo: makeSelectFileInfoForUri(props.uri)(state),
metadata: makeSelectMetadataForUri(props.uri)(state),
@ -26,14 +34,18 @@ const select = (state, props) => ({
playingUri: selectPlayingUri(state),
contentType: makeSelectContentTypeForUri(props.uri)(state),
volume: selectVolume(state),
videoPause: selectVideoPause(state),
mediaPaused: selectMediaPaused(state),
mediaPosition: makeSelectMediaPositionForUri(props.uri)(state),
});
const perform = dispatch => ({
play: uri => dispatch(doPlayUri(uri)),
cancelPlay: () => dispatch(doSetPlayingUri(null)),
changeVolume: volume => dispatch(doChangeVolume(volume)),
setVideoPause: val => dispatch(setVideoPause(val)),
doPlay: () => dispatch(doPlay()),
doPause: () => dispatch(doPause()),
savePosition: (claimId, position) =>
dispatch(savePosition(claimId, position)),
});
export default connect(select, perform)(Video);

View file

@ -21,16 +21,24 @@ class VideoPlayer extends React.PureComponent {
this.togglePlayListener = this.togglePlay.bind(this);
}
componentWillReceiveProps(nextProps) {
if (nextProps.videoPause) {
this.refs.media.children[0].pause();
this.props.setVideoPause(false);
}
componentWillReceiveProps(next) {
const el = this.refs.media.children[0];
if (!this.props.paused && next.paused && !el.paused) el.pause();
}
componentDidMount() {
const container = this.refs.media;
const { contentType, downloadPath, mediaType, changeVolume, volume } = this.props;
const {
contentType,
downloadPath,
mediaType,
changeVolume,
volume,
position,
claim,
uri,
} = this.props;
const loadedMetadata = e => {
this.setState({ hasMetadata: true, startedPlaying: true });
this.refs.media.children[0].play();
@ -61,6 +69,12 @@ class VideoPlayer extends React.PureComponent {
document.addEventListener('keydown', this.togglePlayListener);
const mediaElement = this.refs.media.children[0];
if (mediaElement) {
mediaElement.currentTime = position || 0;
mediaElement.addEventListener('play', () => this.props.doPlay());
mediaElement.addEventListener('pause', () => this.props.doPause());
mediaElement.addEventListener('timeupdate', () =>
this.props.savePosition(claim.claim_id, mediaElement.currentTime)
);
mediaElement.addEventListener('click', this.togglePlayListener);
mediaElement.addEventListener('loadedmetadata', loadedMetadata.bind(this), {
once: true,
@ -79,6 +93,7 @@ class VideoPlayer extends React.PureComponent {
if (mediaElement) {
mediaElement.removeEventListener('click', this.togglePlayListener);
}
this.props.doPause();
}
renderAudio(container, autoplay) {

View file

@ -51,9 +51,13 @@ class Video extends React.PureComponent {
contentType,
changeVolume,
volume,
claim,
uri,
videoPause,
setVideoPause,
doPlay,
doPause,
savePosition,
mediaPaused,
mediaPosition,
} = this.props;
const isPlaying = playingUri === uri;
@ -103,8 +107,13 @@ class Video extends React.PureComponent {
downloadCompleted={fileInfo.completed}
changeVolume={changeVolume}
volume={volume}
videoPause={videoPause}
setVideoPause={setVideoPause}
doPlay={doPlay}
doPause={doPause}
savePosition={savePosition}
claim={claim}
uri={uri}
paused={mediaPaused}
position={mediaPosition}
/>
))}
{!isPlaying && (

View file

@ -157,3 +157,8 @@ export const HAS_FETCHED_SUBSCRIPTIONS = 'HAS_FETCHED_SUBSCRIPTIONS';
// Video controls
export const SET_VIDEO_PAUSE = 'SET_VIDEO_PAUSE';
// Media controls
export const MEDIA_PLAY = 'MEDIA_PLAY';
export const MEDIA_PAUSE = 'MEDIA_PAUSE';
export const MEDIA_POSITION = 'MEDIA_POSITION';

View file

@ -279,7 +279,9 @@ export function doLoadVideo(uri) {
});
dispatch(
doAlertError(
`Failed to download ${uri}, please try again. If this problem persists, visit https://lbry.io/faq/support for support.`
`Failed to download ${
uri
}, please try again. If this problem persists, visit https://lbry.io/faq/support for support.`
)
);
});

View file

@ -0,0 +1,30 @@
// @flow
import * as actions from "constants/action_types";
import type { Action, Dispatch } from "redux/reducers/media";
import lbry from "lbry";
import { makeSelectClaimForUri } from "redux/selectors/claims";
export const doPlay = () => (dispatch: Dispatch) =>
dispatch({
type: actions.MEDIA_PLAY,
});
export const doPause = () => (dispatch: Dispatch) =>
dispatch({
type: actions.MEDIA_PAUSE,
});
export function savePosition(claimId: String, position: Number) {
return function(dispatch: Dispatch, getState: Function) {
const state = getState();
const claim = state.claims.byId[claimId];
const outpoint = `${claim.txid}:${claim.nout}`;
dispatch({
type: actions.MEDIA_POSITION,
data: {
outpoint,
position,
},
});
};
}

View file

@ -0,0 +1,41 @@
// @flow
import * as actions from "constants/action_types";
import { handleActions } from "util/redux-utils";
export type MediaState = {
paused: Boolean,
positions: {
[string]: number,
},
};
export type Action = any;
export type Dispatch = (action: Action) => any;
const defaultState = { paused: true, positions: {} };
export default handleActions(
{
[actions.MEDIA_PLAY]: (state: MediaState, action: Action) => ({
...state,
paused: false,
}),
[actions.MEDIA_PAUSE]: (state: MediaState, action: Action) => ({
...state,
paused: true,
}),
[actions.MEDIA_POSITION]: (state: MediaState, action: Action) => {
const { outpoint, position } = action.data;
return {
...state,
positions: {
...state.positions,
[outpoint]: position,
},
};
},
},
defaultState
);

View file

@ -0,0 +1,17 @@
import * as settings from "constants/settings";
import { createSelector } from "reselect";
import lbryuri from "lbryuri";
import { makeSelectClaimForUri } from "redux/selectors/claims";
const _selectState = state => state.media || {};
export const selectMediaPaused = createSelector(
_selectState,
state => state.paused
);
export const makeSelectMediaPositionForUri = uri =>
createSelector(_selectState, makeSelectClaimForUri(uri), (state, claim) => {
const outpoint = `${claim.txid}:${claim.nout}`;
return state.positions[outpoint] || null;
});

View file

@ -13,7 +13,7 @@ import userReducer from 'redux/reducers/user';
import walletReducer from 'redux/reducers/wallet';
import shapeShiftReducer from 'redux/reducers/shape_shift';
import subscriptionsReducer from 'redux/reducers/subscriptions';
import videoReducer from 'redux/reducers/video';
import mediaReducer from 'redux/reducers/media';
import { persistStore, autoRehydrate } from 'redux-persist';
import createCompressor from 'redux-persist-transform-compress';
import createFilter from 'redux-persist-transform-filter';
@ -64,7 +64,7 @@ const reducers = combineReducers({
user: userReducer,
shapeShift: shapeShiftReducer,
subscriptions: subscriptionsReducer,
video: videoReducer,
media: mediaReducer,
});
const bulkThunk = createBulkThunkMiddleware();