Skip to content
Snippets Groups Projects
Commit 95e5ece6 authored by 2SC1815J's avatar 2SC1815J
Browse files

support for captions and auto-scrolling of annotation list with video playback

parent 452dc918
Branches
Tags
No related merge requests found
Showing
with 325 additions and 42 deletions
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import SyncIcon from '@material-ui/icons/Sync';
import SyncDisabledIcon from '@material-ui/icons/SyncDisabled';
import VisibilityIcon from '@material-ui/icons/VisibilitySharp';
import VisibilityOffIcon from '@material-ui/icons/VisibilityOffSharp';
import MiradorMenuButton from '../containers/MiradorMenuButton';
......@@ -14,10 +16,12 @@ export class AnnotationSettings extends Component {
*/
render() {
const {
displayAll, displayAllDisabled, t, toggleAnnotationDisplay,
autoScroll, autoScrollDisabled,
displayAll, displayAllDisabled, t, toggleAnnotationAutoScroll, toggleAnnotationDisplay,
} = this.props;
return (
<>
<MiradorMenuButton
aria-label={t(displayAll ? 'displayNoAnnotations' : 'highlightAllAnnotations')}
onClick={toggleAnnotationDisplay}
......@@ -26,14 +30,31 @@ export class AnnotationSettings extends Component {
>
{ displayAll ? <VisibilityIcon /> : <VisibilityOffIcon /> }
</MiradorMenuButton>
<MiradorMenuButton
aria-label={autoScroll ? 'Disable auto scroll' : 'Enable auto scroll'}
onClick={toggleAnnotationAutoScroll}
disabled={autoScrollDisabled}
size="small"
>
{ autoScroll ? <SyncIcon /> : <SyncDisabledIcon /> }
</MiradorMenuButton>
</>
);
}
}
AnnotationSettings.defaultProps = {
autoScroll: true,
autoScrollDisabled: true,
toggleAnnotationAutoScroll: () => {},
};
AnnotationSettings.propTypes = {
autoScroll: PropTypes.bool,
autoScrollDisabled: PropTypes.bool,
displayAll: PropTypes.bool.isRequired,
displayAllDisabled: PropTypes.bool.isRequired,
t: PropTypes.func.isRequired,
toggleAnnotationAutoScroll: PropTypes.func,
toggleAnnotationDisplay: PropTypes.func.isRequired,
windowId: PropTypes.string.isRequired, // eslint-disable-line react/no-unused-prop-types
};
......@@ -87,6 +87,7 @@ export class AnnotationsOverlayVideo extends Component {
} else {
this.temporalOffset = 0;
}
this.currentTimeNearestAnnotationId = null;
this.state = {
showProgress: false,
......@@ -111,13 +112,14 @@ export class AnnotationsOverlayVideo extends Component {
hoveredAnnotationIds, selectedAnnotationId,
highlightAllAnnotations,
paused,
setCurrentTime,
setPaused,
seekToTime,
} = this.props;
this.initializeViewer();
let prevVideoPausedState;
if (this.video) {
prevVideoPausedState = this.video.paused;
if (this.video.paused && !paused) {
const promise = this.video.play();
if (promise !== undefined) {
......@@ -126,6 +128,15 @@ export class AnnotationsOverlayVideo extends Component {
} else if (!this.video.paused && paused) {
this.video.pause();
}
if (seekToTime !== prevProps.seekToTime) {
if (seekToTime !== undefined) {
this.seekTo(seekToTime, true);
return;
}
}
if (this.video.seeking) {
return;
}
if (currentTime !== prevProps.currentTime) {
if (paused && this.video.paused) {
this.video.currentTime = currentTime - this.temporalOffset;
......@@ -154,8 +165,10 @@ export class AnnotationsOverlayVideo extends Component {
const selectedAnnotationsUpdated = selectedAnnotationId !== prevProps.selectedAnnotationId;
if (selectedAnnotationsUpdated && selectedAnnotationId) {
setPaused(true);
this.video && this.video.pause();
if (this.currentTimeNearestAnnotationId
&& this.currentTimeNearestAnnotationId === selectedAnnotationId) {
// go through
} else {
annotations.forEach((annotation) => {
annotation.resources.forEach((resource) => {
if (resource.id !== selectedAnnotationId) return;
......@@ -164,17 +177,46 @@ export class AnnotationsOverlayVideo extends Component {
const temporalfragment = resource.temporalfragmentSelector;
if (temporalfragment && temporalfragment.length > 0 && this.video) {
const seekto = temporalfragment[0] || 0;
setPaused(true);
this.video.pause();
setCurrentTime(seekto);
const videoTime = seekto - this.temporalOffset;
if (videoTime >= 0) {
this.video.currentTime = videoTime;
this.seekTo(seekto, !prevVideoPausedState);
}
}
});
});
}
}
// auto scroll
if (this.video && !this.video.paused) {
let minElapsedTimeAfterStart = Number.MAX_VALUE;
let candidateAnnotation;
annotations.forEach((annotation) => {
annotation.resources.forEach((resource) => {
if (!canvasWorld.canvasIds.includes(resource.targetId)) return;
if (AnnotationsOverlayVideo.isAnnotaionInTemporalSegment(resource, currentTime)) {
const temporalfragment = resource.temporalfragmentSelector;
if (temporalfragment && temporalfragment.length > 0 && this.video) {
const seekto = temporalfragment[0] || 0;
const elapsedTimeAfterStart = currentTime - seekto;
if (elapsedTimeAfterStart >= 0 && elapsedTimeAfterStart < minElapsedTimeAfterStart) {
minElapsedTimeAfterStart = elapsedTimeAfterStart;
candidateAnnotation = resource.resource;
}
}
}
});
});
if (candidateAnnotation) {
if (candidateAnnotation.id !== prevProps.selectedAnnotationId) {
const {
selectAnnotation,
windowId,
} = this.props;
if (selectedAnnotationId !== candidateAnnotation.id) {
selectAnnotation(windowId, candidateAnnotation.id);
}
this.currentTimeNearestAnnotationId = candidateAnnotation.id;
}
}
}
const redrawAnnotations = drawAnnotations !== prevProps.drawAnnotations
......@@ -230,17 +272,12 @@ export class AnnotationsOverlayVideo extends Component {
/** */
onVideoPlaying(event) {
if (this.video && this.video.currentTime !== 0) {
const { currentTime } = this.props;
const { currentTime, seekToTime } = this.props;
const currentTimeToVideoTime = currentTime - this.temporalOffset;
const diff = Math.abs(currentTimeToVideoTime - this.video.currentTime);
const acceptableDiff = 1; // sec.
if (diff > acceptableDiff) {
const { setCurrentTime, setPaused } = this.props;
// In the flow of pausing, adjusting currentTime, and resuming playback,
// it is necessary to handle cases where the user explicitly pauses playback.
setPaused(true);
setCurrentTime(this.video.currentTime + this.temporalOffset); // rewind time
setPaused(false);
if (diff > acceptableDiff && seekToTime === undefined) {
this.seekTo(this.video.currentTime + this.temporalOffset, true);
}
}
this.setState({ showProgress: false });
......@@ -397,6 +434,19 @@ export class AnnotationsOverlayVideo extends Component {
return imageSource;
}
/** @private */
seekTo(seekTo, resume) {
const { setCurrentTime, setPaused } = this.props;
setPaused(true);
setCurrentTime(seekTo);
this.video.addEventListener('seeked', function seeked(event) {
event.currentTarget.removeEventListener(event.type, seeked);
if (resume) {
setPaused(false);
}
});
}
/** @private */
isCanvasSizeSpecified() {
const { canvas, canvasWorld } = this.props;
......@@ -600,6 +650,7 @@ AnnotationsOverlayVideo.defaultProps = {
palette: {},
paused: true,
searchAnnotations: [],
seekToTime: undefined,
selectAnnotation: () => {},
selectedAnnotationId: undefined,
setCurrentTime: () => {},
......@@ -621,6 +672,7 @@ AnnotationsOverlayVideo.propTypes = {
palette: PropTypes.object, // eslint-disable-line react/forbid-prop-types
paused: PropTypes.bool,
searchAnnotations: PropTypes.arrayOf(PropTypes.object),
seekToTime: PropTypes.number,
selectAnnotation: PropTypes.func,
selectedAnnotationId: PropTypes.string,
setCurrentTime: PropTypes.func,
......
......@@ -58,7 +58,7 @@ export class CanvasAnnotations extends Component {
*/
render() {
const {
annotations, classes, index, label, selectedAnnotationId, t, totalSize,
annotations, autoScroll, classes, index, label, selectedAnnotationId, t, totalSize,
listContainerComponent, htmlSanitizationRuleSet, hoveredAnnotationIds,
containerRef,
} = this.props;
......@@ -76,7 +76,7 @@ export class CanvasAnnotations extends Component {
containerRef={containerRef}
key={`${annotation.id}-scroll`}
offsetTop={96} // offset for the height of the form above
scrollTo={selectedAnnotationId === annotation.id}
scrollTo={autoScroll ? (selectedAnnotationId === annotation.id) : false}
>
<MenuItem
button
......@@ -126,6 +126,7 @@ CanvasAnnotations.propTypes = {
id: PropTypes.string.isRequired,
}),
),
autoScroll: PropTypes.bool,
classes: PropTypes.objectOf(PropTypes.string),
containerRef: PropTypes.oneOfType([
PropTypes.func,
......@@ -146,6 +147,7 @@ CanvasAnnotations.propTypes = {
};
CanvasAnnotations.defaultProps = {
annotations: [],
autoScroll: true,
classes: {},
containerRef: undefined,
hoveredAnnotationIds: [],
......
......@@ -19,11 +19,28 @@ export class VideoViewer extends Component {
};
}
/** */
componentDidMount() {
const { annotations, setHasTextTrack, setPaused } = this.props;
setPaused(true);
const vttContent = flatten(
flattenDeep([
annotations.map(annotation => annotation.resources.map(
resources_ => resources_.resource,
)),
]).filter(resource => resource.body && resource.body[0] && resource.body[0].format === 'text/vtt'),
);
if (vttContent && vttContent.length > 0) {
setHasTextTrack(true);
}
}
/** */
componentDidUpdate(prevProps) {
const {
canvas, currentTime, muted, paused,
setCurrentTime, setPaused,
textTrackDisabled,
} = this.props;
if (paused !== prevProps.paused) {
......@@ -51,7 +68,18 @@ export class VideoViewer extends Component {
if (video.muted !== muted) {
video.muted = muted;
}
if (video.textTracks && video.textTracks.length > 0) {
const newMode = textTrackDisabled ? 'hidden' : 'showing';
if (video.textTracks[0].mode !== newMode) {
video.textTracks[0].mode = newMode;
}
}
}
}
/** */
componentWillUnmount() {
this.timerStop();
}
/** */
......@@ -85,7 +113,7 @@ export class VideoViewer extends Component {
/** */
render() {
const {
canvas, classes, currentTime, videoOptions, windowId,
annotations, canvas, classes, currentTime, videoOptions, windowId,
} = this.props;
const videoResources = flatten(
......@@ -107,13 +135,26 @@ export class VideoViewer extends Component {
}),
]).filter((resource) => resource.body && resource.body[0].__jsonld && resource.body[0].__jsonld.type === 'Video'),
);
const vttContent = flatten(
flattenDeep([
annotations.map(annotation => annotation.resources.map(
resources_ => resources_.resource,
)),
]).filter(resource => resource.body && resource.body[0] && resource.body[0].format === 'text/vtt'),
);
// Only one video can be displayed at a time in this implementation.
const len = videoResources.length;
const video = len > 0
? videoResources[len - 1].body[0] : null;
const videoTargetTemporalfragment = len > 0
? videoResources[len - 1].temporalfragment : [];
let caption = null;
if (vttContent && vttContent.length > 0) {
caption = {
id: vttContent[0].body[0].id,
};
}
return (
<div className={classes.flexContainer}>
<div className={classes.flexFill}>
......@@ -121,6 +162,9 @@ export class VideoViewer extends Component {
<>
<video className={classes.video} key={video.id} ref={this.videoRef} {...videoOptions}>
<source src={video.id} type={video.getFormat()} />
{ caption && (
<track src={caption.id} />
)}
</video>
<AnnotationsOverlayVideo windowId={windowId} videoRef={this.videoRef} videoTarget={videoTargetTemporalfragment} key={`${windowId} ${video.id}`} />
</>
......@@ -134,23 +178,29 @@ export class VideoViewer extends Component {
}
VideoViewer.propTypes = {
annotations: PropTypes.arrayOf(PropTypes.object),
canvas: PropTypes.object, // eslint-disable-line react/forbid-prop-types
classes: PropTypes.objectOf(PropTypes.string).isRequired,
currentTime: PropTypes.number,
muted: PropTypes.bool,
paused: PropTypes.bool,
setCurrentTime: PropTypes.func,
setHasTextTrack: PropTypes.func,
setPaused: PropTypes.func,
textTrackDisabled: PropTypes.bool,
videoOptions: PropTypes.object, // eslint-disable-line react/forbid-prop-types
windowId: PropTypes.string.isRequired,
};
VideoViewer.defaultProps = {
annotations: [],
canvas: {},
currentTime: 0,
muted: false,
paused: true,
setCurrentTime: () => {},
setHasTextTrack: () => {},
setPaused: () => {},
textTrackDisabled: true,
videoOptions: {},
};
import ClosedCaption from '@material-ui/icons/ClosedCaption';
import ClosedCaptionOutlined from '@material-ui/icons/ClosedCaptionOutlined';
import React, { Component } from 'react';
import PauseRoundedIcon from '@material-ui/icons/PauseRounded';
import PlayArrowRoundedIcon from '@material-ui/icons/PlayArrowRounded';
......@@ -19,13 +21,9 @@ export class ViewerNavigationVideo extends Component {
/** */
handleChange = (event, newValue) => {
const { paused, setCurrentTime, setPaused } = this.props;
const { paused, setCurrentTime, setSeekTo } = this.props;
if (!paused) {
// In the flow of pausing, adjusting currentTime, and resuming playback,
// it is necessary to handle cases where the user explicitly pauses playback.
setPaused(true);
setCurrentTime(newValue);
setPaused(false);
setSeekTo(newValue);
} else {
setCurrentTime(newValue);
}
......@@ -39,10 +37,13 @@ export class ViewerNavigationVideo extends Component {
classes,
currentTime,
duration,
hasTextTrack,
muted,
paused,
setMuted,
setPaused,
setTextTrackDisabled,
textTrackDisabled,
} = this.props;
const start = (duration > 3600 || duration === undefined) ? 11 : 14;
......@@ -79,6 +80,15 @@ export class ViewerNavigationVideo extends Component {
>
{ muted ? <VolumeOffIcon /> : <VolumeUpIcon /> }
</MiradorMenuButton>
{ hasTextTrack && (
<MiradorMenuButton
aria-label={textTrackDisabled ? 'CC show' : 'CC hide'}
className={textTrackDisabled ? ns('next-canvas-button') : ns('next-canvas-button')}
onClick={() => { setTextTrackDisabled(!textTrackDisabled); }}
>
{ textTrackDisabled ? <ClosedCaptionOutlined /> : <ClosedCaption /> }
</MiradorMenuButton>
)}
<span className={classes.divider} />
</div>
);
......@@ -89,19 +99,27 @@ ViewerNavigationVideo.propTypes = {
classes: PropTypes.objectOf(PropTypes.string).isRequired,
currentTime: PropTypes.number,
duration: PropTypes.number,
hasTextTrack: PropTypes.bool,
muted: PropTypes.bool,
paused: PropTypes.bool,
setCurrentTime: PropTypes.func,
setMuted: PropTypes.func,
setPaused: PropTypes.func,
setSeekTo: PropTypes.func,
setTextTrackDisabled: PropTypes.func,
textTrackDisabled: PropTypes.bool,
};
ViewerNavigationVideo.defaultProps = {
currentTime: 0,
duration: undefined,
hasTextTrack: false,
muted: false,
paused: true,
setCurrentTime: () => {},
setMuted: () => {},
setPaused: () => {},
setSeekTo: () => {},
setTextTrackDisabled: () => {},
textTrackDisabled: true,
};
......@@ -360,7 +360,7 @@ export default {
crossOrigin: 'anonymous',
},
videoOptions: { // Additional props passed to <audio> element
controls: true,
controls: false,
crossOrigin: 'anonymous',
},
auth: {
......
......@@ -13,6 +13,8 @@ import { AnnotationSettings } from '../components/AnnotationSettings';
* Mapping redux state to component props using connect
*/
const mapStateToProps = (state, { windowId }) => ({
autoScroll: getWindow(state, { windowId }).autoScrollAnnotationList,
autoScrollDisabled: getAnnotationResourcesByMotivation(state, { windowId }).length < 2,
displayAll: getWindow(state, { windowId }).highlightAllAnnotations,
displayAllDisabled: getAnnotationResourcesByMotivation(
state,
......@@ -24,6 +26,9 @@ const mapStateToProps = (state, { windowId }) => ({
* Mapping redux action dispatches to component props using connect
*/
const mapDispatchToProps = (dispatch, { windowId }) => ({
toggleAnnotationAutoScroll: () => {
dispatch(actions.toggleAnnotationAutoScroll(windowId));
},
toggleAnnotationDisplay: () => {
dispatch(actions.toggleAnnotationDisplay(windowId));
},
......
......@@ -13,6 +13,7 @@ import {
getConfig,
getCurrentCanvas,
getWindowCurrentTime,
getWindowSeekToTime,
getWindowPausedStatus,
getPresentAnnotationsOnSelectedCanvases,
getSelectedAnnotationId,
......@@ -39,6 +40,7 @@ const mapStateToProps = (state, { windowId }) => ({
state,
{ windowId },
),
seekToTime: getWindowSeekToTime(state, { windowId }),
selectedAnnotationId: getSelectedAnnotationId(state, { windowId }),
});
......
......@@ -9,6 +9,7 @@ import {
getCanvasLabel,
getSelectedAnnotationId,
getConfig,
getWindow,
} from '../state/selectors';
import { CanvasAnnotations } from '../components/CanvasAnnotations';
......@@ -32,6 +33,7 @@ const mapStateToProps = (state, { canvasId, windowId }) => ({
state, { canvasId, windowId },
),
),
autoScroll: getWindow(state, { windowId }).autoScrollAnnotationList,
htmlSanitizationRuleSet: getConfig(state).annotations.htmlSanitizationRuleSet,
label: getCanvasLabel(state, {
canvasId,
......
......@@ -12,21 +12,26 @@ import {
getWindowMutedStatus,
getWindowPausedStatus,
getWindowCurrentTime,
getWindowTextTrackDisabledStatus,
getPresentAnnotationsOnSelectedCanvases,
} from '../state/selectors';
/** */
const mapStateToProps = (state, { windowId }) => ({
annotations: getPresentAnnotationsOnSelectedCanvases(state, { windowId }),
canvas: (getCurrentCanvas(state, { windowId }) || {}),
canvasWorld: getCurrentCanvasWorld(state, { windowId }),
currentTime: getWindowCurrentTime(state, { windowId }),
muted: getWindowMutedStatus(state, { windowId }),
paused: getWindowPausedStatus(state, { windowId }),
textTrackDisabled: getWindowTextTrackDisabledStatus(state, { windowId }),
videoOptions: getConfig(state).videoOptions,
});
/** */
const mapDispatchToProps = (dispatch, { windowId }) => ({
setCurrentTime: (...args) => dispatch(actions.setWindowCurrentTime(windowId, ...args)),
setHasTextTrack: (...args) => dispatch(actions.setWindowHasTextTrack(windowId, ...args)),
setPaused: (...args) => dispatch(actions.setWindowPaused(windowId, ...args)),
});
......@@ -46,7 +51,7 @@ const styles = () => ({
height: '100%',
maxHeight: '100%',
maxWidth: '100%',
'object-fit': 'scale-down',
'object-fit': 'contain', // 'scale-down',
'object-position': 'left top',
width: '100%',
},
......
......@@ -10,14 +10,18 @@ import {
getWindowCurrentTime,
getWindowMutedStatus,
getWindowPausedStatus,
getWindowTextTrackDisabledStatus,
getWindowHasTextTrack,
} from '../state/selectors';
/** */
const mapStateToProps = (state, { windowId }) => ({
currentTime: getWindowCurrentTime(state, { windowId }),
duration: getCurrentCanvasDuration(state, { windowId }),
hasTextTrack: getWindowHasTextTrack(state, { windowId }),
muted: getWindowMutedStatus(state, { windowId }),
paused: getWindowPausedStatus(state, { windowId }),
textTrackDisabled: getWindowTextTrackDisabledStatus(state, { windowId }),
});
/**
......@@ -29,6 +33,10 @@ const mapDispatchToProps = (dispatch, { windowId }) => ({
setCurrentTime: (...args) => dispatch(actions.setWindowCurrentTime(windowId, ...args)),
setMuted: (...args) => dispatch(actions.setWindowMuted(windowId, ...args)),
setPaused: (...args) => dispatch(actions.setWindowPaused(windowId, ...args)),
setSeekTo: (...args) => dispatch(actions.setWindowSeekTo(windowId, ...args)),
setTextTrackDisabled: (...args) => dispatch(
actions.setWindowTextTrackDisabled(windowId, ...args),
),
});
const styles = {
......
......@@ -13,6 +13,7 @@ const ActionTypes = {
DESELECT_ANNOTATION: 'mirador/DESELECT_ANNOTATION',
SELECT_ANNOTATION: 'mirador/SELECT_ANNOTATION',
TOGGLE_ANNOTATION_DISPLAY: 'mirador/TOGGLE_ANNOTATION_DISPLAY',
TOGGLE_ANNOTATION_AUTOSCROLL: 'mirador/TOGGLE_ANNOTATION_AUTOSCROLL',
FOCUS_WINDOW: 'mirador/FOCUS_WINDOW',
SET_WORKSPACE_FULLSCREEN: 'mirador/SET_WORKSPACE_FULLSCREEN',
......@@ -74,8 +75,11 @@ const ActionTypes = {
HIDE_COLLECTION_DIALOG: 'mirador/HIDE_COLLECTION_DIALOG',
SET_CURRENT_TIME: 'mirador/SET_CURRENT_TIME',
SET_SEEK_TO_TIME: 'mirador/SET_SEEK_TO_TIME',
SET_VIDEO_PAUSED: 'mirador/SET_VIDEO_PAUSED',
SET_VIDEO_MUTED: 'mirador/SET_VIDEO_MUTED',
SET_VIDEO_TEXTTRACK_DISABLED: 'mirador/SET_VIDEO_TEXTTRACK_DISABLED',
SET_VIDEO_HAS_TEXTTRACK: 'mirador/SET_VIDEO_HAS_TEXTTRACK',
};
export default ActionTypes;
......@@ -107,6 +107,18 @@ export function toggleAnnotationDisplay(windowId) {
};
}
/**
* toggleAnnotationAutoScroll - action creator
*
* @param {String} windowId
* @memberof ActionCreators
*/
export function toggleAnnotationAutoScroll(windowId) {
return {
type: ActionTypes.TOGGLE_ANNOTATION_AUTOSCROLL, windowId,
};
}
/**
* toggleAnnotationDisplay - action creator
*
......
......@@ -61,6 +61,7 @@ export function addWindow({ companionWindows, manifest, ...options }) {
}
const defaultOptions = {
autoScrollAnnotationList: true,
canvasId: undefined,
collectionIndex: 0,
companionAreaOpen: true,
......@@ -221,6 +222,17 @@ export function setWindowCurrentTime(windowId, currentTime) {
});
}
/** */
export function setWindowSeekTo(windowId, seekToTime) {
return ((dispatch) => {
dispatch({
seekToTime,
type: ActionTypes.SET_SEEK_TO_TIME,
windowId,
});
});
}
/** */
export function setWindowPaused(windowId, paused) {
return ((dispatch) => {
......@@ -242,3 +254,25 @@ export function setWindowMuted(windowId, muted) {
});
});
}
/** */
export function setWindowTextTrackDisabled(windowId, disabled) {
return ((dispatch) => {
dispatch({
textTrackDisabled: (disabled === undefined) ? true : disabled,
type: ActionTypes.SET_VIDEO_TEXTTRACK_DISABLED,
windowId,
});
});
}
/** */
export function setWindowHasTextTrack(windowId, hasTextTrack) {
return ((dispatch) => {
dispatch({
hasTextTrack: (hasTextTrack === undefined) ? false : hasTextTrack,
type: ActionTypes.SET_VIDEO_HAS_TEXTTRACK,
windowId,
});
});
}
......@@ -141,6 +141,14 @@ export const windowsReducer = (state = {}, action) => {
highlightAllAnnotations: !state[action.windowId].highlightAllAnnotations,
},
};
case ActionTypes.TOGGLE_ANNOTATION_AUTOSCROLL:
return {
...state,
[action.windowId]: {
...state[action.windowId],
autoScrollAnnotationList: !state[action.windowId].autoScrollAnnotationList,
},
};
case ActionTypes.IMPORT_MIRADOR_STATE:
return action.state.windows || [];
case ActionTypes.REQUEST_SEARCH:
......@@ -177,6 +185,14 @@ export const windowsReducer = (state = {}, action) => {
currentTime: action.currentTime,
},
};
case ActionTypes.SET_SEEK_TO_TIME:
return {
...state,
[action.windowId]: {
...state[action.windowId],
seekToTime: action.seekToTime,
},
};
case ActionTypes.SET_VIDEO_PAUSED:
return {
...state,
......@@ -193,6 +209,22 @@ export const windowsReducer = (state = {}, action) => {
muted: !!action.muted,
},
};
case ActionTypes.SET_VIDEO_TEXTTRACK_DISABLED:
return {
...state,
[action.windowId]: {
...state[action.windowId],
textTrackDisabled: !!action.textTrackDisabled,
},
};
case ActionTypes.SET_VIDEO_HAS_TEXTTRACK:
return {
...state,
[action.windowId]: {
...state[action.windowId],
hasTextTrack: !!action.hasTextTrack,
},
};
default:
return state;
}
......
......@@ -69,7 +69,10 @@ export const getSearchNumTotal = createSelector(
&& result.json
&& result.json.within
));
return resultWithWithin?.json?.within?.total;
if (resultWithWithin && resultWithWithin.json && resultWithWithin.json.within) {
return resultWithWithin.json.within.total;
}
return undefined;
},
);
......
......@@ -12,6 +12,17 @@ export const getWindowCurrentTime = createSelector(
},
);
export const getWindowSeekToTime = createSelector(
[
getWindow,
],
(window) => {
if (!window) return undefined;
return window.seekToTime;
},
);
export const getWindowPausedStatus = createSelector(
[
getWindow,
......@@ -33,3 +44,25 @@ export const getWindowMutedStatus = createSelector(
return window.muted;
},
);
export const getWindowTextTrackDisabledStatus = createSelector(
[
getWindow,
],
(window) => {
if (!window) return undefined;
return window.textTrackDisabled;
},
);
export const getWindowHasTextTrack = createSelector(
[
getWindow,
],
(window) => {
if (!window) return undefined;
return window.hasTextTrack;
},
);
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment