mirror of
https://github.com/LBRYFoundation/lbry-desktop.git
synced 2025-08-23 17:47:24 +00:00
Merge pull request #890 from daovist/video-state
track and manage video state
This commit is contained in:
commit
eed68aae91
12 changed files with 165 additions and 33 deletions
|
@ -87,7 +87,8 @@ class FileCard extends React.PureComponent {
|
||||||
</div>
|
</div>
|
||||||
<div className="card__subtitle">
|
<div className="card__subtitle">
|
||||||
<span className="card__indicators card--file-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 />}
|
{fileInfo && <Icon icon={icons.LOCAL} leftPad />}
|
||||||
</span>
|
</span>
|
||||||
<span className="card--file-subtitle">
|
<span className="card--file-subtitle">
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { makeSelectCostInfoForUri } from 'redux/selectors/cost_info';
|
||||||
import { doFetchAvailability } from 'redux/actions/availability';
|
import { doFetchAvailability } from 'redux/actions/availability';
|
||||||
import { doOpenFileInShell } from 'redux/actions/file_info';
|
import { doOpenFileInShell } from 'redux/actions/file_info';
|
||||||
import { doPurchaseUri, doStartDownload } from 'redux/actions/content';
|
import { doPurchaseUri, doStartDownload } from 'redux/actions/content';
|
||||||
import { setVideoPause } from 'redux/actions/video';
|
import { doPause } from 'redux/actions/media';
|
||||||
import FileDownloadLink from './view';
|
import FileDownloadLink from './view';
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
|
@ -25,7 +25,7 @@ const perform = dispatch => ({
|
||||||
openInShell: path => dispatch(doOpenFileInShell(path)),
|
openInShell: path => dispatch(doOpenFileInShell(path)),
|
||||||
purchaseUri: uri => dispatch(doPurchaseUri(uri)),
|
purchaseUri: uri => dispatch(doPurchaseUri(uri)),
|
||||||
restartDownload: (uri, outpoint) => dispatch(doStartDownload(uri, outpoint)),
|
restartDownload: (uri, outpoint) => dispatch(doStartDownload(uri, outpoint)),
|
||||||
setVideoPause: val => dispatch(setVideoPause(val)),
|
doPause: () => dispatch(doPause()),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(select, perform)(FileDownloadLink);
|
export default connect(select, perform)(FileDownloadLink);
|
||||||
|
|
|
@ -43,12 +43,12 @@ class FileDownloadLink extends React.PureComponent {
|
||||||
purchaseUri,
|
purchaseUri,
|
||||||
costInfo,
|
costInfo,
|
||||||
loading,
|
loading,
|
||||||
setVideoPause,
|
doPause,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const openFile = () => {
|
const openFile = () => {
|
||||||
openInShell(fileInfo.download_path);
|
openInShell(fileInfo.download_path);
|
||||||
setVideoPause(true);
|
doPause();
|
||||||
};
|
};
|
||||||
|
|
||||||
if (loading || downloading) {
|
if (loading || downloading) {
|
||||||
|
|
|
@ -1,22 +1,30 @@
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import { connect } from 'react-redux';
|
import { connect } from "react-redux";
|
||||||
import { doChangeVolume } from 'redux/actions/app';
|
import { doChangeVolume } from "redux/actions/app";
|
||||||
import { selectVolume } from 'redux/selectors/app';
|
import { selectVolume } from "redux/selectors/app";
|
||||||
import { doPlayUri, doSetPlayingUri } from 'redux/actions/content';
|
import { doPlayUri, doSetPlayingUri } from "redux/actions/content";
|
||||||
import { makeSelectMetadataForUri, makeSelectContentTypeForUri } from 'redux/selectors/claims';
|
import { doPlay, doPause, savePosition } from "redux/actions/media";
|
||||||
import { setVideoPause } from 'redux/actions/video';
|
import {
|
||||||
|
makeSelectMetadataForUri,
|
||||||
|
makeSelectContentTypeForUri,
|
||||||
|
} from "redux/selectors/claims";
|
||||||
import {
|
import {
|
||||||
makeSelectFileInfoForUri,
|
makeSelectFileInfoForUri,
|
||||||
makeSelectLoadingForUri,
|
makeSelectLoadingForUri,
|
||||||
makeSelectDownloadingForUri,
|
makeSelectDownloadingForUri,
|
||||||
} from 'redux/selectors/file_info';
|
} from "redux/selectors/file_info";
|
||||||
import { makeSelectCostInfoForUri } from 'redux/selectors/cost_info';
|
import { makeSelectCostInfoForUri } from "redux/selectors/cost_info";
|
||||||
import { selectShowNsfw } from 'redux/selectors/settings';
|
import { selectShowNsfw } from "redux/selectors/settings";
|
||||||
import { selectVideoPause } from 'redux/selectors/video';
|
import {
|
||||||
import Video from './view';
|
selectMediaPaused,
|
||||||
import { selectPlayingUri } from 'redux/selectors/content';
|
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) => ({
|
const select = (state, props) => ({
|
||||||
|
claim: makeSelectClaimForUri(props.uri)(state),
|
||||||
costInfo: makeSelectCostInfoForUri(props.uri)(state),
|
costInfo: makeSelectCostInfoForUri(props.uri)(state),
|
||||||
fileInfo: makeSelectFileInfoForUri(props.uri)(state),
|
fileInfo: makeSelectFileInfoForUri(props.uri)(state),
|
||||||
metadata: makeSelectMetadataForUri(props.uri)(state),
|
metadata: makeSelectMetadataForUri(props.uri)(state),
|
||||||
|
@ -26,14 +34,18 @@ const select = (state, props) => ({
|
||||||
playingUri: selectPlayingUri(state),
|
playingUri: selectPlayingUri(state),
|
||||||
contentType: makeSelectContentTypeForUri(props.uri)(state),
|
contentType: makeSelectContentTypeForUri(props.uri)(state),
|
||||||
volume: selectVolume(state),
|
volume: selectVolume(state),
|
||||||
videoPause: selectVideoPause(state),
|
mediaPaused: selectMediaPaused(state),
|
||||||
|
mediaPosition: makeSelectMediaPositionForUri(props.uri)(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
play: uri => dispatch(doPlayUri(uri)),
|
play: uri => dispatch(doPlayUri(uri)),
|
||||||
cancelPlay: () => dispatch(doSetPlayingUri(null)),
|
cancelPlay: () => dispatch(doSetPlayingUri(null)),
|
||||||
changeVolume: volume => dispatch(doChangeVolume(volume)),
|
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);
|
export default connect(select, perform)(Video);
|
||||||
|
|
|
@ -21,16 +21,24 @@ class VideoPlayer extends React.PureComponent {
|
||||||
this.togglePlayListener = this.togglePlay.bind(this);
|
this.togglePlayListener = this.togglePlay.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(next) {
|
||||||
if (nextProps.videoPause) {
|
const el = this.refs.media.children[0];
|
||||||
this.refs.media.children[0].pause();
|
if (!this.props.paused && next.paused && !el.paused) el.pause();
|
||||||
this.props.setVideoPause(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const container = this.refs.media;
|
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 => {
|
const loadedMetadata = e => {
|
||||||
this.setState({ hasMetadata: true, startedPlaying: true });
|
this.setState({ hasMetadata: true, startedPlaying: true });
|
||||||
this.refs.media.children[0].play();
|
this.refs.media.children[0].play();
|
||||||
|
@ -61,6 +69,12 @@ class VideoPlayer extends React.PureComponent {
|
||||||
document.addEventListener('keydown', this.togglePlayListener);
|
document.addEventListener('keydown', this.togglePlayListener);
|
||||||
const mediaElement = this.refs.media.children[0];
|
const mediaElement = this.refs.media.children[0];
|
||||||
if (mediaElement) {
|
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('click', this.togglePlayListener);
|
||||||
mediaElement.addEventListener('loadedmetadata', loadedMetadata.bind(this), {
|
mediaElement.addEventListener('loadedmetadata', loadedMetadata.bind(this), {
|
||||||
once: true,
|
once: true,
|
||||||
|
@ -79,6 +93,7 @@ class VideoPlayer extends React.PureComponent {
|
||||||
if (mediaElement) {
|
if (mediaElement) {
|
||||||
mediaElement.removeEventListener('click', this.togglePlayListener);
|
mediaElement.removeEventListener('click', this.togglePlayListener);
|
||||||
}
|
}
|
||||||
|
this.props.doPause();
|
||||||
}
|
}
|
||||||
|
|
||||||
renderAudio(container, autoplay) {
|
renderAudio(container, autoplay) {
|
||||||
|
|
|
@ -51,9 +51,13 @@ class Video extends React.PureComponent {
|
||||||
contentType,
|
contentType,
|
||||||
changeVolume,
|
changeVolume,
|
||||||
volume,
|
volume,
|
||||||
|
claim,
|
||||||
uri,
|
uri,
|
||||||
videoPause,
|
doPlay,
|
||||||
setVideoPause,
|
doPause,
|
||||||
|
savePosition,
|
||||||
|
mediaPaused,
|
||||||
|
mediaPosition,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const isPlaying = playingUri === uri;
|
const isPlaying = playingUri === uri;
|
||||||
|
@ -103,8 +107,13 @@ class Video extends React.PureComponent {
|
||||||
downloadCompleted={fileInfo.completed}
|
downloadCompleted={fileInfo.completed}
|
||||||
changeVolume={changeVolume}
|
changeVolume={changeVolume}
|
||||||
volume={volume}
|
volume={volume}
|
||||||
videoPause={videoPause}
|
doPlay={doPlay}
|
||||||
setVideoPause={setVideoPause}
|
doPause={doPause}
|
||||||
|
savePosition={savePosition}
|
||||||
|
claim={claim}
|
||||||
|
uri={uri}
|
||||||
|
paused={mediaPaused}
|
||||||
|
position={mediaPosition}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{!isPlaying && (
|
{!isPlaying && (
|
||||||
|
|
|
@ -157,3 +157,8 @@ export const HAS_FETCHED_SUBSCRIPTIONS = 'HAS_FETCHED_SUBSCRIPTIONS';
|
||||||
|
|
||||||
// Video controls
|
// Video controls
|
||||||
export const SET_VIDEO_PAUSE = 'SET_VIDEO_PAUSE';
|
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';
|
||||||
|
|
|
@ -279,7 +279,9 @@ export function doLoadVideo(uri) {
|
||||||
});
|
});
|
||||||
dispatch(
|
dispatch(
|
||||||
doAlertError(
|
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.`
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
30
src/renderer/redux/actions/media.js
Normal file
30
src/renderer/redux/actions/media.js
Normal 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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
41
src/renderer/redux/reducers/media.js
Normal file
41
src/renderer/redux/reducers/media.js
Normal 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
|
||||||
|
);
|
17
src/renderer/redux/selectors/media.js
Normal file
17
src/renderer/redux/selectors/media.js
Normal 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;
|
||||||
|
});
|
|
@ -13,7 +13,7 @@ import userReducer from 'redux/reducers/user';
|
||||||
import walletReducer from 'redux/reducers/wallet';
|
import walletReducer from 'redux/reducers/wallet';
|
||||||
import shapeShiftReducer from 'redux/reducers/shape_shift';
|
import shapeShiftReducer from 'redux/reducers/shape_shift';
|
||||||
import subscriptionsReducer from 'redux/reducers/subscriptions';
|
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 { persistStore, autoRehydrate } from 'redux-persist';
|
||||||
import createCompressor from 'redux-persist-transform-compress';
|
import createCompressor from 'redux-persist-transform-compress';
|
||||||
import createFilter from 'redux-persist-transform-filter';
|
import createFilter from 'redux-persist-transform-filter';
|
||||||
|
@ -64,7 +64,7 @@ const reducers = combineReducers({
|
||||||
user: userReducer,
|
user: userReducer,
|
||||||
shapeShift: shapeShiftReducer,
|
shapeShift: shapeShiftReducer,
|
||||||
subscriptions: subscriptionsReducer,
|
subscriptions: subscriptionsReducer,
|
||||||
video: videoReducer,
|
media: mediaReducer,
|
||||||
});
|
});
|
||||||
|
|
||||||
const bulkThunk = createBulkThunkMiddleware();
|
const bulkThunk = createBulkThunkMiddleware();
|
||||||
|
|
Loading…
Add table
Reference in a new issue