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
No related branches found
No related tags found
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