diff options
-rw-r--r-- | src/client/util/DictationManager.ts | 2 | ||||
-rw-r--r-- | src/client/util/DragManager.ts | 2 | ||||
-rw-r--r-- | src/client/util/LinkManager.ts | 20 | ||||
-rw-r--r-- | src/client/views/AudioWaveform.scss | 17 | ||||
-rw-r--r-- | src/client/views/AudioWaveform.tsx | 61 | ||||
-rw-r--r-- | src/client/views/DocumentDecorations.tsx | 2 | ||||
-rw-r--r-- | src/client/views/SidebarAnnos.tsx | 2 | ||||
-rw-r--r-- | src/client/views/collections/CollectionStackedTimeline.scss | 7 | ||||
-rw-r--r-- | src/client/views/collections/CollectionStackedTimeline.tsx | 80 | ||||
-rw-r--r-- | src/client/views/nodes/AudioBox.scss | 18 | ||||
-rw-r--r-- | src/client/views/nodes/AudioBox.tsx | 41 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 8 | ||||
-rw-r--r-- | src/client/views/nodes/ScreenshotBox.tsx | 2 | ||||
-rw-r--r-- | src/client/views/nodes/VideoBox.tsx | 9 | ||||
-rw-r--r-- | src/client/views/nodes/formattedText/FormattedTextBox.tsx | 2 | ||||
-rw-r--r-- | src/server/DashUploadUtils.ts | 5 |
16 files changed, 165 insertions, 113 deletions
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index f00cdce1e..a93b2f573 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -89,7 +89,7 @@ export namespace DictationManager { export const listen = async (options?: Partial<ListeningOptions>) => { if (pendingListen instanceof Promise) return pendingListen.then(pl => innerListen(options)); return innerListen(options); - } + }; const innerListen = async (options?: Partial<ListeningOptions>) => { let results: string | undefined; diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 1809a77bf..38d0ecaa6 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -388,7 +388,7 @@ export namespace DragManager { if (dragElement !== ele) { const children = [Array.from(ele.children), Array.from(dragElement.children)]; while (children[0].length) { - const childs = [children[0].pop(), children[1].pop()] + const childs = [children[0].pop(), children[1].pop()]; if (childs[0]?.children) { children[0].push(...Array.from(childs[0].children)); children[1].push(...Array.from(childs[1]!.children)); diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index 159011516..3c3d5c3b8 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -33,7 +33,7 @@ export class LinkManager { constructor() { LinkManager._instance = this; setTimeout(() => { - LinkManager.userDocs = [Doc.LinkDBDoc().data as Doc, ...SharingManager.Instance.users.map(user => user.linkDatabase as Doc)]; + LinkManager.userDocs = [Doc.LinkDBDoc().data as Doc, ...SharingManager.Instance.users.map(user => user.linkDatabase)]; const addLinkToDoc = (link: Doc): any => { const a1 = link?.anchor1; const a2 = link?.anchor2; @@ -43,7 +43,7 @@ export class LinkManager { Doc.GetProto(a2)[DirectLinksSym].add(link); Doc.GetProto(link)[DirectLinksSym].add(link); } - } + }; const remLinkFromDoc = (link: Doc): any => { const a1 = link?.anchor1; const a2 = link?.anchor2; @@ -53,23 +53,23 @@ export class LinkManager { Doc.GetProto(a2)[DirectLinksSym].delete(link); Doc.GetProto(link)[DirectLinksSym].delete(link); } - } + }; const watchUserLinks = (userLinks: List<Doc>) => { const toRealField = (field: Field) => field instanceof ProxyField ? field.value() : field; // see List.ts. data structure is not a simple list of Docs, but a list of ProxyField/Fields observe(userLinks, change => { - switch (change.type) { + switch (change.type as any) { case "splice": (change as any).added.forEach((link: any) => addLinkToDoc(toRealField(link))); (change as any).removed.forEach((link: any) => remLinkFromDoc(toRealField(link))); break; - case "update": let oldValue = change.oldValue; + case "update": //let oldValue = change.oldValue; } }, true); - } + }; observe(LinkManager.userDocs, change => { - switch (change.type) { + switch (change.type as any) { case "splice": (change as any).added.forEach(watchUserLinks); break; - case "update": let oldValue = change.oldValue; + case "update": //let oldValue = change.oldValue; } }, true); }); @@ -82,7 +82,9 @@ export class LinkManager { public deleteAllLinksOnAnchor(anchor: Doc) { LinkManager.Instance.relatedLinker(anchor).forEach(linkDoc => LinkManager.Instance.deleteLink(linkDoc)); } public getAllRelatedLinks(anchor: Doc) { return this.relatedLinker(anchor); } // finds all links that contain the given anchor - public getAllDirectLinks(anchor: Doc): Doc[] { return Array.from(Doc.GetProto(anchor)[DirectLinksSym]); } // finds all links that contain the given anchor + public getAllDirectLinks(anchor: Doc): Doc[] { + return Array.from(Doc.GetProto(anchor)[DirectLinksSym]); + } // finds all links that contain the given anchor public getAllLinks(): Doc[] { return []; }//this.allLinks(); } // allLinks = computedFn(function allLinks(this: any): Doc[] { diff --git a/src/client/views/AudioWaveform.scss b/src/client/views/AudioWaveform.scss new file mode 100644 index 000000000..e20434a25 --- /dev/null +++ b/src/client/views/AudioWaveform.scss @@ -0,0 +1,17 @@ +.audioWaveform { + position: relative; + width: 100%; + height: 100%; + overflow: hidden; + z-index: -1000; + bottom: 0; + pointer-events: none; + div { + height: 100% !important; + width: 100% !important; + } + canvas { + height: 100% !important; + width: 100% !important; + } +} diff --git a/src/client/views/AudioWaveform.tsx b/src/client/views/AudioWaveform.tsx new file mode 100644 index 000000000..7ff9c1071 --- /dev/null +++ b/src/client/views/AudioWaveform.tsx @@ -0,0 +1,61 @@ +import React = require("react"); +import axios from "axios"; +import { action, computed } from "mobx"; +import { observer } from "mobx-react"; +import Waveform from "react-audio-waveform"; +import { Doc } from "../../fields/Doc"; +import { List } from "../../fields/List"; +import { listSpec } from "../../fields/Schema"; +import { Cast } from "../../fields/Types"; +import { numberRange } from "../../Utils"; +import "./AudioWaveform.scss"; + +export interface AudioWaveformProps { + duration: number; + mediaPath: string; + dataDoc: Doc; + PanelHeight: () => number; +} + +@observer +export class AudioWaveform extends React.Component<AudioWaveformProps> { + public static NUMBER_OF_BUCKETS = 100; + @computed get _waveHeight() { return Math.max(50, this.props.PanelHeight()); } + componentDidMount() { + const audioBuckets = Cast(this.props.dataDoc.audioBuckets, listSpec("number"), []); + if (!audioBuckets.length) { + this.props.dataDoc.audioBuckets = new List<number>([0, 0]); /// "lock" to prevent other views from computing the same data + setTimeout(this.createWaveformBuckets); + } + } + // decodes the audio file into peaks for generating the waveform + createWaveformBuckets = async () => { + axios({ url: this.props.mediaPath, responseType: "arraybuffer" }) + .then(response => { + const context = new window.AudioContext(); + context.decodeAudioData(response.data, + action(buffer => { + const decodedAudioData = buffer.getChannelData(0); + const bucketDataSize = Math.floor(decodedAudioData.length / AudioWaveform.NUMBER_OF_BUCKETS); + const brange = Array.from(Array(bucketDataSize)); + this.props.dataDoc.audioBuckets = new List<number>( + numberRange(AudioWaveform.NUMBER_OF_BUCKETS).map((i: number) => + brange.reduce((p, x, j) => Math.abs(Math.max(p, decodedAudioData[i * bucketDataSize + j])), 0) / 2)); + })); + }); + } + + render() { + const audioBuckets = Cast(this.props.dataDoc.audioBuckets, listSpec("number"), []); + return <div className="audioWaveform"> + <Waveform + color={"darkblue"} + height={this._waveHeight} + barWidth={0.1} + pos={this.props.duration} + duration={this.props.duration} + peaks={audioBuckets.length === AudioWaveform.NUMBER_OF_BUCKETS ? audioBuckets : undefined} + progressColor={"blue"} /> + </div>; + } +}
\ No newline at end of file diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index da50ba4d8..a92891ee5 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -94,7 +94,7 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b setupMoveUpEvents(this, e, e => this.onBackgroundMove(true, e), (e) => { }, action((e) => { !this._edtingTitle && (this._accumulatedTitle = this._titleControlString.startsWith("#") ? this.selectionTitle : this._titleControlString); this._edtingTitle = true; - this._keyinput.current && setTimeout(this._keyinput.current!.focus); + this._keyinput.current && setTimeout(this._keyinput.current.focus); })); } diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx index 87887483f..6b0b928b3 100644 --- a/src/client/views/SidebarAnnos.tsx +++ b/src/client/views/SidebarAnnos.tsx @@ -79,7 +79,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> { sidebarStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps | DocumentViewProps>, property: string) => { if (property === StyleProp.ShowTitle) return StrCast(this.props.layoutDoc["sidebar-childShowTitle"], "title"); - return this.props.styleProvider?.(doc, props, property) + return this.props.styleProvider?.(doc, props, property); } render() { const renderTag = (tag: string) => { diff --git a/src/client/views/collections/CollectionStackedTimeline.scss b/src/client/views/collections/CollectionStackedTimeline.scss index cc56831f3..2b9f3d782 100644 --- a/src/client/views/collections/CollectionStackedTimeline.scss +++ b/src/client/views/collections/CollectionStackedTimeline.scss @@ -58,4 +58,11 @@ left: 0; } } + + .collectionStackedTimeline-waveform { + position: absolute; + width: 100%; + top: 0; + left: 0; + } }
\ No newline at end of file diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 66b74277b..2191a3df5 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -1,26 +1,26 @@ import React = require("react"); -import { action, computed, IReactionDisposer, observable, runInAction, reaction } from "mobx"; +import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; -import { Doc, Opt, DocListCast } from "../../../fields/Doc"; +import { computedFn } from "mobx-utils"; +import { Doc, DocListCast } from "../../../fields/Doc"; import { Id } from "../../../fields/FieldSymbols"; import { List } from "../../../fields/List"; import { listSpec, makeInterface } from "../../../fields/Schema"; import { ComputedField, ScriptField } from "../../../fields/ScriptField"; import { Cast, NumCast } from "../../../fields/Types"; -import { emptyFunction, formatTime, OmitKeys, returnFalse, setupMoveUpEvents, StopEvent, returnOne } from "../../../Utils"; +import { emptyFunction, formatTime, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, StopEvent } from "../../../Utils"; import { Docs } from "../../documents/Documents"; +import { LinkManager } from "../../util/LinkManager"; import { Scripting } from "../../util/Scripting"; import { SelectionManager } from "../../util/SelectionManager"; +import { Transform } from "../../util/Transform"; import { undoBatch } from "../../util/UndoManager"; +import { AudioWaveform } from "../AudioWaveform"; import { CollectionSubView } from "../collections/CollectionSubView"; -import { DocumentView, DocAfterFocusFunc, DocFocusFunc, DocumentViewProps } from "../nodes/DocumentView"; +import { LightboxView } from "../LightboxView"; +import { DocAfterFocusFunc, DocFocusFunc, DocumentView, DocumentViewProps } from "../nodes/DocumentView"; import { LabelBox } from "../nodes/LabelBox"; import "./CollectionStackedTimeline.scss"; -import { Transform } from "../../util/Transform"; -import { LinkManager } from "../../util/LinkManager"; -import { computedFn } from "mobx-utils"; -import { LightboxView } from "../LightboxView"; -import { FormattedTextBox } from "../nodes/formattedText/FormattedTextBox"; type PanZoomDocument = makeInterface<[]>; const PanZoomDocument = makeInterface(); @@ -35,6 +35,7 @@ export type CollectionStackedTimelineProps = { isChildActive: () => boolean; startTag: string; endTag: string; + mediaPath: string; }; @observer @@ -232,11 +233,42 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument placed.push({ anchorStartTime: x1, anchorEndTime: x2, level }); return level; } + + dictationHeight = () => this.props.PanelHeight() / 3; + timelineContentHeight = () => this.props.PanelHeight() * 2 / 3; + @computed get renderDictation() { + const dictation = Cast(this.dataDoc[this.props.fieldKey.replace("annotations", "dictation")], Doc, null); + return !dictation ? (null) : <div style={{ position: "absolute", height: this.dictationHeight(), top: this.timelineContentHeight(), background: "tan" }}> + <DocumentView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit} + Document={dictation} + PanelHeight={this.dictationHeight} + isAnnotationOverlay={true} + select={emptyFunction} + active={returnFalse} + scaling={returnOne} + xMargin={25} + yMargin={10} + whenActiveChanged={emptyFunction} + removeDocument={returnFalse} + moveDocument={returnFalse} + addDocument={returnFalse} + CollectionView={undefined} + renderDepth={this.props.renderDepth + 1}> + </DocumentView> + </div>; + } + @computed get renderAudioWaveform() { + return !this.props.mediaPath ? (null) : <div style={{ position: "absolute", width: "100%", top: 0, left: 0 }}> + <AudioWaveform + duration={this.duration} + mediaPath={this.props.mediaPath} + dataDoc={this.dataDoc} + PanelHeight={this.timelineContentHeight} /> + </div>; + } currentTimecode = () => this.currentTime; render() { const timelineContentWidth = this.props.PanelWidth(); - const timelineContentHeight = this.props.PanelHeight() * 2 / 3; - const dictationHeight = this.props.PanelHeight() / 3; const overlaps: { anchorStartTime: number, anchorEndTime: number, level: number }[] = []; const drawAnchors = this.childDocs.map(anchor => ({ level: this.getLevel(anchor, overlaps), anchor })); const maxLevel = overlaps.reduce((m, o) => Math.max(m, o.level), 0) + 2; @@ -247,11 +279,11 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument const start = this.anchorStart(d.anchor); const end = this.anchorEnd(d.anchor, start + 10 / timelineContentWidth * this.duration); const left = start / this.duration * timelineContentWidth; - const top = d.level / maxLevel * timelineContentHeight; + const top = d.level / maxLevel * this.timelineContentHeight(); const timespan = end - start; return this.props.Document.hideAnchors ? (null) : <div className={"collectionStackedTimeline-marker-timeline"} key={d.anchor[Id]} - style={{ left, top, width: `${timespan / this.duration * timelineContentWidth}px`, height: `${timelineContentHeight / maxLevel}px` }} + style={{ left, top, width: `${timespan / this.duration * timelineContentWidth}px`, height: `${this.timelineContentHeight() / maxLevel}px` }} onClick={e => { this.props.playFrom(start, this.anchorEnd(d.anchor)); e.stopPropagation(); }} > <StackedTimelineAnchor {...this.props} mark={d.anchor} @@ -260,7 +292,7 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument left={left} top={top} width={timelineContentWidth * timespan / this.duration} - height={timelineContentHeight / maxLevel} + height={this.timelineContentHeight() / maxLevel} toTimeline={this.toTimeline} layoutDoc={this.layoutDoc} currentTimecode={this.currentTimecode} @@ -270,24 +302,8 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument </div>; })} {this.selectionContainer} - <div style={{ position: "absolute", height: dictationHeight, top: timelineContentHeight, background: "tan" }}> - <DocumentView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit} - Document={this.dataDoc[this.props.fieldKey.replace("annotations", "dictation")]} - PanelHeight={() => dictationHeight} - isAnnotationOverlay={true} - select={emptyFunction} - active={returnFalse} - scaling={returnOne} - xMargin={25} - yMargin={10} - whenActiveChanged={emptyFunction} - removeDocument={returnFalse} - moveDocument={returnFalse} - addDocument={returnFalse} - CollectionView={undefined} - renderDepth={this.props.renderDepth + 1}> - </DocumentView> - </div> + {this.renderAudioWaveform} + {this.renderDictation} <div className="collectionStackedTimeline-current" style={{ left: `${this.currentTime / this.duration * 100}%` }} /> </div>; diff --git a/src/client/views/nodes/AudioBox.scss b/src/client/views/nodes/AudioBox.scss index fc881ca25..3fcb024df 100644 --- a/src/client/views/nodes/AudioBox.scss +++ b/src/client/views/nodes/AudioBox.scss @@ -144,24 +144,6 @@ border-radius: 3px; z-index: 1000; overflow: hidden; - - .waveform { - position: relative; - width: 100%; - height: 100%; - overflow: hidden; - z-index: -1000; - bottom: 0; - pointer-events: none; - div { - height: 100% !important; - width: 100% !important; - } - canvas { - height: 100% !important; - width: 100% !important; - } - } } .audioBox-total-time, diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 2f7a6cfd8..06a27c22a 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -1,18 +1,15 @@ import React = require("react"); import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import axios from "axios"; import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; -import Waveform from "react-audio-waveform"; import { DateField } from "../../../fields/DateField"; import { Doc, DocListCast, Opt } from "../../../fields/Doc"; import { documentSchema } from "../../../fields/documentSchemas"; -import { List } from "../../../fields/List"; -import { createSchema, listSpec, makeInterface } from "../../../fields/Schema"; +import { makeInterface } from "../../../fields/Schema"; import { ComputedField } from "../../../fields/ScriptField"; import { Cast, NumCast } from "../../../fields/Types"; import { AudioField, nullAudio } from "../../../fields/URLField"; -import { emptyFunction, formatTime, numberRange, Utils } from "../../../Utils"; +import { emptyFunction, formatTime, Utils } from "../../../Utils"; import { DocUtils } from "../../documents/Documents"; import { Networking } from "../../Network"; import { CurrentUserUtils } from "../../util/CurrentUserUtils"; @@ -35,7 +32,6 @@ const AudioDocument = makeInterface(documentSchema); export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioDocument>(AudioDocument) { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(AudioBox, fieldKey); } public static Enabled = false; - public static NUMBER_OF_BUCKETS = 100; static playheadWidth = 30; // width of playhead static heightPercent = 80; // height of timeline in percent of height of audioBox. static Instance: AudioBox; @@ -295,35 +291,6 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD e.stopPropagation(); } - // returns the audio waveform - @computed get waveform() { - const audioBuckets = Cast(this.dataDoc.audioBuckets, listSpec("number"), []); - !audioBuckets.length && setTimeout(() => this.createWaveformBuckets()); - return <Waveform - color={"darkblue"} - height={this._waveHeight} - barWidth={0.1} - pos={this.duration} - duration={this.duration} - peaks={audioBuckets.length === AudioBox.NUMBER_OF_BUCKETS ? audioBuckets : undefined} - progressColor={"blue"} />; - } - - // decodes the audio file into peaks for generating the waveform - createWaveformBuckets = async () => { - axios({ url: this.path, responseType: "arraybuffer" }) - .then(response => (new (window.AudioContext)()).decodeAudioData(response.data, - action(buffer => { - const decodedAudioData = buffer.getChannelData(0); - const bucketDataSize = Math.floor(decodedAudioData.length / AudioBox.NUMBER_OF_BUCKETS); - const brange = Array.from(Array(bucketDataSize)); - this.dataDoc.audioBuckets = new List<number>( - numberRange(AudioBox.NUMBER_OF_BUCKETS).map(i => - brange.reduce((p, x, j) => Math.abs(Math.max(p, decodedAudioData[i * bucketDataSize + j])), 0) / 2)); - })) - ); - } - playing = () => this.mediaState === "playing"; playLink = (link: Doc) => { const stack = this._stackedTimeline.current; @@ -353,6 +320,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD @computed get renderTimeline() { return <CollectionStackedTimeline ref={this._stackedTimeline} {...this.props} fieldKey={this.annotationKey} + mediaPath={this.path} renderDepth={this.props.renderDepth + 1} startTag={"_timecodeToShow" /* audioStart */} endTag={"_timecodeToHide" /* audioEnd */} @@ -407,9 +375,6 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD <div className="audiobox-player" style={{ height: `${AudioBox.heightPercent}%` }} > <div className="audiobox-playhead" style={{ width: AudioBox.playheadWidth }} title={this.mediaState === "paused" ? "play" : "pause"} onClick={this.Play}> <FontAwesomeIcon style={{ width: "100%", position: "absolute", left: "0px", top: "5px", borderWidth: "thin", borderColor: "white" }} icon={this.mediaState === "paused" ? "play" : "pause"} size={"1x"} /></div> <div className="audiobox-timeline" style={{ top: 0, height: `100%`, left: AudioBox.playheadWidth, width: `calc(100% - ${AudioBox.playheadWidth}px)`, background: "white" }}> - <div className="waveform"> - {this.waveform} - </div> {this.renderTimeline} </div> {this.audio} diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index df769a407..8a2a755ac 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1102,16 +1102,16 @@ export class DocumentView extends React.Component<DocumentViewProps> { render() { TraceMobx(); - const xshift = this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined; - const yshift = this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined; + const xshift = () => (this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined); + const yshift = () => (this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined); return (<div className="contentFittingDocumentView"> {!this.props.Document || !this.props.PanelWidth() ? (null) : ( <div className="contentFittingDocumentView-previewDoc" ref={this.ContentRef} style={{ position: this.props.Document.isInkMask ? "absolute" : undefined, transform: `translate(${this.centeringX}px, ${this.centeringY}px)`, - width: xshift ?? `${100 * (this.props.PanelWidth() - this.Xshift * 2) / this.props.PanelWidth()}%`, - height: yshift ?? (this.fitWidth ? `${this.panelHeight}px` : + width: xshift() ?? `${100 * (this.props.PanelWidth() - this.Xshift * 2) / this.props.PanelWidth()}%`, + height: yshift() ?? (this.fitWidth ? `${this.panelHeight}px` : `${100 * this.effectiveNativeHeight / this.effectiveNativeWidth * this.props.PanelWidth() / this.props.PanelHeight()}%`), }}> <DocumentViewInternal {...this.props} diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx index 6c019d6f2..aba7be68d 100644 --- a/src/client/views/nodes/ScreenshotBox.tsx +++ b/src/client/views/nodes/ScreenshotBox.tsx @@ -146,7 +146,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps, S } contentFunc = () => [this.content]; videoPanelHeight = () => NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"], 1) / NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"], 1) * this.props.PanelWidth(); - formattedPanelHeight = () => Math.max(0, this.props.PanelHeight() - this.videoPanelHeight()) + formattedPanelHeight = () => Math.max(0, this.props.PanelHeight() - this.videoPanelHeight()); render() { TraceMobx(); return <div className="videoBox" onContextMenu={this.specificContextMenu} style={{ width: "100%", height: "100%" }} > diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 4e03589d6..da05b0c13 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -297,9 +297,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD // returns the path of the audio file @computed get audiopath() { - const field = Cast(this.props.Document[this.props.fieldKey + '-audio'], AudioField); - const path = (field instanceof AudioField) ? field.url.href : ""; - return path === nullAudio ? "" : path; + const field = Cast(this.props.Document[this.props.fieldKey + '-audio'], AudioField, null); + const vfield = Cast(this.dataDoc[this.fieldKey], VideoField, null); + return field?.url.href ?? vfield?.url.href ?? ""; } // ref for updating time _audioPlayer: HTMLAudioElement | null = null; @@ -322,7 +322,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD <source src={field.url.href} type="video/mp4" /> Not supported. </video> - {!this.audiopath ? (null) : + {!this.audiopath || this.audiopath === field.url.href ? (null) : <audio ref={this.setAudioRef} className={`audiobox-control${this.active() ? "-interactive" : ""}`}> <source src={this.audiopath} type="audio/mpeg" /> Not supported. @@ -510,6 +510,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD return <div className="videoBox-stackPanel" style={{ transition: this.transition, height: `${100 - this.heightPercent}%` }}> <CollectionStackedTimeline ref={this._stackedTimeline} {...this.props} fieldKey={this.annotationKey} + mediaPath={this.audiopath} renderDepth={this.props.renderDepth + 1} startTag={"_timecodeToShow" /* videoStart */} endTag={"_timecodeToHide" /* videoEnd */} diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index aaf3a938e..abfc63b40 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1153,7 +1153,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp setTimeout(func); } else docView.ComponentView?.playFrom?.(timecode, Cast(anchor.timecodeToHide, "number", null)); // bcz: would be nice to find the next audio tag in the doc and play until that - } + }; func(); } }); diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts index e2c0e7ac9..ff6b2381c 100644 --- a/src/server/DashUploadUtils.ts +++ b/src/server/DashUploadUtils.ts @@ -16,7 +16,7 @@ import { resolvedServerUrl } from "./server_Initialization"; import { AcceptableMedia, Upload } from './SharedMediaTypes'; import request = require('request-promise'); const parse = require('pdf-parse'); -var ffmpeg = require("fluent-ffmpeg"); +const ffmpeg = require("fluent-ffmpeg"); const requestImageSize = require("../client/util/request-image-size"); export enum SizeSuffix { @@ -75,7 +75,8 @@ export namespace DashUploadUtils { if (format.includes("x-matroska")) { await new Promise(res => ffmpeg(file.path) .videoCodec("copy") // this will copy the data instead of reencode it - .save(file.path.replace(".mkv", ".mp4")).on('end', () => res())); + .save(file.path.replace(".mkv", ".mp4")) + .on('end', res)); file.path = file.path.replace(".mkv", ".mp4"); format = ".mp4"; } |