diff --git a/src/screens/Watch/Components/Transcriptions/CaptionLine/index.js b/src/screens/Watch/Components/Transcriptions/CaptionLine/index.js
index 79f70ccb..3a7d99cc 100644
--- a/src/screens/Watch/Components/Transcriptions/CaptionLine/index.js
+++ b/src/screens/Watch/Components/Transcriptions/CaptionLine/index.js
@@ -1,7 +1,10 @@
import React, { useRef, useState, useEffect } from 'react';
import { isMobile } from 'react-device-detect';
import * as KeyCode from 'keycode-js';
-
+import {
+ WEBVTT_SUBTITLES,
+ WEBVTT_DESCRIPTIONS,
+} from '../../../Utils/constants.util';
import { prettierTimeStr } from '../../../Utils';
import './index.scss';
@@ -12,7 +15,6 @@ function CaptionLine({ caption = {}, allowEdit, dispatch, fontSize }) {
const endTimeRef = useRef();
const textRef = useRef();
-
const [fullBeginTime, setFullBeginTime] = useState(begin);
const [fullEndTime, setFullEndTime] = useState(end);
const [savedText, setSavedText] = useState(text);
@@ -33,15 +35,13 @@ function CaptionLine({ caption = {}, allowEdit, dispatch, fontSize }) {
const validateText = (input) => {
const MAX_LINE_LENGTH = 42;
let lines = [];
- let violations = [];
+ let violationArr = [];
- const splitText = (text) => {
+ const splitText = (textInput) => {
let currentLine = '';
- let words = text.split(' ');
+ let words = textInput.split(' ');
let currentLineLength = 0;
-
- console.log(`Processing text: "${text}"`);
-
+
words.forEach((word) => {
if (currentLineLength + word.length + (currentLineLength > 0 ? 1 : 0) > MAX_LINE_LENGTH) {
lines.push(currentLine.trim());
@@ -62,36 +62,35 @@ function CaptionLine({ caption = {}, allowEdit, dispatch, fontSize }) {
}
};
splitText(input);
-
- console.log(`Lines after split: ${JSON.stringify(lines)}`);
-
+
lines.forEach((line, index) => {
if (line.length > MAX_LINE_LENGTH) {
- violations.push(`Line ${index + 1} exceeds the max character length.`);
+ violationArr.push(`Line ${index + 1} exceeds the max character length.`);
}
});
if (input.length <= MAX_LINE_LENGTH && lines.length > 1) {
- violations.push("Text is incorrectly flagged as multi-line for a short subtitle.");
+ violationArr.push("Text is incorrectly flagged as multi-line for a short subtitle.");
}
if (lines.length > 2) {
- violations.push('Text exceeds two lines.');
+ violationArr.push('Text exceeds two lines.');
}
-
- console.log(`Violations: ${JSON.stringify(violations)}`);
-
- return { lines, violations };
+
+ return { lines, violationArr };
};
- // The control flow is that a change to time is saved in handleTimeKeyDown,
- // which triggers handleSave, which then changes the displayedTime to the correct truncated time
const handleSave = () => {
- const { lines, violations: textViolations } = validateText(savedText);
+ const { lines, violationArr = [] } = validateText(savedText);
+ const parseTime = (timeStr) => {
+ const [hours, minutes, seconds] = timeStr.split(':').map(Number);
+ const [sec, ms] = seconds.toString().split('.');
+ return hours * 3600 + minutes * 60 + (Number(sec) || 0) + (ms ? parseFloat(`0.${ms}`) : 0);
+ };
- const beginTime = parseFloat(fullBeginTime.replace(/:/g, ''));
- const endTime = parseFloat(fullEndTime.replace(/:/g, ''));
+ const beginTime = parseTime(fullBeginTime);
+ const endTime = parseTime(fullEndTime);
const duration = endTime - beginTime;
const durationViolations = [];
@@ -100,8 +99,8 @@ function CaptionLine({ caption = {}, allowEdit, dispatch, fontSize }) {
} else if (duration > 6) {
durationViolations.push('Caption duration is too long (more than 6 seconds).');
}
-
- const allViolations = [...textViolations, ...durationViolations];
+
+ const allViolations = [...violationArr, ...durationViolations];
dispatch({
type: 'watch/saveCaption',
@@ -109,8 +108,10 @@ function CaptionLine({ caption = {}, allowEdit, dispatch, fontSize }) {
});
setDisplayedStartTime(prettierTimeStr(fullBeginTime, false));
setDisplayedEndTime(prettierTimeStr(fullEndTime, false));
- setViolations(allViolations);
- setIsTextInvalid(allViolations.length > 0);
+ if(kind === WEBVTT_SUBTITLES) {
+ setViolations(allViolations);
+ setIsTextInvalid(allViolations.length > 0);
+ }
};
@@ -118,22 +119,14 @@ function CaptionLine({ caption = {}, allowEdit, dispatch, fontSize }) {
handleSave()
}, [savedText, fullBeginTime, fullEndTime])
- // NOTE: ALL editable text boxes reset the value to the original if the textbox loses focus
- // Users MUST hit enter for their changes to not be lost
const handleTimeBlur = (setDisplayedTime, originalValue) => {
setDisplayedTime(prettierTimeStr(originalValue, false));
};
- // Ideally, you could do something like setSavedText(savedText), akin to how handleTextKeyDown
- // lazy updates savedText, but this won't trigger a DOM update, so we have to do manually
- // update the DOM
const handleTextBlur = () => {
if (textRef.current) {
- // textRef.current.innerText = savedText
- const { lines, violations: newViolations } = validateText(savedText);
+ const { lines, violationArr = [] } = validateText(savedText);
textRef.current.innerText = lines.join('\n');
- setViolations(newViolations);
- setIsTextInvalid(newViolations.length > 0);
}
};
@@ -195,8 +188,15 @@ function CaptionLine({ caption = {}, allowEdit, dispatch, fontSize }) {
className={`watch-caption-line ${isTextInvalid ? 'invalid-text' : ''}`}
kind={kind}
data-unsaved
+ onFocus={() => setIsTextInvalid(violations.length > 0)}
+ onBlur={() => setIsTextInvalid(false)}
>
+ {/* Triangle indicator */}
+ {(violations.length > 0 && kind === WEBVTT_SUBTITLES) && (
+
+ )}
+
{/* Editable Start Time */}
{displayedStartTime}
-
+
{/* Editable Text */}
{savedText}
-
-
{violations.join(', ')}
+
+
0 ? 'show-tooltip' : ''}`}>
+ {violations.length > 0 && {violations.join(', ')}}
+
{/* Editable End Time */}
-
- {/* Action Buttons */}
- {/*
- {true && (
-
Return (save changes). Shift-Return (newline)
- )}
-
-
-
*/}
- );
+ );
}
export default CaptionLine;
diff --git a/src/screens/Watch/Components/Transcriptions/CaptionLine/index.scss b/src/screens/Watch/Components/Transcriptions/CaptionLine/index.scss
index 90205a8c..76848c2a 100644
--- a/src/screens/Watch/Components/Transcriptions/CaptionLine/index.scss
+++ b/src/screens/Watch/Components/Transcriptions/CaptionLine/index.scss
@@ -1,405 +1,425 @@
-/* Time */
-/* Text */
-/* .watch-caption-line[data-unsaved=true] .caption-line-text {
- color: rgb(255, 166, 0);
-} */
-/* Save Button */
-/* .watch-caption-line[data-unsaved=true] .caption-line-btns .caption-line-prompt {
- color: rgb(255, 166, 0);
-} */
-/* Description */
-.watch-caption-line {
- position: relative;
- display: flex;
- flex-direction: column;
- justify-content: center;
- min-height: 4em;
- padding: .7em;
- transition: var(--ct-transition-all);
- -o-transition: var(--ct-transition-all);
- -moz-transition: var(--ct-transition-all);
- -webkit-transition: var(--ct-transition-all);
- border-radius: 5px;
- z-index: 10;
-}
-.watch-caption-line[editing=true] {
- background: rgb(46, 46, 46);
- .caption-line-text {
- color: var(--ct-text-white) !important;
- outline: none;
- font-weight: bold;
- }
- >.caption-line-btns {
- height: 40px;
- }
-}
-.watch-caption-line[hide=true] {
- display: none;
-}
-.caption-line-content {
- position: relative;
- width: 100%;
- display: flex;
- flex-direction: row;
- align-items: flex-start;
- justify-content: flex-start;
-}
-.caption-line-time-display {
- margin-top: 10px;
- margin-right: .5em;
- span {
- display: flex;
- justify-content: center;
- align-items: center;
- padding: .1em .3em;
- color: rgb(209, 209, 209);
- border: 1px solid #75757577;
- border-radius: 2px;
- font-size: 14px;
- font-weight: bold;
- white-space: nowrap;
- }
- &:hover {
- >span {
- color: black;
- background: var(--ct-text-highlight);
- border-color: var(--ct-text-highlight);
- }
- }
-}
-.watch-caption-line[current=true] {
- .caption-line-time-display {
- span {
- color: black;
- background: var(--ct-text-highlight);
- border-color: var(--ct-text-highlight);
- }
- }
- .caption-line-text {
- color: var(--ct-text-white);
- outline: none;
- font-weight: bold;
- &:hover {
- outline: none;
- color: var(--ct-text-highlight);
- }
- }
- .caption-line-text-normal {
- color: var(--ct-text-white);
- outline: none;
- font-weight: bold;
- &:hover {
- outline: none;
- color: var(--ct-text-highlight);
- }
- }
- .caption-line-text-large {
- color: var(--ct-text-white);
- outline: none;
- font-weight: bold;
- &:hover {
- outline: none;
- color: var(--ct-text-highlight);
- }
- }
-
- .caption-line-text-small {
- color: var(--ct-text-white);
- outline: none;
- font-weight: bold;
- &:hover {
- outline: none;
- color: var(--ct-text-highlight);
- }
- }
-}
-.caption-line-text {
- font-weight: bold;
- font-size: 1.2em;
- line-height: 18px;
- min-height: 36px;
- color: rgba(215, 215, 215, 0.747);
- white-space: pre-line;
- cursor: pointer;
- line-break: normal;
- width: 100%;
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
- background: none;
- border: none;
- box-shadow: none;
- text-align: center;
- display: flex;
- flex-direction: row;
- align-items: flex-start;
- justify-content: center;
- resize: none;
- padding-top: 10px;
- &:focus {
- cursor: text;
- color: var(--ct-text-white);
- outline: none;
- font-weight: bold;
- &:hover {
- color: var(--ct-text-white);
- outline: none;
- font-weight: bold;
- }
- }
- &:active {
- color: var(--ct-text-white);
- outline: none;
- font-weight: bold;
- }
- &:hover {
- outline: none;
- color: var(--ct-text-highlight);
- }
-}
-
-.caption-line-text-normal {
- font-weight: bold;
- font-size: 1.2em;
- line-height: 18px;
- min-height: 36px;
- color: rgba(215, 215, 215, 0.747);
- white-space: pre-line;
- cursor: pointer;
- line-break: normal;
- width: 100%;
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
- background: none;
- border: none;
- box-shadow: none;
- text-align: center;
- display: flex;
- flex-direction: row;
- align-items: flex-start;
- justify-content: center;
- resize: none;
- padding-top: 10px;
- &:focus {
- cursor: text;
- color: var(--ct-text-white);
- outline: none;
- font-weight: bold;
- &:hover {
- color: var(--ct-text-white);
- outline: none;
- font-weight: bold;
- }
- }
- &:active {
- color: var(--ct-text-white);
- outline: none;
- font-weight: bold;
- }
- &:hover {
- outline: none;
- color: var(--ct-text-highlight);
- }
-}
-
-.caption-line-text-large {
- font-weight: bold;
- font-size: 1.6em;
- line-height: 18px;
- min-height: 36px;
- color: rgba(215, 215, 215, 0.747);
- white-space: pre-line;
- cursor: pointer;
- line-break: normal;
- width: 100%;
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
- background: none;
- border: none;
- box-shadow: none;
- text-align: center;
- display: flex;
- flex-direction: row;
- align-items: flex-start;
- justify-content: center;
- resize: none;
- padding-top: 10px;
- &:focus {
- cursor: text;
- color: var(--ct-text-white);
- outline: none;
- font-weight: bold;
- &:hover {
- color: var(--ct-text-white);
- outline: none;
- font-weight: bold;
- }
- }
- &:active {
- color: var(--ct-text-white);
- outline: none;
- font-weight: bold;
- }
- &:hover {
- outline: none;
- color: var(--ct-text-highlight);
- }}
-
-.caption-line-text-small {
- font-weight: bold;
- font-size: .8em;
- line-height: 18px;
- min-height: 36px;
- color: rgba(215, 215, 215, 0.747);
- white-space: pre-line;
- cursor: pointer;
- line-break: normal;
- width: 100%;
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
- background: none;
- border: none;
- box-shadow: none;
- text-align: center;
- display: flex;
- flex-direction: row;
- align-items: flex-start;
- justify-content: center;
- resize: none;
- padding-top: 10px;
- &:focus {
- cursor: text;
- color: var(--ct-text-white);
- outline: none;
- font-weight: bold;
- &:hover {
- color: var(--ct-text-white);
- outline: none;
- font-weight: bold;
- }
- }
- &:active {
- color: var(--ct-text-white);
- outline: none;
- font-weight: bold;
- }
- &:hover {
- outline: none;
- color: var(--ct-text-highlight);
- }}
-
-.caption-line-btns {
- position: relative;
- width: 100%;
- display: flex;
- flex-direction: row;
- align-items: center;
- justify-content: flex-end;
- overflow: hidden;
- height: 0;
- font-family: var(--ct-font-google);
- transition: var(--ct-transition-all);
- -o-transition: var(--ct-transition-all);
- -moz-transition: var(--ct-transition-all);
- -webkit-transition: var(--ct-transition-all);
- .caption-line-prompt {
- color: rgb(168, 168, 168);
- }
-}
-.caption-line-save-btn {
- margin-top: .5em;
- color: white;
- background: rgb(97, 97, 97);
- padding: .5em 1em;
- border-radius: 20px;
- font-weight: bold;
- margin-right: .5em;
- transition: var(--ct-transition-all);
- -o-transition: var(--ct-transition-all);
- -moz-transition: var(--ct-transition-all);
- -webkit-transition: var(--ct-transition-all);
- &:hover {
- outline: none;
- background: rgb(88, 88, 88);
- }
- &:focus {
- outline: none;
- background: rgb(88, 88, 88);
- }
-}
-.watch-caption-line[kind=descriptions] {
- border: 1px solid grey;
- margin-top: 10px;
- margin-bottom: 10px;
- .caption-line-time-display {
- &:hover {
- >span {
- background: rgb(255, 213, 28);
- border-color: rgb(255, 213, 28);
- }
- }
- &:focus {
- >span {
- background: rgb(255, 213, 28);
- border-color: rgb(255, 213, 28);
- }
- }
- }
-}
-.description-line-text {
- width: 100%;
- text-align: center;
- font-weight: bold;
- font-style: italic;
- font-size: 1.3em;
-}
-.watch-caption-line[kind=descriptions][current=true] {
- background: rgba(209, 209, 209, 0.178);
- .caption-line-time-display {
- span {
- background: rgb(255, 213, 28);
- border-color: rgb(255, 213, 28);
- }
- }
-}
-.description-line-text-title {
- font-size: 0.8em;
- line-height: 2em;
- color: var(--ct-text-white-hover);
-}
-
-.invalid-text {
- color: red !important;
-}
-
-.tooltip {
- visibility: hidden;
- width: 200px;
- background-color: rgba(0, 0, 0, 0.8);
- color: #fff;
- text-align: center;
- border-radius: 5px;
- padding: 8px;
- position: absolute;
- z-index: 1;
- bottom: 100%;
- left: 50%;
- transform: translateX(-50%);
- opacity: 0;
- transition: opacity 0.2s ease-in-out;
-}
-
-.tooltip::after {
- content: '';
- position: absolute;
- top: 100%;
- left: 50%;
- margin-left: -5px;
- border-width: 5px;
- border-style: solid;
- border-color: rgba(0, 0, 0, 0.8) transparent transparent transparent;
-}
-
-.caption-line-text-small:hover .tooltip,
-.caption-line-text-medium:hover .tooltip,
-.caption-line-text-large:hover .tooltip,
-.caption-line-text-normal:hover .tooltip,
-.watch-caption-line.invalid-text:hover .tooltip {
- visibility: visible;
- opacity: 1;
-}
+/* Time */
+/* Text */
+/* .watch-caption-line[data-unsaved=true] .caption-line-text {
+ color: rgb(255, 166, 0);
+} */
+/* Save Button */
+/* .watch-caption-line[data-unsaved=true] .caption-line-btns .caption-line-prompt {
+ color: rgb(255, 166, 0);
+} */
+/* Description */
+.watch-caption-line {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ min-height: 4em;
+ padding: .7em;
+ transition: var(--ct-transition-all);
+ -o-transition: var(--ct-transition-all);
+ -moz-transition: var(--ct-transition-all);
+ -webkit-transition: var(--ct-transition-all);
+ border-radius: 5px;
+ z-index: 10;
+}
+.watch-caption-line[editing=true] {
+ background: rgb(46, 46, 46);
+ .caption-line-text {
+ color: var(--ct-text-white) !important;
+ outline: none;
+ font-weight: bold;
+ }
+ >.caption-line-btns {
+ height: 40px;
+ }
+}
+.watch-caption-line[hide=true] {
+ display: none;
+}
+.caption-line-content {
+ position: relative;
+ width: 100%;
+ display: flex;
+ flex-direction: row;
+ align-items: flex-start;
+ justify-content: flex-start;
+}
+.caption-line-time-display {
+ margin-top: 10px;
+ // margin-right: .5em;
+ span {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ padding: .1em .3em;
+ color: rgb(209, 209, 209);
+ border: 1px solid #75757577;
+ border-radius: 2px;
+ font-size: 14px;
+ font-weight: bold;
+ white-space: nowrap;
+ }
+ &:hover {
+ >span {
+ color: black;
+ background: var(--ct-text-highlight);
+ border-color: var(--ct-text-highlight);
+ }
+ }
+}
+.watch-caption-line[current=true] {
+ .caption-line-time-display {
+ span {
+ color: black;
+ background: var(--ct-text-highlight);
+ border-color: var(--ct-text-highlight);
+ }
+ }
+ .caption-line-text {
+ color: var(--ct-text-white);
+ outline: none;
+ font-weight: bold;
+ &:hover {
+ outline: none;
+ color: var(--ct-text-highlight);
+ }
+ }
+ .caption-line-text-normal {
+ color: var(--ct-text-white);
+ outline: none;
+ font-weight: bold;
+ &:hover {
+ outline: none;
+ color: var(--ct-text-highlight);
+ }
+ }
+ .caption-line-text-large {
+ color: var(--ct-text-white);
+ outline: none;
+ font-weight: bold;
+ &:hover {
+ outline: none;
+ color: var(--ct-text-highlight);
+ }
+ }
+
+ .caption-line-text-small {
+ color: var(--ct-text-white);
+ outline: none;
+ font-weight: bold;
+ &:hover {
+ outline: none;
+ color: var(--ct-text-highlight);
+ }
+ }
+}
+.caption-line-text {
+ font-weight: bold;
+ font-size: 1.2em;
+ line-height: 18px;
+ min-height: 36px;
+ color: rgba(215, 215, 215, 0.747);
+ white-space: pre-line;
+ cursor: pointer;
+ line-break: normal;
+ width: 100%;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
+ background: none;
+ border: none;
+ box-shadow: none;
+ text-align: center;
+ display: flex;
+ flex-direction: row;
+ align-items: flex-start;
+ justify-content: center;
+ resize: none;
+ padding-top: 10px;
+ &:focus {
+ cursor: text;
+ color: var(--ct-text-white);
+ outline: none;
+ font-weight: bold;
+ &:hover {
+ color: var(--ct-text-white);
+ outline: none;
+ font-weight: bold;
+ }
+ }
+ &:active {
+ color: var(--ct-text-white);
+ outline: none;
+ font-weight: bold;
+ }
+ &:hover {
+ outline: none;
+ color: var(--ct-text-highlight);
+ }
+}
+
+.caption-line-text-normal {
+ font-weight: bold;
+ font-size: 1.2em;
+ line-height: 18px;
+ min-height: 36px;
+ color: rgba(215, 215, 215, 0.747);
+ white-space: pre-line;
+ cursor: pointer;
+ line-break: normal;
+ width: 100%;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
+ background: none;
+ border: none;
+ box-shadow: none;
+ text-align: center;
+ display: flex;
+ flex-direction: row;
+ align-items: flex-start;
+ justify-content: center;
+ resize: none;
+ padding-top: 10px;
+ &:focus {
+ cursor: text;
+ color: var(--ct-text-white);
+ outline: none;
+ font-weight: bold;
+ &:hover {
+ color: var(--ct-text-white);
+ outline: none;
+ font-weight: bold;
+ }
+ }
+ &:active {
+ color: var(--ct-text-white);
+ outline: none;
+ font-weight: bold;
+ }
+ &:hover {
+ outline: none;
+ color: var(--ct-text-highlight);
+ }
+}
+
+.caption-line-text-large {
+ font-weight: bold;
+ font-size: 1.6em;
+ line-height: 18px;
+ min-height: 36px;
+ color: rgba(215, 215, 215, 0.747);
+ white-space: pre-line;
+ cursor: pointer;
+ line-break: normal;
+ width: 100%;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
+ background: none;
+ border: none;
+ box-shadow: none;
+ text-align: center;
+ display: flex;
+ flex-direction: row;
+ align-items: flex-start;
+ justify-content: center;
+ resize: none;
+ padding-top: 10px;
+ &:focus {
+ cursor: text;
+ color: var(--ct-text-white);
+ outline: none;
+ font-weight: bold;
+ &:hover {
+ color: var(--ct-text-white);
+ outline: none;
+ font-weight: bold;
+ }
+ }
+ &:active {
+ color: var(--ct-text-white);
+ outline: none;
+ font-weight: bold;
+ }
+ &:hover {
+ outline: none;
+ color: var(--ct-text-highlight);
+ }}
+
+.caption-line-text-small {
+ font-weight: bold;
+ font-size: .8em;
+ line-height: 18px;
+ min-height: 36px;
+ color: rgba(215, 215, 215, 0.747);
+ white-space: pre-line;
+ cursor: pointer;
+ line-break: normal;
+ width: 100%;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
+ background: none;
+ border: none;
+ box-shadow: none;
+ text-align: center;
+ display: flex;
+ flex-direction: row;
+ align-items: flex-start;
+ justify-content: center;
+ resize: none;
+ padding-top: 10px;
+ &:focus {
+ cursor: text;
+ color: var(--ct-text-white);
+ outline: none;
+ font-weight: bold;
+ &:hover {
+ color: var(--ct-text-white);
+ outline: none;
+ font-weight: bold;
+ }
+ }
+ &:active {
+ color: var(--ct-text-white);
+ outline: none;
+ font-weight: bold;
+ }
+ &:hover {
+ outline: none;
+ color: var(--ct-text-highlight);
+ }}
+
+.caption-line-btns {
+ position: relative;
+ width: 100%;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: flex-end;
+ overflow: hidden;
+ height: 0;
+ font-family: var(--ct-font-google);
+ transition: var(--ct-transition-all);
+ -o-transition: var(--ct-transition-all);
+ -moz-transition: var(--ct-transition-all);
+ -webkit-transition: var(--ct-transition-all);
+ .caption-line-prompt {
+ color: rgb(168, 168, 168);
+ }
+}
+.caption-line-save-btn {
+ margin-top: .5em;
+ color: white;
+ background: rgb(97, 97, 97);
+ padding: .5em 1em;
+ border-radius: 20px;
+ font-weight: bold;
+ margin-right: .5em;
+ transition: var(--ct-transition-all);
+ -o-transition: var(--ct-transition-all);
+ -moz-transition: var(--ct-transition-all);
+ -webkit-transition: var(--ct-transition-all);
+ &:hover {
+ outline: none;
+ background: rgb(88, 88, 88);
+ }
+ &:focus {
+ outline: none;
+ background: rgb(88, 88, 88);
+ }
+}
+.watch-caption-line[kind=descriptions] {
+ border: 1px solid grey;
+ margin-top: 10px;
+ margin-bottom: 10px;
+ .caption-line-time-display {
+ &:hover {
+ >span {
+ background: rgb(255, 213, 28);
+ border-color: rgb(255, 213, 28);
+ }
+ }
+ &:focus {
+ >span {
+ background: rgb(255, 213, 28);
+ border-color: rgb(255, 213, 28);
+ }
+ }
+ }
+}
+
+.description-line-text {
+ width: 100%;
+ text-align: center;
+ font-weight: bold;
+ font-style: italic;
+ font-size: 1.3em;
+}
+.watch-caption-line[kind=descriptions][current=true] {
+ background: rgba(209, 209, 209, 0.178);
+ .caption-line-time-display {
+ span {
+ background: rgb(255, 213, 28);
+ border-color: rgb(255, 213, 28);
+ }
+ }
+}
+.description-line-text-title {
+ font-size: 0.8em;
+ line-height: 2em;
+ color: var(--ct-text-white-hover);
+}
+.invalid-text {
+ position: relative; /* To position the triangle */
+}
+
+.tooltip {
+ visibility: hidden;
+ width: 200px;
+ background-color: rgba(0, 0, 0, 0.8);
+ border-color: white;
+ border-style: groove;
+ color: #fff;
+ text-align: center;
+ border-radius: 5px;
+ padding: 8px;
+ position: absolute;
+ z-index: 1;
+ bottom: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ opacity: 0;
+ transition: opacity 0.2s ease-in-out;
+}
+
+.tooltip::after {
+ content: '';
+ position: absolute;
+ top: 100%;
+ left: 50%;
+ margin-left: -5px;
+ border-width: 5px;
+ border-style: solid;
+ border-color: rgba(0, 0, 0, 0.8) transparent transparent transparent;
+}
+
+.caption-line-text-small:hover .tooltip,
+.caption-line-text-medium:hover .tooltip,
+.caption-line-text-large:hover .tooltip,
+.caption-line-text-normal:hover .tooltip,
+.watch-caption-line.invalid-text:hover .tooltip {
+ visibility: visible;
+ opacity: 1;
+}
+
+.watch-caption-line:focus-within .tooltip {
+ visibility: visible;
+ opacity: 1;
+}
+
+.triangle-indicator {
+ // position: absolute;
+ // left: -45px;
+ // top: 50%;
+ // transform: translateY(-50%);
+ width: 0;
+ height: 0;
+ border-left: 6px solid transparent;
+ border-right: 6px solid transparent;
+ border-bottom: 10px solid rgb(204, 0, 0);
+ opacity: 1;
+}
diff --git a/src/screens/Watch/Utils/helpers.js b/src/screens/Watch/Utils/helpers.js
index b64cfde7..84dec2b0 100644
--- a/src/screens/Watch/Utils/helpers.js
+++ b/src/screens/Watch/Utils/helpers.js
@@ -1,121 +1,121 @@
-import { uurl, links } from 'utils';
-import moment from 'moment';
-import { cc_colorMap, CC_COLOR_BLACK } from './constants.util';
-
-/**
- * Convert seconds to a readable time string `H:mm:ss`
- * @param {Number} sec - seconds
- * @returns {String} H:mm:ss
- */
-export function parseSec(sec) {
- const formatter = sec < 3600 ? 'mm:ss' : 'H:mm:ss';
- return moment().startOf('day').seconds(sec).format(formatter);
-}
-
-/**
- * Parse time string H:mm:ss to seconds
- * @param {String} str - time string H:mm:ss
- * @returns {Number} seconds
- */
-export function timeStrToSec(str) {
- if (typeof str !== 'string') return '';
- const strs = str.split(':');
- const len3 = strs.length > 2;
- const sec = (len3 ? parseFloat(strs[2]) : parseFloat(strs[1])) || 0;
- const min = (len3 ? parseFloat(strs[1]) : parseFloat(strs[0])) * 60 || 0;
- const hr = (len3 ? parseFloat(strs[0]) : 0) * 3600 || 0;
- return sec + min + hr;
-}
-
-/**
- * @param {Number} time current time
- * @returns {()=>Boolean}
- */
-export function isEarlier(time) {
- return ({ begin }) => time >= timeStrToSec(begin);
-}
-
-/**
- * @param {Number} time current time
- * @returns {()=>Boolean}
- */
-export function isLater(time) {
- return ({ begin }) => time <= timeStrToSec(begin);
-}
-
-export function prettierTimeStr(time, showMilliseconds = false) {
- if (typeof time !== 'string') return '';
-
- const parts = time.split(':').map((part) => parseFloat(part));
- let hours = 0;
- let mins = 0;
- let secs = 0;
- let millis = 0;
-
- if (parts.length === 3) {
- hours = parts[0];
- mins = parts[1];
- secs = Math.floor(parts[2]);
- millis = Math.round((parts[2] % 1) * 1000);
- } else if (parts.length === 2) {
- mins = parts[0];
- secs = Math.floor(parts[1]);
- millis = Math.round((parts[1] % 1) * 1000);
- } else if (parts.length === 1) {
- secs = Math.floor(parts[0]);
- millis = Math.round((parts[0] % 1) * 1000);
- }
-
- const format = (num, digits = 2) => String(num).padStart(digits, '0');
- const formattedTime = `${format(hours)}:${format(mins)}:${format(secs)}`;
-
- return showMilliseconds
- ? `${formattedTime}.${format(millis, 3)}`
- : formattedTime;
-}
-
-
-
-// export function prettierTimeStr(str) {
-// if (typeof str !== 'string') return '';
-
-// const strs = str.split(':');
-// if (strs.length !== 3) return ''; // Ensure the input is in HH:MM:SS format
-
-// let hours = parseInt(strs[0], 10);
-// let mins = parseInt(strs[1], 10);
-// let sec = parseInt(strs[2], 10);
-
-// // Format minutes and seconds to two digits
-// if (hours < 10) hours = `0${hours}`;
-// if (mins < 10) mins = `0${mins}`;
-// if (sec < 10) sec = `0${sec}`;
-
-// return `${hours}:${mins}:${sec}`;
-// }
-
-
-export function getCCSelectOptions(array = [], operation = (item) => item) {
- const options = [];
- array.forEach((item) => {
- const text = operation(item);
- options.push({ text, value: item });
- });
- return options;
-}
-
-export function colorMap(color = CC_COLOR_BLACK, opacity = 1) {
- const colorStr = cc_colorMap[color];
- if (!colorStr) return CC_COLOR_BLACK;
- return colorStr.replace('*', opacity);
-}
-
-/** handle Share */
-// Get share url
-export function getShareableURL(begin = 0) {
- const { origin } = window.location;
- const { id } = uurl.useSearch();
- const pathname = links.watch(id, { begin, from: 'sharedlink' });
-
- return origin + pathname;
-}
+import { uurl, links } from 'utils';
+import moment from 'moment';
+import { cc_colorMap, CC_COLOR_BLACK } from './constants.util';
+
+/**
+ * Convert seconds to a readable time string `H:mm:ss`
+ * @param {Number} sec - seconds
+ * @returns {String} H:mm:ss
+ */
+export function parseSec(sec) {
+ const formatter = sec < 3600 ? 'mm:ss' : 'H:mm:ss';
+ return moment().startOf('day').seconds(sec).format(formatter);
+}
+
+/**
+ * Parse time string H:mm:ss to seconds
+ * @param {String} str - time string H:mm:ss
+ * @returns {Number} seconds
+ */
+export function timeStrToSec(str) {
+ if (typeof str !== 'string') return '';
+ const strs = str.split(':');
+ const len3 = strs.length > 2;
+ const sec = (len3 ? parseFloat(strs[2]) : parseFloat(strs[1])) || 0;
+ const min = (len3 ? parseFloat(strs[1]) : parseFloat(strs[0])) * 60 || 0;
+ const hr = (len3 ? parseFloat(strs[0]) : 0) * 3600 || 0;
+ return sec + min + hr;
+}
+
+/**
+ * @param {Number} time current time
+ * @returns {()=>Boolean}
+ */
+export function isEarlier(time) {
+ return ({ begin }) => time >= timeStrToSec(begin);
+}
+
+/**
+ * @param {Number} time current time
+ * @returns {()=>Boolean}
+ */
+export function isLater(time) {
+ return ({ begin }) => time <= timeStrToSec(begin);
+}
+
+export function prettierTimeStr(time, showMilliseconds = false) {
+ if (typeof time !== 'string') return '';
+
+ const parts = time.split(':').map((part) => parseFloat(part));
+ let hours = 0;
+ let mins = 0;
+ let secs = 0;
+ let millis = 0;
+
+ if (parts.length === 3) {
+ hours = parts[0];
+ mins = parts[1];
+ secs = Math.floor(parts[2]);
+ millis = Math.round((parts[2] % 1) * 1000);
+ } else if (parts.length === 2) {
+ mins = parts[0];
+ secs = Math.floor(parts[1]);
+ millis = Math.round((parts[1] % 1) * 1000);
+ } else if (parts.length === 1) {
+ secs = Math.floor(parts[0]);
+ millis = Math.round((parts[0] % 1) * 1000);
+ }
+
+ const format = (num, digits = 2) => String(num).padStart(digits, '0');
+ const formattedTime = `${format(hours)}:${format(mins)}:${format(secs)}`;
+
+ return showMilliseconds
+ ? `${formattedTime}.${format(millis, 3)}`
+ : formattedTime;
+}
+
+
+
+// export function prettierTimeStr(str) {
+// if (typeof str !== 'string') return '';
+
+// const strs = str.split(':');
+// if (strs.length !== 3) return ''; // Ensure the input is in HH:MM:SS format
+
+// let hours = parseInt(strs[0], 10);
+// let mins = parseInt(strs[1], 10);
+// let sec = parseInt(strs[2], 10);
+
+// // Format minutes and seconds to two digits
+// if (hours < 10) hours = `0${hours}`;
+// if (mins < 10) mins = `0${mins}`;
+// if (sec < 10) sec = `0${sec}`;
+
+// return `${hours}:${mins}:${sec}`;
+// }
+
+
+export function getCCSelectOptions(array = [], operation = (item) => item) {
+ const options = [];
+ array.forEach((item) => {
+ const text = operation(item);
+ options.push({ text, value: item });
+ });
+ return options;
+}
+
+export function colorMap(color = CC_COLOR_BLACK, opacity = 1) {
+ const colorStr = cc_colorMap[color];
+ if (!colorStr) return CC_COLOR_BLACK;
+ return colorStr.replace('*', opacity);
+}
+
+/** handle Share */
+// Get share url
+export function getShareableURL(begin = 0) {
+ const { origin } = window.location;
+ const { id } = uurl.useSearch();
+ const pathname = links.watch(id, { begin, from: 'sharedlink' });
+
+ return origin + pathname;
+}
diff --git a/src/screens/Watch/Utils/prompt.control.js b/src/screens/Watch/Utils/prompt.control.js
index 97c58035..09071c8f 100644
--- a/src/screens/Watch/Utils/prompt.control.js
+++ b/src/screens/Watch/Utils/prompt.control.js
@@ -1,81 +1,81 @@
-import { prompt } from 'utils';
-
-/**
- * Functions for controlling prompts
- */
-const standardPosition = [70, 70];
-
-export const promptControl = {
-
- closePrompt() {
- prompt.closeAll();
- },
-
- editCaptionUsingKeyboard() {
- prompt.addOne({
- status: 'success',
- text: 'You are editing the current caption! Hit return to save changes.',
- offset: standardPosition,
- });
- },
-
- editCaptionTips() {
- prompt.addOne({
- text: 'Hit return to save your changes!',
- offset: standardPosition,
- });
- },
-
- savingCaption() { },
-
- savedCaption(isClosedCaption, success = true) {
- const captionType = isClosedCaption ? 'Closed Caption' : 'Description'
- prompt.addOne({
- status: success ? 'success' : 'error',
- text: success ? `${captionType} updated` : `${captionType} could not be saved`,
- offset: standardPosition,
- timeout: 3000,
- });
- },
-
- timestampFailed(isClosedCaption) {
- const captionType = isClosedCaption ? 'Closed Caption' : 'Description'
- prompt.addOne({
- status: 'error',
- text: `${captionType} could not be saved. Timestamps must be in HH:MM:SS.MS format`,
- offset: standardPosition,
- timeout: 3000,
- });
- },
-
- hideSecondaryScreen() {
- prompt.addOne({
- text: 'Click
video_label to see more screen options.',
- offset: standardPosition,
- timeout: 5000,
- });
- },
-
- error(target = 'media data') {
- const { search, pathname } = window.location;
- prompt.addOne({
- text: `Couldn't load ${target}. Please
refresh to retry.`,
- offset: standardPosition,
- status: 'error',
- });
- },
-
- videoNotLoading() {
- prompt.addOne({
- text: `Sorry, if the video can't load, please use Chrome to open the page.
-
Click to open in Chrome`,
- position: 'top',
- status: 'error',
- });
- },
-};
+import { prompt } from 'utils';
+
+/**
+ * Functions for controlling prompts
+ */
+const standardPosition = [70, 70];
+
+export const promptControl = {
+
+ closePrompt() {
+ prompt.closeAll();
+ },
+
+ editCaptionUsingKeyboard() {
+ prompt.addOne({
+ status: 'success',
+ text: 'You are editing the current caption! Hit return to save changes.',
+ offset: standardPosition,
+ });
+ },
+
+ editCaptionTips() {
+ prompt.addOne({
+ text: 'Hit return to save your changes!',
+ offset: standardPosition,
+ });
+ },
+
+ savingCaption() { },
+
+ savedCaption(isClosedCaption, success = true) {
+ const captionType = isClosedCaption ? 'Closed Caption' : 'Description'
+ prompt.addOne({
+ status: success ? 'success' : 'error',
+ text: success ? `${captionType} updated` : `${captionType} could not be saved`,
+ offset: standardPosition,
+ timeout: 3000,
+ });
+ },
+
+ timestampFailed(isClosedCaption) {
+ const captionType = isClosedCaption ? 'Closed Caption' : 'Description'
+ prompt.addOne({
+ status: 'error',
+ text: `${captionType} could not be saved. Timestamps must be in HH:MM:SS.MS format`,
+ offset: standardPosition,
+ timeout: 3000,
+ });
+ },
+
+ hideSecondaryScreen() {
+ prompt.addOne({
+ text: 'Click
video_label to see more screen options.',
+ offset: standardPosition,
+ timeout: 5000,
+ });
+ },
+
+ error(target = 'media data') {
+ const { search, pathname } = window.location;
+ prompt.addOne({
+ text: `Couldn't load ${target}. Please
refresh to retry.`,
+ offset: standardPosition,
+ status: 'error',
+ });
+ },
+
+ videoNotLoading() {
+ prompt.addOne({
+ text: `Sorry, if the video can't load, please use Chrome to open the page.
+
Click to open in Chrome`,
+ position: 'top',
+ status: 'error',
+ });
+ },
+};
diff --git a/src/screens/Watch/model.js b/src/screens/Watch/model.js
index 2a7802eb..98f01bb1 100644
--- a/src/screens/Watch/model.js
+++ b/src/screens/Watch/model.js
@@ -1,512 +1,512 @@
-/* eslint-disable no-console */
-import { isSafari, isIPad13, isIPhone13, isMobile } from 'react-device-detect';
-import { api, prompt, uurl } from 'utils';
-import _ from 'lodash';
-import { ARRAY_INIT, DEFAULT_ROLE } from 'utils/constants';
-import { timeStrToSec } from './Utils/helpers';
-import PlayerData from './player'
-import {
- WEBVTT_SUBTITLES,
- SEARCH_HIDE,
- WEBVTT_DESCRIPTIONS,
- ARRAY_EMPTY,
- // PROFANITY_LIST,
-} from './Utils/constants.util';
-
-import { uEvent } from './Utils/UserEventController';
-import { promptControl } from './Utils/prompt.control';
-import setup from './model/setup'
-import player_effects from './model/player_effects'
-import menu_effects from './model/menu_effects'
-import trans_effects from './model/trans_effects'
-import search_effects from './model/search_effects'
-import {
- // constants
- MENU_HIDE,
- NORMAL_MODE,
- SEARCH_INIT,
- MODAL_HIDE,
- CTP_LOADING,
- CTP_PLAYING,
- ERR_INVALID_MEDIA_ID,
- ERR_AUTH
- // MODAL_SHARE
-} from './Utils';
-
-
-const initState = {
- // Basics
- userRole: DEFAULT_ROLE,
-
- error: null,
-
- // Metadata
- media: {
- id: '',
- mediaName: '',
- createdAt: '',
- isTwoScreen: false,
- hasASL: false,
- videos: [],
- transcriptions: [],
- isUnavailable: false,
- flashDetected: false
- },
- flashAcknowledged: false,
- playlist: {},
- playlists: [],
- offering: {},
- watchHistory: [],
- starredOfferings: [],
-
- // VideoInfo
- time: 0,
- duration: 0,
- bufferedTime: 0,
- isSwitched: false,
- paused: true,
-
- isFullscreen: false,
- isFullscreenTwo: false,
- ctpPriEvent: CTP_LOADING,
- ctpSecEvent: CTP_LOADING,
-
- // Trans
- transcriptions: [],
- currTrans: {},
- trackerMap: new Map(),
- transcript: [],
- captions: [],
- currCaption: null,
- descriptions: [],
- currDescription: null,
- currEditing: null,
- bulkEditing: false,
- updating: false,
- currCaptionIndex: 0,
- captionSpeedUp: 0,
- offSet: 0,
- sliderOffSet: 0,
- fontSize: 'normal',
- eventListener: undefined,
-
- // screen options
- mode: NORMAL_MODE,
-
- menu: MENU_HIDE,
- modal: MODAL_HIDE,
- liveMode: false,
- englishTrack: undefined,
- currentAudioTrack: 0,
- audioTracks: undefined,
-
- // Others
- prompt: null,
- search: SEARCH_INIT,
- mouseOnCaption: false,
- embedded: false,
- textTracks: [],
-}
-/**
-* Function used to union two caption arrays
-* Merging is based on the { begin, end } of each entry in the arrays
-*/
-// const unionTranscript = (captions, source) => {
-// let union = _.concat(
-// captions === ARRAY_EMPTY ? [] : captions,
-// source === ARRAY_EMPTY ? [] : source,
-// );
-// //
-// union = _.sortBy(union, (item) => timeStrToSec(item.begin));
-// union = _.map(union, (item, index) => ({ ...item, index }));
-// return union;
-// }
-
-const WatchModel = {
- namespace: 'watch',
- state: { ...initState },
- reducers: {
- // Metadata
- setError(state, { payload }) {
- return { ...state, error: payload };
- },
-
- setMedia(state, { payload }) {
- return { ...state, media: payload, embedded: false, liveMode: payload.isLive ? 1 : 0 };
- },
- setEmbeddedMedia(state, { payload: { media, ...embeded_payload } }) {
- return {
- ...state, media,
- embedded: embeded_payload,
- liveMode: media.isLive ? 1 : 0
- };
- },
- setLiveMode(state, { payload }) {
- return { ...state, liveMode: payload };
- },
- setTextTracks(state, { payload }) {
- return { ...state, textTracks: payload };
- },
-
- setAudioTracks(state, { payload }) {
- return { ...state, audioTracks: payload };
- },
- setCurrCaptionIndex(state, { payload }) {
- return { ...state, currCaptionIndex: payload };
- },
- setPlaylist(state, { payload }) {
- return { ...state, playlist: payload };
- },
- setPlaylists(state, { payload }) {
- return { ...state, playlists: payload };
- },
- setOffering(state, { payload }) {
- return { ...state, offering: payload };
- },
- setEventListener(state, { payload }) {
- return { ...state, setEventListener: payload };
- },
- setWatchHistory(state, { payload }) {
- return { ...state, watchHistory: payload };
- },
- setOffSet(state, { payload }) {
- return { ...state, offSet: payload };
- },
-
- setCaptionSpeedUp(state, { payload }) {
- return { ...state, captionSpeedUp: payload };
- },
- setStarredOfferings(state, { payload }) {
- return { ...state, starredOfferings: payload };
- },
- setEnglishTrack(state, { payload }) {
- if(state.englishTrack !== undefined) {
- // state.englishTrack.mode = 'hidden';
- // state.englishTrack.removeEventListener('cuechange', state.eventListener);
-
- }
- let currTrack = document.getElementsByTagName('video')[0].textTracks;
- return { ...state, englishTrack: currTrack[payload], transcript: []};
- },
-
- setFullscreen(state, { payload }) {
- return { ...state, isFullscreen: payload };
- },
- setFullscreenTwo(state, { payload }) {
- return { ...state, isFullscreenTwo: payload };
- },
- // Transcription
- setTranscriptions(state, { payload }) {
- return { ...state, transcriptions: payload };
- },
- // setCurrTrans(state, { payload }) {
- // return { ...state, currTrans: payload };
- // },
- setCurrentTranscriptionMulti(state, { payload }) {
- const { transKey, active } = payload;
- let { currentTranscriptionMulti = {transKeysSelected:[] } } = state;
- let newKeys = currentTranscriptionMulti.transKeysSelected.filter(i => (i !== transKey))
- if( active ) {
- newKeys.push(transKey)
- }
- return { ...state, currentTranscriptionMulti: {transKeysSelected: newKeys} };
- },
- setUpdating(state, { payload }) {
- //
- return { ...state, updating: payload };
- },
-
- // Test live caption font size change
- setFontSize(state, { payload }) {
- return { ...state, fontSize: payload };
- },
-
- // setup the transcript UI model; the caption and description data must already be loaded
- // eslint-disable-next-line no-unused-vars
- setTranscript(state, _unused) {
- // Todo check that the payload is immutable because we use the subobjects in our immutable model
- // console.log("setTranscript")
- let all = [... state.captions,...state.descriptions]
-
- let transcript = all;
- // Using String sort so numbers (1.123 21.0) must be right aligned with same number of decimal places
- // Put Closed Captions after Descriptions
- transcript = _.sortBy(transcript, (item) => `${timeStrToSec(item.begin).toFixed(2).padStart(10)}/${item.transcription.transcriptionType === 0?'Z':item.transcription.transcriptionType}`);
- transcript= _.map(transcript, (item, index) => ({ ...item, index }));
-
- if (transcript.length === 0) transcript = ARRAY_EMPTY;
-
- return { ...state, transcript };
- }
- ,
- /**
- * Function called for setting captions array
- */
- setCaptions(state, { payload }) {
- // console.log(`setCaptions ${payload.length}`)
- let parsedCap = _.map(payload, (c) => ({ ...c, kind: WEBVTT_SUBTITLES }));
- // if (parsedCap.length === 0) parsedCap = ARRAY_EMPTY;
- return { ...state, captions: parsedCap };
- },
- setCurrCaption(state, { payload }) {
- return {...state, currCaption: payload}
- },
- /**
- * * Function called for get or set audio descriptions
- *
- */
- setDescriptions(state, { payload }) {
- const parsedDes = _.map(payload, (d) => ({ ...d, kind: WEBVTT_DESCRIPTIONS }));
- return { ...state, descriptions: parsedDes };
- },
- setCurrDescription(state, { payload }) {
- return { ...state, currDescription: payload };
- },
- setCurrEditing(state, { payload }) {
- return { ...state, currEditing: payload };
- },
- setBulkEditing(state, { payload }) {
- return { ...state, bulkEditing: payload };
- },
-
- // Settings
- setMode(state, { payload }) {
- return { ...state, mode: payload, prevmode: state.mode };
- },
- setMenu(state, { payload }) {
- return { ...state, menu: payload };
- },
- setModal(state, { payload }) {
- return { ...state, modal: payload };
- },
-
- setTime(state, { payload }) {
- let liveMode = state.liveMode
- if(state.liveMode === 1) {
- liveMode = payload < state.duration - 60 ? 2 : 1
- }
-
- return { ...state, time: payload, liveMode };
- },
- setBufferedTime(state, { payload }) {
- return { ...state, bufferedTime: payload };
- },
- setDuration(state, { payload }) {
- return { ...state, duration: payload };
- },
- switchScreen(state, { payload }) {
- return { ...state, isSwitched: payload };
- },
- setMouseOnCaption(state, { payload }) {
- return { ...state, mouseOnCaption: payload };
- },
- setPause(state, { payload }) {
- return { ...state, paused: payload };
- },
- setCTPEvent(state, { payload: { event = CTP_PLAYING, priVideo = true } }) {
- if (priVideo) {
- return { ...state, ctpPriEvent: event };
- }
- return { ...state, ctpSecEvent: event };
- },
- // Others
- setSearch(state, { payload }) {
- return { ...state, search: { ...state.search, ...payload } };
- },
- resetSearch(state, { payload: status = SEARCH_HIDE }) {
- return {
- ...state, search: {
- status,
- value: '',
- inVideoTransResults: ARRAY_INIT,
- inCourseTransResults: ARRAY_INIT,
- playlistResults: ARRAY_INIT,
- }
- }
- },
- setPrompt(state, { payload }) {
- return { ...state, prompt: payload };
- },
-
- // actions
- setReduxState(state, { payload }) {
- return { ...state, ...payload };
- },
-
-
- setFlashAcknowledged(state, { payload }) {
- return { ...state, flashAcknowledged: payload };
- },
-
- changeVideo(state, { payload }) {
- return {
- ...state,
- ...payload,
- time: 0,
- duration: 0,
- bufferedTime: 0,
- isFullscreen: false,
- hasASL: false,
- ctpPriEvent: CTP_LOADING,
- ctpSecEvent: CTP_LOADING,
- paused: true,
- isSwitched: false,
-
- transcriptions: [],
- currTrans: {},
- transcript: [],
- captions: [],
- currCaption: null,
- descriptions: [],
- currDescription: null,
- currEditing: null,
- bulkEditing: false,
-
- modal: MODAL_HIDE,
- liveMode: false,
- prompt: null,
- search: SEARCH_INIT,
- flashAcknowledged: false,
- };
- },
-
- // eslint-disable-next-line no-unused-vars
- resetStates(_state, { _unused }) {
- return { ...initState };
- },
- },
- effects: {
- *setupMedia(_unused, { call, put }) {
- // Get media
- yield put.resolve({ type: 'changeVideo', payload: { media: {} } })
- const { id } = uurl.useSearch();
- let media = null;
- try {
- const { data } = yield call(api.getMediaById, id);
- media = api.parseMedia(data);
- } catch (error) {
- if (api.parseError(error).status === 404) {
- yield put({ type: 'setError', payload: ERR_INVALID_MEDIA_ID });
- } else {
- yield put({ type: 'setError', payload: ERR_AUTH });
- }
- return null;
- }
- PlayerData.param = {};
- yield put({ type: 'setMedia', payload: media })
- yield put({ type: 'setMenu', payload: MENU_HIDE })
- // Set transcriptions
-
- const { transcriptions } = media;
- // console.log('-----');
- // console.log(`*setupMedia ${transcriptions.length} transcriptions`);
-
- // setTranscriptions
- yield put({ type: 'setTranscriptions', payload: transcriptions })
- // Get Playlist
- const { playlistId } = media;
- const playlist = yield call(setup.getPlaylist, playlistId);
- if (!playlist) {
- promptControl.error('playlist');
- api.contentLoaded();
- return;
- }
- // Set data
- yield put({ type: 'setPlaylist', payload: playlist })
-
- const { offeringId } = playlist;
- let { data: offering } = yield call(api.getOfferingById, offeringId);
- offering = api.parseSingleOffering(offering);
- yield put({ type: 'setOffering', payload: offering })
- // register the ids to the user event controller
- uEvent.registerIds(media.id, offeringId);
- // send select video event
- uEvent.selectvideo(media.id);
-
- api.contentLoaded();
-
- // Get playlists
- const playlists = yield call(setup.getPlaylists, offeringId);
- if (playlists) {
- yield put({ type: 'setPlaylists', payload: playlists })
- }
- if (isSafari && isIPad13 && isIPhone13) {
- promptControl.videoNotLoading();
- }
- try {
- let { data } = yield call(api.getUserWatchHistories)
- yield put({ type: 'setWatchHistories', payload: data.filter(media_ => media_?.id) })
- } catch (error) {
- prompt.addOne({ text: "Couldn't load watch histories.", status: 'error' });
- }
- },
- *setupEmbeddedMedia({ payload }, { call, put }) {
- const { mediaId, ...props } = payload;
- let media = payload.media;
- if (!media) {
- if (mediaId) {
- try {
- const { data } = yield call(api.getMediaById, mediaId);
- media = api.parseMedia(data);
- } catch (error) {
- if (api.parseError(error).status === 404) {
- yield put({ type: 'setError', payload: ERR_INVALID_MEDIA_ID });
- } else {
- yield put({ type: 'setError', payload: ERR_AUTH });
- }
- return false;
- }
- } else {
- return false;
- }
- }
- const transcriptions = media.transcriptions;
- delete props.media
- // delete media.transcriptions;
- yield put({ type: 'setEmbeddedMedia', payload: { media, ...props } })
- yield put({ type: 'setTranscriptions', payload: transcriptions })
- },
- ...player_effects,
- ...menu_effects,
- ...trans_effects,
- ...search_effects
- },
- subscriptions: {
- setup({ dispatch, history }) {
- if (!isMobile) {
- // document.removeEventListener('fullscreenchange', this.onFullScreenChange, true);
- document.addEventListener('fullscreenchange', (e) => {
- dispatch({ type: 'onFullScreenChange', payload: e })
- }, true);
- if (isMobile) {
- window.addEventListener('orientationchange', () => {
- //
- if ([90, -90].includes(window.orientation)) {
- /* NOT IMPLEMENTED
- if (that.currTime() > 0) {
- that.enterFullScreen();
- }
- */
- }
- });
- } else {
- window.addEventListener('resize', () => {
- if (window.innerWidth < 900) {
- /* NOT IMPLEMENTED
- if (that.SCREEN_MODE === PS_MODE) {
- this.dispatch({ type: 'watch/setWatchMode', payload: { mode: NESTED_MODE, config: { sendUserAction: false } } });
- }
- */
- }
- });
- }
- }
- history.listen((event) => {
- if (event.pathname === '/video' || event.action === 'PUSH' && event.location.pathname === '/video') {
- dispatch({ type: 'setupMedia' });
- }
- })
- }
- }
-}
+/* eslint-disable no-console */
+import { isSafari, isIPad13, isIPhone13, isMobile } from 'react-device-detect';
+import { api, prompt, uurl } from 'utils';
+import _ from 'lodash';
+import { ARRAY_INIT, DEFAULT_ROLE } from 'utils/constants';
+import { timeStrToSec } from './Utils/helpers';
+import PlayerData from './player'
+import {
+ WEBVTT_SUBTITLES,
+ SEARCH_HIDE,
+ WEBVTT_DESCRIPTIONS,
+ ARRAY_EMPTY,
+ // PROFANITY_LIST,
+} from './Utils/constants.util';
+
+import { uEvent } from './Utils/UserEventController';
+import { promptControl } from './Utils/prompt.control';
+import setup from './model/setup'
+import player_effects from './model/player_effects'
+import menu_effects from './model/menu_effects'
+import trans_effects from './model/trans_effects'
+import search_effects from './model/search_effects'
+import {
+ // constants
+ MENU_HIDE,
+ NORMAL_MODE,
+ SEARCH_INIT,
+ MODAL_HIDE,
+ CTP_LOADING,
+ CTP_PLAYING,
+ ERR_INVALID_MEDIA_ID,
+ ERR_AUTH
+ // MODAL_SHARE
+} from './Utils';
+
+
+const initState = {
+ // Basics
+ userRole: DEFAULT_ROLE,
+
+ error: null,
+
+ // Metadata
+ media: {
+ id: '',
+ mediaName: '',
+ createdAt: '',
+ isTwoScreen: false,
+ hasASL: false,
+ videos: [],
+ transcriptions: [],
+ isUnavailable: false,
+ flashDetected: false
+ },
+ flashAcknowledged: false,
+ playlist: {},
+ playlists: [],
+ offering: {},
+ watchHistory: [],
+ starredOfferings: [],
+
+ // VideoInfo
+ time: 0,
+ duration: 0,
+ bufferedTime: 0,
+ isSwitched: false,
+ paused: true,
+
+ isFullscreen: false,
+ isFullscreenTwo: false,
+ ctpPriEvent: CTP_LOADING,
+ ctpSecEvent: CTP_LOADING,
+
+ // Trans
+ transcriptions: [],
+ currTrans: {},
+ trackerMap: new Map(),
+ transcript: [],
+ captions: [],
+ currCaption: null,
+ descriptions: [],
+ currDescription: null,
+ currEditing: null,
+ bulkEditing: false,
+ updating: false,
+ currCaptionIndex: 0,
+ captionSpeedUp: 0,
+ offSet: 0,
+ sliderOffSet: 0,
+ fontSize: 'normal',
+ eventListener: undefined,
+
+ // screen options
+ mode: NORMAL_MODE,
+
+ menu: MENU_HIDE,
+ modal: MODAL_HIDE,
+ liveMode: false,
+ englishTrack: undefined,
+ currentAudioTrack: 0,
+ audioTracks: undefined,
+
+ // Others
+ prompt: null,
+ search: SEARCH_INIT,
+ mouseOnCaption: false,
+ embedded: false,
+ textTracks: [],
+}
+/**
+* Function used to union two caption arrays
+* Merging is based on the { begin, end } of each entry in the arrays
+*/
+// const unionTranscript = (captions, source) => {
+// let union = _.concat(
+// captions === ARRAY_EMPTY ? [] : captions,
+// source === ARRAY_EMPTY ? [] : source,
+// );
+// //
+// union = _.sortBy(union, (item) => timeStrToSec(item.begin));
+// union = _.map(union, (item, index) => ({ ...item, index }));
+// return union;
+// }
+
+const WatchModel = {
+ namespace: 'watch',
+ state: { ...initState },
+ reducers: {
+ // Metadata
+ setError(state, { payload }) {
+ return { ...state, error: payload };
+ },
+
+ setMedia(state, { payload }) {
+ return { ...state, media: payload, embedded: false, liveMode: payload.isLive ? 1 : 0 };
+ },
+ setEmbeddedMedia(state, { payload: { media, ...embeded_payload } }) {
+ return {
+ ...state, media,
+ embedded: embeded_payload,
+ liveMode: media.isLive ? 1 : 0
+ };
+ },
+ setLiveMode(state, { payload }) {
+ return { ...state, liveMode: payload };
+ },
+ setTextTracks(state, { payload }) {
+ return { ...state, textTracks: payload };
+ },
+
+ setAudioTracks(state, { payload }) {
+ return { ...state, audioTracks: payload };
+ },
+ setCurrCaptionIndex(state, { payload }) {
+ return { ...state, currCaptionIndex: payload };
+ },
+ setPlaylist(state, { payload }) {
+ return { ...state, playlist: payload };
+ },
+ setPlaylists(state, { payload }) {
+ return { ...state, playlists: payload };
+ },
+ setOffering(state, { payload }) {
+ return { ...state, offering: payload };
+ },
+ setEventListener(state, { payload }) {
+ return { ...state, setEventListener: payload };
+ },
+ setWatchHistory(state, { payload }) {
+ return { ...state, watchHistory: payload };
+ },
+ setOffSet(state, { payload }) {
+ return { ...state, offSet: payload };
+ },
+
+ setCaptionSpeedUp(state, { payload }) {
+ return { ...state, captionSpeedUp: payload };
+ },
+ setStarredOfferings(state, { payload }) {
+ return { ...state, starredOfferings: payload };
+ },
+ setEnglishTrack(state, { payload }) {
+ if(state.englishTrack !== undefined) {
+ // state.englishTrack.mode = 'hidden';
+ // state.englishTrack.removeEventListener('cuechange', state.eventListener);
+
+ }
+ let currTrack = document.getElementsByTagName('video')[0].textTracks;
+ return { ...state, englishTrack: currTrack[payload], transcript: []};
+ },
+
+ setFullscreen(state, { payload }) {
+ return { ...state, isFullscreen: payload };
+ },
+ setFullscreenTwo(state, { payload }) {
+ return { ...state, isFullscreenTwo: payload };
+ },
+ // Transcription
+ setTranscriptions(state, { payload }) {
+ return { ...state, transcriptions: payload };
+ },
+ // setCurrTrans(state, { payload }) {
+ // return { ...state, currTrans: payload };
+ // },
+ setCurrentTranscriptionMulti(state, { payload }) {
+ const { transKey, active } = payload;
+ let { currentTranscriptionMulti = {transKeysSelected:[] } } = state;
+ let newKeys = currentTranscriptionMulti.transKeysSelected.filter(i => (i !== transKey))
+ if( active ) {
+ newKeys.push(transKey)
+ }
+ return { ...state, currentTranscriptionMulti: {transKeysSelected: newKeys} };
+ },
+ setUpdating(state, { payload }) {
+ //
+ return { ...state, updating: payload };
+ },
+
+ // Test live caption font size change
+ setFontSize(state, { payload }) {
+ return { ...state, fontSize: payload };
+ },
+
+ // setup the transcript UI model; the caption and description data must already be loaded
+ // eslint-disable-next-line no-unused-vars
+ setTranscript(state, _unused) {
+ // Todo check that the payload is immutable because we use the subobjects in our immutable model
+ // console.log("setTranscript")
+ let all = [... state.captions,...state.descriptions]
+
+ let transcript = all;
+ // Using String sort so numbers (1.123 21.0) must be right aligned with same number of decimal places
+ // Put Closed Captions after Descriptions
+ transcript = _.sortBy(transcript, (item) => `${timeStrToSec(item.begin).toFixed(2).padStart(10)}/${item.transcription.transcriptionType === 0?'Z':item.transcription.transcriptionType}`);
+ transcript= _.map(transcript, (item, index) => ({ ...item, index }));
+
+ if (transcript.length === 0) transcript = ARRAY_EMPTY;
+
+ return { ...state, transcript };
+ }
+ ,
+ /**
+ * Function called for setting captions array
+ */
+ setCaptions(state, { payload }) {
+ // console.log(`setCaptions ${payload.length}`)
+ let parsedCap = _.map(payload, (c) => ({ ...c, kind: WEBVTT_SUBTITLES }));
+ // if (parsedCap.length === 0) parsedCap = ARRAY_EMPTY;
+ return { ...state, captions: parsedCap };
+ },
+ setCurrCaption(state, { payload }) {
+ return {...state, currCaption: payload}
+ },
+ /**
+ * * Function called for get or set audio descriptions
+ *
+ */
+ setDescriptions(state, { payload }) {
+ const parsedDes = _.map(payload, (d) => ({ ...d, kind: WEBVTT_DESCRIPTIONS }));
+ return { ...state, descriptions: parsedDes };
+ },
+ setCurrDescription(state, { payload }) {
+ return { ...state, currDescription: payload };
+ },
+ setCurrEditing(state, { payload }) {
+ return { ...state, currEditing: payload };
+ },
+ setBulkEditing(state, { payload }) {
+ return { ...state, bulkEditing: payload };
+ },
+
+ // Settings
+ setMode(state, { payload }) {
+ return { ...state, mode: payload, prevmode: state.mode };
+ },
+ setMenu(state, { payload }) {
+ return { ...state, menu: payload };
+ },
+ setModal(state, { payload }) {
+ return { ...state, modal: payload };
+ },
+
+ setTime(state, { payload }) {
+ let liveMode = state.liveMode
+ if(state.liveMode === 1) {
+ liveMode = payload < state.duration - 60 ? 2 : 1
+ }
+
+ return { ...state, time: payload, liveMode };
+ },
+ setBufferedTime(state, { payload }) {
+ return { ...state, bufferedTime: payload };
+ },
+ setDuration(state, { payload }) {
+ return { ...state, duration: payload };
+ },
+ switchScreen(state, { payload }) {
+ return { ...state, isSwitched: payload };
+ },
+ setMouseOnCaption(state, { payload }) {
+ return { ...state, mouseOnCaption: payload };
+ },
+ setPause(state, { payload }) {
+ return { ...state, paused: payload };
+ },
+ setCTPEvent(state, { payload: { event = CTP_PLAYING, priVideo = true } }) {
+ if (priVideo) {
+ return { ...state, ctpPriEvent: event };
+ }
+ return { ...state, ctpSecEvent: event };
+ },
+ // Others
+ setSearch(state, { payload }) {
+ return { ...state, search: { ...state.search, ...payload } };
+ },
+ resetSearch(state, { payload: status = SEARCH_HIDE }) {
+ return {
+ ...state, search: {
+ status,
+ value: '',
+ inVideoTransResults: ARRAY_INIT,
+ inCourseTransResults: ARRAY_INIT,
+ playlistResults: ARRAY_INIT,
+ }
+ }
+ },
+ setPrompt(state, { payload }) {
+ return { ...state, prompt: payload };
+ },
+
+ // actions
+ setReduxState(state, { payload }) {
+ return { ...state, ...payload };
+ },
+
+
+ setFlashAcknowledged(state, { payload }) {
+ return { ...state, flashAcknowledged: payload };
+ },
+
+ changeVideo(state, { payload }) {
+ return {
+ ...state,
+ ...payload,
+ time: 0,
+ duration: 0,
+ bufferedTime: 0,
+ isFullscreen: false,
+ hasASL: false,
+ ctpPriEvent: CTP_LOADING,
+ ctpSecEvent: CTP_LOADING,
+ paused: true,
+ isSwitched: false,
+
+ transcriptions: [],
+ currTrans: {},
+ transcript: [],
+ captions: [],
+ currCaption: null,
+ descriptions: [],
+ currDescription: null,
+ currEditing: null,
+ bulkEditing: false,
+
+ modal: MODAL_HIDE,
+ liveMode: false,
+ prompt: null,
+ search: SEARCH_INIT,
+ flashAcknowledged: false,
+ };
+ },
+
+ // eslint-disable-next-line no-unused-vars
+ resetStates(_state, { _unused }) {
+ return { ...initState };
+ },
+ },
+ effects: {
+ *setupMedia(_unused, { call, put }) {
+ // Get media
+ yield put.resolve({ type: 'changeVideo', payload: { media: {} } })
+ const { id } = uurl.useSearch();
+ let media = null;
+ try {
+ const { data } = yield call(api.getMediaById, id);
+ media = api.parseMedia(data);
+ } catch (error) {
+ if (api.parseError(error).status === 404) {
+ yield put({ type: 'setError', payload: ERR_INVALID_MEDIA_ID });
+ } else {
+ yield put({ type: 'setError', payload: ERR_AUTH });
+ }
+ return null;
+ }
+ PlayerData.param = {};
+ yield put({ type: 'setMedia', payload: media })
+ yield put({ type: 'setMenu', payload: MENU_HIDE })
+ // Set transcriptions
+
+ const { transcriptions } = media;
+ // console.log('-----');
+ // console.log(`*setupMedia ${transcriptions.length} transcriptions`);
+
+ // setTranscriptions
+ yield put({ type: 'setTranscriptions', payload: transcriptions })
+ // Get Playlist
+ const { playlistId } = media;
+ const playlist = yield call(setup.getPlaylist, playlistId);
+ if (!playlist) {
+ promptControl.error('playlist');
+ api.contentLoaded();
+ return;
+ }
+ // Set data
+ yield put({ type: 'setPlaylist', payload: playlist })
+
+ const { offeringId } = playlist;
+ let { data: offering } = yield call(api.getOfferingById, offeringId);
+ offering = api.parseSingleOffering(offering);
+ yield put({ type: 'setOffering', payload: offering })
+ // register the ids to the user event controller
+ uEvent.registerIds(media.id, offeringId);
+ // send select video event
+ uEvent.selectvideo(media.id);
+
+ api.contentLoaded();
+
+ // Get playlists
+ const playlists = yield call(setup.getPlaylists, offeringId);
+ if (playlists) {
+ yield put({ type: 'setPlaylists', payload: playlists })
+ }
+ if (isSafari && isIPad13 && isIPhone13) {
+ promptControl.videoNotLoading();
+ }
+ try {
+ let { data } = yield call(api.getUserWatchHistories)
+ yield put({ type: 'setWatchHistories', payload: data.filter(media_ => media_?.id) })
+ } catch (error) {
+ prompt.addOne({ text: "Couldn't load watch histories.", status: 'error' });
+ }
+ },
+ *setupEmbeddedMedia({ payload }, { call, put }) {
+ const { mediaId, ...props } = payload;
+ let media = payload.media;
+ if (!media) {
+ if (mediaId) {
+ try {
+ const { data } = yield call(api.getMediaById, mediaId);
+ media = api.parseMedia(data);
+ } catch (error) {
+ if (api.parseError(error).status === 404) {
+ yield put({ type: 'setError', payload: ERR_INVALID_MEDIA_ID });
+ } else {
+ yield put({ type: 'setError', payload: ERR_AUTH });
+ }
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+ const transcriptions = media.transcriptions;
+ delete props.media
+ // delete media.transcriptions;
+ yield put({ type: 'setEmbeddedMedia', payload: { media, ...props } })
+ yield put({ type: 'setTranscriptions', payload: transcriptions })
+ },
+ ...player_effects,
+ ...menu_effects,
+ ...trans_effects,
+ ...search_effects
+ },
+ subscriptions: {
+ setup({ dispatch, history }) {
+ if (!isMobile) {
+ // document.removeEventListener('fullscreenchange', this.onFullScreenChange, true);
+ document.addEventListener('fullscreenchange', (e) => {
+ dispatch({ type: 'onFullScreenChange', payload: e })
+ }, true);
+ if (isMobile) {
+ window.addEventListener('orientationchange', () => {
+ //
+ if ([90, -90].includes(window.orientation)) {
+ /* NOT IMPLEMENTED
+ if (that.currTime() > 0) {
+ that.enterFullScreen();
+ }
+ */
+ }
+ });
+ } else {
+ window.addEventListener('resize', () => {
+ if (window.innerWidth < 900) {
+ /* NOT IMPLEMENTED
+ if (that.SCREEN_MODE === PS_MODE) {
+ this.dispatch({ type: 'watch/setWatchMode', payload: { mode: NESTED_MODE, config: { sendUserAction: false } } });
+ }
+ */
+ }
+ });
+ }
+ }
+ history.listen((event) => {
+ if (event.pathname === '/video' || event.action === 'PUSH' && event.location.pathname === '/video') {
+ dispatch({ type: 'setupMedia' });
+ }
+ })
+ }
+ }
+}
export default WatchModel
\ No newline at end of file
diff --git a/src/screens/Watch/model/trans_effects.js b/src/screens/Watch/model/trans_effects.js
index ce29e79a..571fbb2e 100644
--- a/src/screens/Watch/model/trans_effects.js
+++ b/src/screens/Watch/model/trans_effects.js
@@ -1,324 +1,324 @@
-/* eslint-disable no-console */
-/* eslint-disable complexity */
-
-import { api } from 'utils';
-import _ from 'lodash';
-// import { isMobile } from 'react-device-detect';
-import { /* CROWDEDIT_ALLOW, */CROWDEDIT_FREEZE_ALL } from 'utils/constants.js';
-import { promptControl } from '../Utils/prompt.control';
-import { timeStrToSec } from '../Utils/helpers';
-
-import { uEvent } from '../Utils/UserEventController';
-import { scrollTransToView, findTransByLanguages } from '../Utils'
-/**
- * * Find subtitle based on current time
-*/
-const findCurrent = (array = [], prev = {}, now = 0, deterFunc) => {
- if (!array || array.length === 0) return null;
- let next = prev;
- const isCurrent = (item) => {
- if (!item) return false; // Check the item type
- const end = timeStrToSec(item.end);
- const begin = timeStrToSec(item.begin);
- let deter = true;
- if (deterFunc) deter = deterFunc(item, prev);
- return begin <= now && now <= end && deter;
- };
-
- // if it's the first time to find captions
- if (!prev) {
- next = _.find(array, isCurrent) || null;
-
- // if looking for caption that is after the current one
- } else if (now > timeStrToSec(prev.begin)) {
- next = _.find(array, isCurrent, prev.index + 1) || prev;
-
- // if looking for caption that is prior to the current one
- } else if (now < timeStrToSec(prev.end)) {
- next = _.findLast(array, isCurrent, prev.index - 1) || prev;
- }
-
- return next;
-}
-
-const findCurrentDescription = (descriptions, currentTime) => {
- let closestDescription = null;
- let maxEndTime = -Infinity;
-
- for (const description of descriptions) {
- const endTime = timeStrToSec(description.end);
-
- // Check if the description ends before or at the current time
- if (endTime <= currentTime && endTime > maxEndTime) {
- maxEndTime = endTime;
- closestDescription = description;
- }
- }
-
- return closestDescription;
-};
-
-export default {
-
- // We have an array of transcript Ids to display, time to get the actual transcripts from the server
- *setCurrTrans({ payload: trans }, { all, call, put }) {
- // // console.log("Starting setCurrTrans with trans payload:", trans);
-
- // Ensure trans is an array
- if (!Array.isArray(trans)) {
- trans = [trans];
- }
- // // console.log("Normalized trans array:", trans);
-
- let alldata;
- if (trans.length > 0) {
- // // console.log("Fetching captions for each transcription ID...");
-
- // Fetch data for each transcription ID
- const allTranscriptionData = yield all(
- trans.map((tran) => call(api.getCaptionsByTranscriptionId, tran.id))
- );
- // // console.log("Fetched allTranscriptionData:", allTranscriptionData);
-
- // Attach transcription reference to each caption
- allTranscriptionData.forEach((captionList, listIndex) => {
- const t = trans[listIndex];
- captionList.data?.forEach((c) => {
- c.transcription = t;
- // // console.log(`Assigned transcription for caption (ID: ${c.id}):`, c.transcription);
- });
- });
-
- // Merge all caption data into alldata
- alldata = allTranscriptionData.reduce((acc, { data = [] }) => [...acc, ...data], []);
- // // console.log("Combined alldata:", alldata);
- }
-
- if (alldata === undefined) {
- alldata = [];
- }
-
- // Filter captions by transcription type
- let closedcaptions = alldata.filter((c) => c.transcription.transcriptionType === 0);
- let descriptions = alldata.filter((c) => c.transcription.transcriptionType !== 0);
- // // console.log("Filtered closedcaptions:", closedcaptions);
- // // console.log("Filtered descriptions:", descriptions);
-
- // Dispatch closed captions
- yield put({ type: 'setCaptions', payload: closedcaptions });
-
- // Dispatch descriptions
- const descriptionData = descriptions;
- // // console.log("Dispatching descriptionData:", descriptionData);
- yield put.resolve({ type: 'setDescriptions', payload: descriptionData });
-
- // Dispatch final transcript set
- yield put({ type: 'setTranscript' });
- // // console.log("Completed setCurrTrans");
- },
-
- *setTranscriptions({ payload: trans }, { put, select }) {
- const { playerpref } = yield select();
- let keys = playerpref.transKeys;
- if (keys === undefined) {
- // No preference, so look for 1 caption and 1 description...
- const seen = new Set();
- keys = [];
- for (const t of trans) {
- if (!seen.has(t.transcriptionType)) {
- keys.push(t.transKey);
- seen.add(t.transcriptionType);
- }
- }
- // if(keys.length === 0 && trans.length > 0) {
- yield put({
- type: 'playerpref/setPreference', payload: { transKeys: keys }
- });
- }
- for (const t of keys) {
- yield put({
- type: 'setCurrentTranscriptionMulti',
- payload: { transKey: t, active: true },
- });
- }
- },
- *updateTranscript({ payload: currentTime }, { put, select }) {
- const { watch, playerpref } = yield select();
- const prevCaption_ = watch.caption;
- // TODO: Fix: if watch.transcript is ARRAY_EMPTY (?) or length 0 then findCurrent will
- // return with the current transcript. Should put({ type: 'setCurrCaption', payload: null
-
- // TODO: is index reset in frontend? the findCurrent assumes index are increasing integers
- // Ans: Yes in model.js/setTranscript, after filtering wanted transcriptions -
- // "transcript= _.map(transcript, (item, index) => ({ ...item, index }));
-
-
- const next = findCurrent(watch.transcript, prevCaption_, currentTime);
- if (next && next.id) {
- // // console.log(next);
- // pause video if it's AD
-
- // determine whether should scroll smoothly
- const smoothScroll =
- prevCaption_ && next && Math.abs(prevCaption_.index - next.index) === 1;
- yield put({ type: 'setCurrCaption', payload: next });
-
- if (playerpref.autoScroll && !watch.mouseOnCaption && !watch.currEditing) {
- const { media = {} } = watch;
- scrollTransToView(next.id, smoothScroll, media.isTwoScreen);
- }
- } else {
- yield put({ type: 'setCurrCaption', payload: null });
- }
- // // console.log(watch)
- // // console.log(`pauseWhileAD:${playerpref.pauseWhileAD}`);
- const nextDescription = findCurrentDescription(watch.descriptions, currentTime);
- if (playerpref.openAD && nextDescription) {
- const nextDescriptionBeginTime = timeStrToSec(nextDescription.begin);
- if (Math.abs(currentTime - nextDescriptionBeginTime) <= 1) {
- if (playerpref.pauseWhileAD) {
- yield put({ type: 'media_pause' });
- }
- // Speak out loud
- // // console.log(`SPEAK ${nextDescription.text}`);
- yield put({ type: 'playerpref/setPreference', payload: { description: nextDescription.text } })
- }
- }
- return next || null;
- // transControl.updateTranscript(currentTime);
- },
- *setLanguage({ payload: language }, { put, select }) {
- const { watch } = yield select();
- const currTrans = findTransByLanguages(watch.transcriptions, [language]);
- if (currTrans) {
- yield put({ type: 'setCurrTrans', payload: currTrans });
- }
- uEvent.langchange(watch.time, language);
- uEvent.registerLanguage(language);
- },
- *setCurrentTranscriptionMulti(_ignore, { put, select }) {
- const { watch } = yield select();
-
- const selected = watch.currentTranscriptionMulti.transKeysSelected
-
- const currTrans = watch.transcriptions.filter((t) => selected.includes(t.transKey));
-
- // if (currTrans.length > 0) {
- yield put({ type: 'setCurrTrans', payload: currTrans });
- // }
- const currKeys = currTrans.map((t) => t.transKey);
- // remember preference for next time
- yield put({ type: 'playerpref/setPreference', payload: { transKeys: currKeys } });
- // TODO - fix uEvent
- // uEvent.langchange(watch.time, language);
- // uEvent.registerLanguage(language);
- },
-
- *setTransEditMode({ payload: { caption } }, { put, select }) {
- // if no param caption, edit current caption
- const { watch, playerpref } = yield select();
-
- const media = watch.media;
- const crowdEdit = media.crowdEditMode !== CROWDEDIT_FREEZE_ALL;
- if (!crowdEdit) {
- return;
- }
-
- const currCap = caption || watch.currCaption_;
- yield put({ type: 'setCurrEditing', payload: currCap });
- if (playerpref.pauseWhileEditing) {
- yield put({ type: 'media_pause' });
- }
- if (playerpref.showCaptionTips) {
- promptControl.editCaptionTips();
- yield put({ type: 'playerpref/setPreference', payload: { showCaptionTips: false } });
- }
- },
- *timestampFailed({ payload: { caption } }) {
- promptControl.timestampFailed(caption.transcription.transcriptionType === 0);
- yield;
- },
- // This is a transcript caption
- *saveCaption({ payload: { caption, text, begin, end } }, { call, put, select }) {
- const { watch } = yield select();
-
- // console.log("Entering saveCaption with payload:", { caption, text, begin, end });
- // console.log("Current watch state:", watch);
-
- /**
- * @todo check PROFANITY_LIST
- */
- /**
- * @todo check begin overlap or other timestamp checks
- */
-
- // currEditing could be missing if captions are frozen
- // if (!text || !watch?.currEditing || (watch.currEditing && watch.currEditing.text === text && watch.currEditing.begin === begin)) {
- // // console.log("Exiting saveCaption early. Conditions not met.");
- // promptControl.closePrompt();
- // return;
- // // return this.edit(null); NOT IMPLEMENTED
- // }
-
- if (!text) {
- // console.log("Exiting saveCaption early: 'text' is falsy.");
- promptControl.closePrompt();
- return;
- }
-
- if (!watch?.currEditing) {
- // console.log("Exiting saveCaption early: 'watch.currEditing' is falsy.");
- promptControl.closePrompt();
- return;
- }
-
- if (watch.currEditing && watch.currEditing.text === text && watch.currEditing.begin === begin && watch.currEditing.end === end) {
- // console.log("Exiting saveCaption early: No changes detected in 'currEditing'.");
- promptControl.closePrompt();
- return;
- }
-
- // console.log("Updating caption with text:", text);
- caption.text = text; // update data model
- caption.begin = begin;
- caption.end = end;
- promptControl.savingCaption(); // just a ui prompt, empty atm
-
- const { id } = watch.currEditing;
- // console.log("Sending user event with ID:", id, "Current time:", watch.currTime, "Old text:", watch.currEditing.text, "New text:", text);
-
- const isClosedCaption = caption.transcription.transcriptionType === 0;
- // console.log("Is closed caption:", isClosedCaption);
-
- yield put({ type: 'setCurrEditing', payload: null });
-
- try {
- // console.log("Calling API to update caption line with data:", { id, text, begin, end });
- yield call(api.updateCaptionLine, { id, text, begin, end });
-
- if (isClosedCaption) {
- // console.log("Updating closed captions in state.");
- yield put({ type: 'setCaptions', payload: watch.captions });
- } else {
- // console.log("Updating descriptions in state.");
- yield put({ type: 'setDescriptions', payload: watch.descriptions });
- }
- // another elif here for chapter breaks eventually
- promptControl.savedCaption(isClosedCaption, true);
- // console.log("Caption saved successfully.");
- } catch (error) {
- console.error("Error saving caption:", error);
- promptControl.savedCaption(isClosedCaption, false);
- }
- },
- *setFontSize({ payload: fontSize }, { put, select }) {
- const { watch } = yield select();
- if (fontSize == null) {
- yield put({ type: 'setFontSize', payload: "normal" });
- } else if (fontSize === watch.fontSize) {
- // very good it has changed so stop calling yourself
- } else {
- yield put({ type: 'setFontSize', payload: fontSize });
- }
- },
+/* eslint-disable no-console */
+/* eslint-disable complexity */
+
+import { api } from 'utils';
+import _ from 'lodash';
+// import { isMobile } from 'react-device-detect';
+import { /* CROWDEDIT_ALLOW, */CROWDEDIT_FREEZE_ALL } from 'utils/constants.js';
+import { promptControl } from '../Utils/prompt.control';
+import { timeStrToSec } from '../Utils/helpers';
+
+import { uEvent } from '../Utils/UserEventController';
+import { scrollTransToView, findTransByLanguages } from '../Utils'
+/**
+ * * Find subtitle based on current time
+*/
+const findCurrent = (array = [], prev = {}, now = 0, deterFunc) => {
+ if (!array || array.length === 0) return null;
+ let next = prev;
+ const isCurrent = (item) => {
+ if (!item) return false; // Check the item type
+ const end = timeStrToSec(item.end);
+ const begin = timeStrToSec(item.begin);
+ let deter = true;
+ if (deterFunc) deter = deterFunc(item, prev);
+ return begin <= now && now <= end && deter;
+ };
+
+ // if it's the first time to find captions
+ if (!prev) {
+ next = _.find(array, isCurrent) || null;
+
+ // if looking for caption that is after the current one
+ } else if (now > timeStrToSec(prev.begin)) {
+ next = _.find(array, isCurrent, prev.index + 1) || prev;
+
+ // if looking for caption that is prior to the current one
+ } else if (now < timeStrToSec(prev.end)) {
+ next = _.findLast(array, isCurrent, prev.index - 1) || prev;
+ }
+
+ return next;
+}
+
+const findCurrentDescription = (descriptions, currentTime) => {
+ let closestDescription = null;
+ let maxEndTime = -Infinity;
+
+ for (const description of descriptions) {
+ const endTime = timeStrToSec(description.end);
+
+ // Check if the description ends before or at the current time
+ if (endTime <= currentTime && endTime > maxEndTime) {
+ maxEndTime = endTime;
+ closestDescription = description;
+ }
+ }
+
+ return closestDescription;
+};
+
+export default {
+
+ // We have an array of transcript Ids to display, time to get the actual transcripts from the server
+ *setCurrTrans({ payload: trans }, { all, call, put }) {
+ // // console.log("Starting setCurrTrans with trans payload:", trans);
+
+ // Ensure trans is an array
+ if (!Array.isArray(trans)) {
+ trans = [trans];
+ }
+ // // console.log("Normalized trans array:", trans);
+
+ let alldata;
+ if (trans.length > 0) {
+ // // console.log("Fetching captions for each transcription ID...");
+
+ // Fetch data for each transcription ID
+ const allTranscriptionData = yield all(
+ trans.map((tran) => call(api.getCaptionsByTranscriptionId, tran.id))
+ );
+ // // console.log("Fetched allTranscriptionData:", allTranscriptionData);
+
+ // Attach transcription reference to each caption
+ allTranscriptionData.forEach((captionList, listIndex) => {
+ const t = trans[listIndex];
+ captionList.data?.forEach((c) => {
+ c.transcription = t;
+ // // console.log(`Assigned transcription for caption (ID: ${c.id}):`, c.transcription);
+ });
+ });
+
+ // Merge all caption data into alldata
+ alldata = allTranscriptionData.reduce((acc, { data = [] }) => [...acc, ...data], []);
+ // // console.log("Combined alldata:", alldata);
+ }
+
+ if (alldata === undefined) {
+ alldata = [];
+ }
+
+ // Filter captions by transcription type
+ let closedcaptions = alldata.filter((c) => c.transcription.transcriptionType === 0);
+ let descriptions = alldata.filter((c) => c.transcription.transcriptionType !== 0);
+ // // console.log("Filtered closedcaptions:", closedcaptions);
+ // // console.log("Filtered descriptions:", descriptions);
+
+ // Dispatch closed captions
+ yield put({ type: 'setCaptions', payload: closedcaptions });
+
+ // Dispatch descriptions
+ const descriptionData = descriptions;
+ // // console.log("Dispatching descriptionData:", descriptionData);
+ yield put.resolve({ type: 'setDescriptions', payload: descriptionData });
+
+ // Dispatch final transcript set
+ yield put({ type: 'setTranscript' });
+ // // console.log("Completed setCurrTrans");
+ },
+
+ *setTranscriptions({ payload: trans }, { put, select }) {
+ const { playerpref } = yield select();
+ let keys = playerpref.transKeys;
+ if (keys === undefined) {
+ // No preference, so look for 1 caption and 1 description...
+ const seen = new Set();
+ keys = [];
+ for (const t of trans) {
+ if (!seen.has(t.transcriptionType)) {
+ keys.push(t.transKey);
+ seen.add(t.transcriptionType);
+ }
+ }
+ // if(keys.length === 0 && trans.length > 0) {
+ yield put({
+ type: 'playerpref/setPreference', payload: { transKeys: keys }
+ });
+ }
+ for (const t of keys) {
+ yield put({
+ type: 'setCurrentTranscriptionMulti',
+ payload: { transKey: t, active: true },
+ });
+ }
+ },
+ *updateTranscript({ payload: currentTime }, { put, select }) {
+ const { watch, playerpref } = yield select();
+ const prevCaption_ = watch.caption;
+ // TODO: Fix: if watch.transcript is ARRAY_EMPTY (?) or length 0 then findCurrent will
+ // return with the current transcript. Should put({ type: 'setCurrCaption', payload: null
+
+ // TODO: is index reset in frontend? the findCurrent assumes index are increasing integers
+ // Ans: Yes in model.js/setTranscript, after filtering wanted transcriptions -
+ // "transcript= _.map(transcript, (item, index) => ({ ...item, index }));
+
+
+ const next = findCurrent(watch.transcript, prevCaption_, currentTime);
+ if (next && next.id) {
+ // // console.log(next);
+ // pause video if it's AD
+
+ // determine whether should scroll smoothly
+ const smoothScroll =
+ prevCaption_ && next && Math.abs(prevCaption_.index - next.index) === 1;
+ yield put({ type: 'setCurrCaption', payload: next });
+
+ if (playerpref.autoScroll && !watch.mouseOnCaption && !watch.currEditing) {
+ const { media = {} } = watch;
+ scrollTransToView(next.id, smoothScroll, media.isTwoScreen);
+ }
+ } else {
+ yield put({ type: 'setCurrCaption', payload: null });
+ }
+ // // console.log(watch)
+ // // console.log(`pauseWhileAD:${playerpref.pauseWhileAD}`);
+ const nextDescription = findCurrentDescription(watch.descriptions, currentTime);
+ if (playerpref.openAD && nextDescription) {
+ const nextDescriptionBeginTime = timeStrToSec(nextDescription.begin);
+ if (Math.abs(currentTime - nextDescriptionBeginTime) <= 1) {
+ if (playerpref.pauseWhileAD) {
+ yield put({ type: 'media_pause' });
+ }
+ // Speak out loud
+ // // console.log(`SPEAK ${nextDescription.text}`);
+ yield put({ type: 'playerpref/setPreference', payload: { description: nextDescription.text } })
+ }
+ }
+ return next || null;
+ // transControl.updateTranscript(currentTime);
+ },
+ *setLanguage({ payload: language }, { put, select }) {
+ const { watch } = yield select();
+ const currTrans = findTransByLanguages(watch.transcriptions, [language]);
+ if (currTrans) {
+ yield put({ type: 'setCurrTrans', payload: currTrans });
+ }
+ uEvent.langchange(watch.time, language);
+ uEvent.registerLanguage(language);
+ },
+ *setCurrentTranscriptionMulti(_ignore, { put, select }) {
+ const { watch } = yield select();
+
+ const selected = watch.currentTranscriptionMulti.transKeysSelected
+
+ const currTrans = watch.transcriptions.filter((t) => selected.includes(t.transKey));
+
+ // if (currTrans.length > 0) {
+ yield put({ type: 'setCurrTrans', payload: currTrans });
+ // }
+ const currKeys = currTrans.map((t) => t.transKey);
+ // remember preference for next time
+ yield put({ type: 'playerpref/setPreference', payload: { transKeys: currKeys } });
+ // TODO - fix uEvent
+ // uEvent.langchange(watch.time, language);
+ // uEvent.registerLanguage(language);
+ },
+
+ *setTransEditMode({ payload: { caption } }, { put, select }) {
+ // if no param caption, edit current caption
+ const { watch, playerpref } = yield select();
+
+ const media = watch.media;
+ const crowdEdit = media.crowdEditMode !== CROWDEDIT_FREEZE_ALL;
+ if (!crowdEdit) {
+ return;
+ }
+
+ const currCap = caption || watch.currCaption_;
+ yield put({ type: 'setCurrEditing', payload: currCap });
+ if (playerpref.pauseWhileEditing) {
+ yield put({ type: 'media_pause' });
+ }
+ if (playerpref.showCaptionTips) {
+ promptControl.editCaptionTips();
+ yield put({ type: 'playerpref/setPreference', payload: { showCaptionTips: false } });
+ }
+ },
+ *timestampFailed({ payload: { caption } }) {
+ promptControl.timestampFailed(caption.transcription.transcriptionType === 0);
+ yield;
+ },
+ // This is a transcript caption
+ *saveCaption({ payload: { caption, text, begin, end } }, { call, put, select }) {
+ const { watch } = yield select();
+
+ // console.log("Entering saveCaption with payload:", { caption, text, begin, end });
+ // console.log("Current watch state:", watch);
+
+ /**
+ * @todo check PROFANITY_LIST
+ */
+ /**
+ * @todo check begin overlap or other timestamp checks
+ */
+
+ // currEditing could be missing if captions are frozen
+ // if (!text || !watch?.currEditing || (watch.currEditing && watch.currEditing.text === text && watch.currEditing.begin === begin)) {
+ // // console.log("Exiting saveCaption early. Conditions not met.");
+ // promptControl.closePrompt();
+ // return;
+ // // return this.edit(null); NOT IMPLEMENTED
+ // }
+
+ if (!text) {
+ // console.log("Exiting saveCaption early: 'text' is falsy.");
+ promptControl.closePrompt();
+ return;
+ }
+
+ if (!watch?.currEditing) {
+ // console.log("Exiting saveCaption early: 'watch.currEditing' is falsy.");
+ promptControl.closePrompt();
+ return;
+ }
+
+ if (watch.currEditing && watch.currEditing.text === text && watch.currEditing.begin === begin && watch.currEditing.end === end) {
+ // console.log("Exiting saveCaption early: No changes detected in 'currEditing'.");
+ promptControl.closePrompt();
+ return;
+ }
+
+ // console.log("Updating caption with text:", text);
+ caption.text = text; // update data model
+ caption.begin = begin;
+ caption.end = end;
+ promptControl.savingCaption(); // just a ui prompt, empty atm
+
+ const { id } = watch.currEditing;
+ // console.log("Sending user event with ID:", id, "Current time:", watch.currTime, "Old text:", watch.currEditing.text, "New text:", text);
+
+ const isClosedCaption = caption.transcription.transcriptionType === 0;
+ // console.log("Is closed caption:", isClosedCaption);
+
+ yield put({ type: 'setCurrEditing', payload: null });
+
+ try {
+ // console.log("Calling API to update caption line with data:", { id, text, begin, end });
+ yield call(api.updateCaptionLine, { id, text, begin, end });
+
+ if (isClosedCaption) {
+ // console.log("Updating closed captions in state.");
+ yield put({ type: 'setCaptions', payload: watch.captions });
+ } else {
+ // console.log("Updating descriptions in state.");
+ yield put({ type: 'setDescriptions', payload: watch.descriptions });
+ }
+ // another elif here for chapter breaks eventually
+ promptControl.savedCaption(isClosedCaption, true);
+ // console.log("Caption saved successfully.");
+ } catch (error) {
+ console.error("Error saving caption:", error);
+ promptControl.savedCaption(isClosedCaption, false);
+ }
+ },
+ *setFontSize({ payload: fontSize }, { put, select }) {
+ const { watch } = yield select();
+ if (fontSize == null) {
+ yield put({ type: 'setFontSize', payload: "normal" });
+ } else if (fontSize === watch.fontSize) {
+ // very good it has changed so stop calling yourself
+ } else {
+ yield put({ type: 'setFontSize', payload: fontSize });
+ }
+ },
}
\ No newline at end of file
diff --git a/src/utils/cthttp/entities/Captions.js b/src/utils/cthttp/entities/Captions.js
index 6e4aab1b..7ef27a3b 100644
--- a/src/utils/cthttp/entities/Captions.js
+++ b/src/utils/cthttp/entities/Captions.js
@@ -1,87 +1,87 @@
-import { cthttp } from '../request';
-
-// const qs = require('qs')
-
-// ------------------------------------------------------------
-// Captions
-// ------------------------------------------------------------
-
-// GET
-
-export function getTranscriptionFile(transcriptionId,format) {
- // {vtt,srt,txt}
- return cthttp.get(`Captions/TranscriptionFile/${transcriptionId}/${format}`);
-}
-
-
-export function getCaptionsByTranscriptionId(transcriptionId) {
- return cthttp.get(`Captions/ByTranscription/${transcriptionId}`);
-}
-
-export function getCaptionLine(transcriptionId, index) {
- return cthttp.get('Captions', { params: { transcriptionId, index } });
-}
-
-export function searchCaptionInOffering(offeringId, query, filterLanguage = 'en-US') {
- return cthttp.get('Captions/SearchInOffering', { params: { offeringId, query, filterLanguage } });
-}
-
-// POST
-
-export function updateCaptionLine(data) {
- // eslint-disable-next-line no-console
- console.log("Preparing to update caption line with data:", data);
-
- // Check if all required fields are present
- if (!data.id || !data.text || !data.begin || !data.end) {
- // eslint-disable-next-line no-console
- console.error("Missing required data fields:", data);
- throw new Error("Required data fields are missing.");
- }
-
- return cthttp.post('Captions', {
- id: data.id,
- text: data.text,
- begin: data.begin,
- end: data.end
- }).then(response => {
- // eslint-disable-next-line no-console
- console.log("Caption line updated successfully:", response);
- return response;
- }).catch(error => {
- // eslint-disable-next-line no-console
- console.error("Error updating caption line:", error);
- throw error; // Re-throw to handle it upstream if necessary
- });
-}
-
-
-export function searchCaptions(transList, data) {
- return cthttp.post('CaptionsSearch',
- transList
- , { params: { query: data.text, page: data.page, pageSize: data.pageSize } });
-}
-
-// ------------------------------------------------------------
-// Vote
-// ------------------------------------------------------------
-
-export function captionUpVote(id) {
- // captionId
- return cthttp.post('Captions/UpVote', null, { params: { id } });
-}
-
-export function captionCancelUpVote(id) {
- // captionId
- return cthttp.post('Captions/CancelUpVote', null, { params: { id } });
-}
-
-export function captionDownVote(id) {
- // captionId
- return cthttp.post('Captions/DownVote', null, { params: { id } });
-}
-
-export function captionCancelDownVote(id) {
- // captionId
- return cthttp.post('Captions/CancelDownVote', null, { params: { id } });
-}
+import { cthttp } from '../request';
+
+// const qs = require('qs')
+
+// ------------------------------------------------------------
+// Captions
+// ------------------------------------------------------------
+
+// GET
+
+export function getTranscriptionFile(transcriptionId,format) {
+ // {vtt,srt,txt}
+ return cthttp.get(`Captions/TranscriptionFile/${transcriptionId}/${format}`);
+}
+
+
+export function getCaptionsByTranscriptionId(transcriptionId) {
+ return cthttp.get(`Captions/ByTranscription/${transcriptionId}`);
+}
+
+export function getCaptionLine(transcriptionId, index) {
+ return cthttp.get('Captions', { params: { transcriptionId, index } });
+}
+
+export function searchCaptionInOffering(offeringId, query, filterLanguage = 'en-US') {
+ return cthttp.get('Captions/SearchInOffering', { params: { offeringId, query, filterLanguage } });
+}
+
+// POST
+
+export function updateCaptionLine(data) {
+ // eslint-disable-next-line no-console
+ console.log("Preparing to update caption line with data:", data);
+
+ // Check if all required fields are present
+ if (!data.id || !data.text || !data.begin || !data.end) {
+ // eslint-disable-next-line no-console
+ console.error("Missing required data fields:", data);
+ throw new Error("Required data fields are missing.");
+ }
+
+ return cthttp.post('Captions', {
+ id: data.id,
+ text: data.text,
+ begin: data.begin,
+ end: data.end
+ }).then(response => {
+ // eslint-disable-next-line no-console
+ console.log("Caption line updated successfully:", response);
+ return response;
+ }).catch(error => {
+ // eslint-disable-next-line no-console
+ console.error("Error updating caption line:", error);
+ throw error; // Re-throw to handle it upstream if necessary
+ });
+}
+
+
+export function searchCaptions(transList, data) {
+ return cthttp.post('CaptionsSearch',
+ transList
+ , { params: { query: data.text, page: data.page, pageSize: data.pageSize } });
+}
+
+// ------------------------------------------------------------
+// Vote
+// ------------------------------------------------------------
+
+export function captionUpVote(id) {
+ // captionId
+ return cthttp.post('Captions/UpVote', null, { params: { id } });
+}
+
+export function captionCancelUpVote(id) {
+ // captionId
+ return cthttp.post('Captions/CancelUpVote', null, { params: { id } });
+}
+
+export function captionDownVote(id) {
+ // captionId
+ return cthttp.post('Captions/DownVote', null, { params: { id } });
+}
+
+export function captionCancelDownVote(id) {
+ // captionId
+ return cthttp.post('Captions/CancelDownVote', null, { params: { id } });
+}