diff --git a/src/renderer/component/fileCard/view.jsx b/src/renderer/component/fileCard/view.jsx
index fdc070cf9..e38bf06c2 100644
--- a/src/renderer/component/fileCard/view.jsx
+++ b/src/renderer/component/fileCard/view.jsx
@@ -87,7 +87,8 @@ class FileCard extends React.PureComponent {
- {isRewardContent && }{' '}
+ {' '}
+ {isRewardContent && }{' '}
{fileInfo && }
diff --git a/src/renderer/component/fileDownloadLink/index.js b/src/renderer/component/fileDownloadLink/index.js
index 4b5ad978c..e13b4b3d4 100644
--- a/src/renderer/component/fileDownloadLink/index.js
+++ b/src/renderer/component/fileDownloadLink/index.js
@@ -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);
diff --git a/src/renderer/component/fileDownloadLink/view.jsx b/src/renderer/component/fileDownloadLink/view.jsx
index 4595b36d9..57818da51 100644
--- a/src/renderer/component/fileDownloadLink/view.jsx
+++ b/src/renderer/component/fileDownloadLink/view.jsx
@@ -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) {
diff --git a/src/renderer/component/video/index.js b/src/renderer/component/video/index.js
index 38046f2f8..8c0a91289 100644
--- a/src/renderer/component/video/index.js
+++ b/src/renderer/component/video/index.js
@@ -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);
diff --git a/src/renderer/component/video/internal/player.jsx b/src/renderer/component/video/internal/player.jsx
index 7572243c8..e938a856f 100644
--- a/src/renderer/component/video/internal/player.jsx
+++ b/src/renderer/component/video/internal/player.jsx
@@ -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) {
diff --git a/src/renderer/component/video/view.jsx b/src/renderer/component/video/view.jsx
index 1edd40c7d..9a1afe7e4 100644
--- a/src/renderer/component/video/view.jsx
+++ b/src/renderer/component/video/view.jsx
@@ -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 && (
diff --git a/src/renderer/constants/action_types.js b/src/renderer/constants/action_types.js
index 01c5a4c5d..3c828f5cb 100644
--- a/src/renderer/constants/action_types.js
+++ b/src/renderer/constants/action_types.js
@@ -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';
diff --git a/src/renderer/redux/actions/content.js b/src/renderer/redux/actions/content.js
index a5df999ed..7309f1c13 100644
--- a/src/renderer/redux/actions/content.js
+++ b/src/renderer/redux/actions/content.js
@@ -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.`
)
);
});
diff --git a/src/renderer/redux/actions/media.js b/src/renderer/redux/actions/media.js
new file mode 100644
index 000000000..d3076ed75
--- /dev/null
+++ b/src/renderer/redux/actions/media.js
@@ -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,
+ },
+ });
+ };
+}
diff --git a/src/renderer/redux/reducers/media.js b/src/renderer/redux/reducers/media.js
new file mode 100644
index 000000000..7e3bb9591
--- /dev/null
+++ b/src/renderer/redux/reducers/media.js
@@ -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
+);
diff --git a/src/renderer/redux/selectors/media.js b/src/renderer/redux/selectors/media.js
new file mode 100644
index 000000000..9ef16abaf
--- /dev/null
+++ b/src/renderer/redux/selectors/media.js
@@ -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;
+ });
diff --git a/src/renderer/store.js b/src/renderer/store.js
index 74cbadd82..ca6db3afd 100644
--- a/src/renderer/store.js
+++ b/src/renderer/store.js
@@ -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();