From d13532ab66335d7ebba9c687746fc466ae5ec622 Mon Sep 17 00:00:00 2001 From: bobzel Date: Sun, 21 Mar 2021 00:39:14 -0400 Subject: upload screengrab video to server in mkv format ... can't yet play them. --- src/server/DashUploadUtils.ts | 2 +- src/server/SharedMediaTypes.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src/server') diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts index d9b38c014..a6f752168 100644 --- a/src/server/DashUploadUtils.ts +++ b/src/server/DashUploadUtils.ts @@ -62,7 +62,7 @@ export namespace DashUploadUtils { const category = types[0]; let format = `.${types[1]}`; - console.log(green(`Processing upload of file (${name}) with upload type (${type}) in category (${category}).`)); + console.log(green(`Processing upload of file (${name}) and form (${format}) with upload type (${type}) in category (${category}).`)); switch (category) { case "image": diff --git a/src/server/SharedMediaTypes.ts b/src/server/SharedMediaTypes.ts index f1fe582e5..0c823b8fc 100644 --- a/src/server/SharedMediaTypes.ts +++ b/src/server/SharedMediaTypes.ts @@ -8,7 +8,7 @@ export namespace AcceptableMedia { export const webps = [".webp"]; export const tiffs = [".tiff"]; export const imageFormats = [...pngs, ...jpgs, ...gifs, ...webps, ...tiffs]; - export const videoFormats = [".mov", ".mp4", ".quicktime"]; + export const videoFormats = [".mov", ".mp4", ".quicktime", ".x-matroska;codecs=avc1"]; export const applicationFormats = [".pdf"]; export const audioFormats = [".wav", ".mp3", ".mpeg", ".flac", ".au", ".aiff", ".m4a", ".webm"]; } -- cgit v1.2.3-70-g09d2 From 72631204e8ed6cfa230fd623eb02d5217efe3b04 Mon Sep 17 00:00:00 2001 From: bobzel Date: Sun, 21 Mar 2021 02:02:57 -0400 Subject: made it possible to capture screen recordings as videos. --- package-lock.json | 9 +++++++++ package.json | 1 + src/client/views/nodes/ScreenshotBox.tsx | 17 ++++++++--------- src/server/DashUploadUtils.ts | 7 +++++++ 4 files changed, 25 insertions(+), 9 deletions(-) (limited to 'src/server') diff --git a/package-lock.json b/package-lock.json index f74368dd3..c0206ef4c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6483,6 +6483,15 @@ "resolved": "https://registry.npmjs.org/flexlayout-react/-/flexlayout-react-0.3.11.tgz", "integrity": "sha512-V+rEfyYJBqNk9oBgotPoXg8rmom4/ji9Mvr6f7T8sIJs83RuyK1D3oOrya2ydZIUCP2T6BIvj7rFTmRRkzpU8w==" }, + "fluent-ffmpeg": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/fluent-ffmpeg/-/fluent-ffmpeg-2.1.2.tgz", + "integrity": "sha1-yVLeIkD4EuvaCqgAbXd27irPfXQ=", + "requires": { + "async": ">=0.2.9", + "which": "^1.1.1" + } + }, "flush-write-stream": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", diff --git a/package.json b/package.json index 5c456449d..9b782c057 100644 --- a/package.json +++ b/package.json @@ -164,6 +164,7 @@ "find-in-files": "^0.5.0", "fit-curve": "^0.1.7", "flexlayout-react": "^0.3.11", + "fluent-ffmpeg": "^2.1.2", "formidable": "^1.2.1", "function-plot": "^1.22.7", "golden-layout": "^1.5.9", diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx index 94d00f429..0a62a8de1 100644 --- a/src/client/views/nodes/ScreenshotBox.tsx +++ b/src/client/views/nodes/ScreenshotBox.tsx @@ -153,22 +153,21 @@ export class ScreenshotBox extends ViewBoxBaseComponent this._chunks.push(e.data); this._recorder.onstop = async (e: any) => { - const file = new File([this._chunks], `${this.rootDoc[Id]}.mkv`, { type: this._chunks[0].type, lastModified: Date.now() }); - const [{ result }] = await Networking.UploadFilesToServer(file); + const file = new File(this._chunks, `${this.rootDoc[Id]}.mkv`, { type: this._chunks[0].type, lastModified: Date.now() }); + const completeBlob = new Blob(this._chunks, { type: this._chunks[0].type }); + (completeBlob as any).lastModifiedDate = new Date(); + (completeBlob as any).name = `${this.rootDoc[Id]}.mkv`; + const [{ result }] = await Networking.UploadFilesToServer(file);//completeBlob as File); if (!(result instanceof Error)) { this.dataDoc.type = DocumentType.VID; this.layoutDoc.layout = VideoBox.LayoutString(this.fieldKey); - this.props.Document[this.props.fieldKey] = new VideoField(Utils.prepend(result.accessPaths.agnostic.client)); + this.dataDoc[this.props.fieldKey] = new VideoField(Utils.prepend(result.accessPaths.agnostic.client)); console.log(this.props.Document[this.props.fieldKey]); - } else alert("video conversion failed") + } else alert("video conversion failed"); }; this._recorder.start(); } else { diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts index a6f752168..5f329094d 100644 --- a/src/server/DashUploadUtils.ts +++ b/src/server/DashUploadUtils.ts @@ -16,6 +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 requestImageSize = require("../client/util/request-image-size"); export enum SizeSuffix { @@ -72,6 +73,12 @@ export namespace DashUploadUtils { } case "video": if (videoFormats.includes(format)) { + if (format.includes("x-matroska")) { + await new Promise(res => ffmpeg(file.path) + .videoCodec("copy") // this will copy the data instead or reencode it + .save(file.path.replace(".mkv", ".mp4")).on('end', () => res())); + file.path = file.path.replace(".mkv", ".mp4"); + } return MoveParsedFile(file, Directory.videos); } case "application": -- cgit v1.2.3-70-g09d2 From 217594bb340afa6e98449624e7c7b2175e77bff4 Mon Sep 17 00:00:00 2001 From: bobzel Date: Sun, 21 Mar 2021 10:23:57 -0400 Subject: from last. tweaks to uploading video screengrabs. --- src/client/views/nodes/ScreenshotBox.tsx | 1 - src/server/DashUploadUtils.ts | 15 ++++++++------- src/server/SharedMediaTypes.ts | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) (limited to 'src/server') diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx index 0a62a8de1..087a1be7a 100644 --- a/src/client/views/nodes/ScreenshotBox.tsx +++ b/src/client/views/nodes/ScreenshotBox.tsx @@ -166,7 +166,6 @@ export class ScreenshotBox extends ViewBoxBaseComponent ffmpeg(file.path) + .videoCodec("copy") // this will copy the data instead of reencode it + .save(file.path.replace(".mkv", ".mp4")).on('end', () => res())); + file.path = file.path.replace(".mkv", ".mp4"); + format = ".mp4"; + } if (videoFormats.includes(format)) { - if (format.includes("x-matroska")) { - await new Promise(res => ffmpeg(file.path) - .videoCodec("copy") // this will copy the data instead or reencode it - .save(file.path.replace(".mkv", ".mp4")).on('end', () => res())); - file.path = file.path.replace(".mkv", ".mp4"); - } return MoveParsedFile(file, Directory.videos); } case "application": diff --git a/src/server/SharedMediaTypes.ts b/src/server/SharedMediaTypes.ts index 0c823b8fc..fdc65188f 100644 --- a/src/server/SharedMediaTypes.ts +++ b/src/server/SharedMediaTypes.ts @@ -8,7 +8,7 @@ export namespace AcceptableMedia { export const webps = [".webp"]; export const tiffs = [".tiff"]; export const imageFormats = [...pngs, ...jpgs, ...gifs, ...webps, ...tiffs]; - export const videoFormats = [".mov", ".mp4", ".quicktime", ".x-matroska;codecs=avc1"]; + export const videoFormats = [".mov", ".mp4", ".quicktime", ".mkv", ".x-matroska;codecs=avc1"]; export const applicationFormats = [".pdf"]; export const audioFormats = [".wav", ".mp3", ".mpeg", ".flac", ".au", ".aiff", ".m4a", ".webm"]; } -- cgit v1.2.3-70-g09d2 From 36028677c61be7ab5937e864e881fa91a3b82d8c Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 26 Mar 2021 13:22:13 -0400 Subject: extracted AudioWaveform out of AudioBox in order to use in CollectionStackedTimelineView to share between audioBox and videoBox. --- src/client/util/DictationManager.ts | 2 +- src/client/util/DragManager.ts | 2 +- src/client/util/LinkManager.ts | 20 +++--- src/client/views/AudioWaveform.scss | 17 +++++ src/client/views/AudioWaveform.tsx | 61 +++++++++++++++++ src/client/views/DocumentDecorations.tsx | 2 +- src/client/views/SidebarAnnos.tsx | 2 +- .../collections/CollectionStackedTimeline.scss | 7 ++ .../collections/CollectionStackedTimeline.tsx | 80 +++++++++++++--------- src/client/views/nodes/AudioBox.scss | 18 ----- src/client/views/nodes/AudioBox.tsx | 41 +---------- src/client/views/nodes/DocumentView.tsx | 8 +-- src/client/views/nodes/ScreenshotBox.tsx | 2 +- src/client/views/nodes/VideoBox.tsx | 9 +-- .../views/nodes/formattedText/FormattedTextBox.tsx | 2 +- src/server/DashUploadUtils.ts | 5 +- 16 files changed, 165 insertions(+), 113 deletions(-) create mode 100644 src/client/views/AudioWaveform.scss create mode 100644 src/client/views/AudioWaveform.tsx (limited to 'src/server') 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) => { if (pendingListen instanceof Promise) return pendingListen.then(pl => innerListen(options)); return innerListen(options); - } + }; const innerListen = async (options?: Partial) => { 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) => { 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 { + 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([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( + 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
+ +
; + } +} \ 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 { sidebarStyleProvider = (doc: Opt, props: Opt, 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 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) :
+ + +
; + } + @computed get renderAudioWaveform() { + return !this.props.mediaPath ? (null) :
+ +
; + } 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 { this.props.playFrom(start, this.anchorEnd(d.anchor)); e.stopPropagation(); }} > ; })} {this.selectionContainer} -
- 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}> - -
+ {this.renderAudioWaveform} + {this.renderDictation}
; 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(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 this.createWaveformBuckets()); - return ; - } - - // 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( - 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
-
- {this.waveform} -
{this.renderTimeline}
{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 { 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 (
{!this.props.Document || !this.props.PanelWidth() ? (null) : (
[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
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 Not supported. - {!this.audiopath ? (null) : + {!this.audiopath || this.audiopath === field.url.href ? (null) :