diff options
-rw-r--r-- | package.json | 3 | ||||
-rw-r--r-- | src/client/documents/DocumentTypes.ts | 1 | ||||
-rw-r--r-- | src/client/documents/Documents.ts | 21 | ||||
-rw-r--r-- | src/client/views/MainView.tsx | 7 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentContentsView.tsx | 18 | ||||
-rw-r--r-- | src/client/views/webcam/DashWebCam.tsx | 400 | ||||
-rw-r--r-- | src/client/views/webcam/DashWebRTC.tsx | 138 | ||||
-rw-r--r-- | src/new_fields/URLField.ts | 2 | ||||
-rw-r--r-- | src/typings/index.d.ts | 3 |
9 files changed, 593 insertions, 0 deletions
diff --git a/package.json b/package.json index e516a6979..3db7e33c4 100644 --- a/package.json +++ b/package.json @@ -102,6 +102,7 @@ "@types/react-color": "^2.14.1", "@types/react-measure": "^2.0.4", "@types/react-table": "^6.7.22", + "@types/react-webcam": "^3.0.0", "@types/request": "^2.48.1", "@types/request-promise": "^4.1.42", "@types/sharp": "^0.22.2", @@ -206,6 +207,7 @@ "react-simple-dropdown": "^3.2.3", "react-split-pane": "^0.1.85", "react-table": "^6.9.2", + "react-webcam": "^3.0.1", "readline": "^1.3.0", "request": "^2.88.0", "request-promise": "^4.2.4", @@ -219,6 +221,7 @@ "typescript-collections": "^1.3.2", "url-loader": "^1.1.2", "uuid": "^3.3.2", + "webrtc-adapter": "^7.3.0", "words-to-numbers": "^1.5.1", "xoauth2": "^1.2.0", "youtube": "^0.1.0" diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts index e5d5885cd..be290862a 100644 --- a/src/client/documents/DocumentTypes.ts +++ b/src/client/documents/DocumentTypes.ts @@ -17,6 +17,7 @@ export enum DocumentType { TEMPLATE = "template", EXTENSION = "extension", YOUTUBE = "youtube", + WEBCAM = "webcam", DRAGBOX = "dragbox", PRES = "presentation", LINKFOLLOW = "linkfollow", diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 0d04d044e..3482033d5 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -20,8 +20,12 @@ import { AggregateFunction } from "../northstar/model/idea/idea"; import { MINIMIZED_ICON_SIZE } from "../views/globalCssVariables.scss"; import { IconBox } from "../views/nodes/IconBox"; import { OmitKeys, JSONUtils } from "../../Utils"; +<<<<<<< HEAD +import { ImageField, VideoField, AudioField, PdfField, WebField, YoutubeField, WebCamField } from "../../new_fields/URLField"; +======= import { Field, Doc, Opt, DocListCastAsync } from "../../new_fields/Doc"; import { ImageField, VideoField, AudioField, PdfField, WebField, YoutubeField } from "../../new_fields/URLField"; +>>>>>>> 69e4a936c4eb0cc2e35e4e7f3258aed1f72b8da7 import { HtmlField } from "../../new_fields/HtmlField"; import { List } from "../../new_fields/List"; import { Cast, NumCast } from "../../new_fields/Types"; @@ -45,7 +49,14 @@ import { ComputedField } from "../../new_fields/ScriptField"; import { ProxyField } from "../../new_fields/Proxy"; import { DocumentType } from "./DocumentTypes"; import { LinkFollowBox } from "../views/linking/LinkFollowBox"; +<<<<<<< HEAD +import { DashWebCam } from "../views/webcam/DashWebCam"; +import { DashWebRTC } from "../views/webcam/DashWebRTC"; +//import { PresBox } from "../views/nodes/PresBox"; +//import { PresField } from "../../new_fields/PresField"; +======= import { PresElementBox } from "../views/presentationview/PresElementBox"; +>>>>>>> 69e4a936c4eb0cc2e35e4e7f3258aed1f72b8da7 var requestImageSize = require('../util/request-image-size'); var path = require('path'); @@ -175,9 +186,15 @@ export namespace Docs { [DocumentType.LINKFOLLOW, { layout: { view: LinkFollowBox } }], +<<<<<<< HEAD + [DocumentType.WEBCAM, { + layout: { view: DashWebRTC } + }] +======= [DocumentType.PRESELEMENT, { layout: { view: PresElementBox } }], +>>>>>>> 69e4a936c4eb0cc2e35e4e7f3258aed1f72b8da7 ]); // All document prototypes are initialized with at least these values @@ -365,6 +382,10 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.YOUTUBE), new YoutubeField(new URL(url)), options); } + export function WebCamDocument(url: string, options: DocumentOptions = {}) { + return InstanceFromProto(Prototypes.get(DocumentType.WEBCAM), "", options); + } + export function AudioDocument(url: string, options: DocumentOptions = {}) { return InstanceFromProto(Prototypes.get(DocumentType.AUDIO), new AudioField(new URL(url)), options); } diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 35d527c91..22d8171c0 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -1,5 +1,9 @@ import { IconName, library } from '@fortawesome/fontawesome-svg-core'; +<<<<<<< HEAD +import { faLink, faArrowDown, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faClone, faCloudUploadAlt, faCommentAlt, faCut, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight, faMusic, faObjectGroup, faPause, faPenNib, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faUndoAlt, faTv, faVideo } from '@fortawesome/free-solid-svg-icons'; +======= import { faArrowDown, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faClone, faCloudUploadAlt, faCommentAlt, faCut, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight, faMusic, faObjectGroup, faPause, faPenNib, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faTv, faUndoAlt } from '@fortawesome/free-solid-svg-icons'; +>>>>>>> 69e4a936c4eb0cc2e35e4e7f3258aed1f72b8da7 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, configure, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; @@ -213,6 +217,7 @@ export class MainView extends React.Component { library.add(faArrowUp); library.add(faCloudUploadAlt); library.add(faBolt); + library.add(faVideo); this.initEventListeners(); this.initAuthenticationRouters(); } @@ -535,6 +540,7 @@ export class MainView extends React.Component { let addImportCollectionNode = action(() => Docs.Create.DirectoryImportDocument({ title: "Directory Import", width: 400, height: 400 })); // let youtubeurl = "https://www.youtube.com/embed/TqcApsGRzWw"; // let addYoutubeSearcher = action(() => Docs.Create.YoutubeDocument(youtubeurl, { width: 600, height: 600, title: "youtube search" })); + let addWebCam = action(() => Docs.Create.WebCamDocument("", {})); // let googlePhotosSearch = () => GooglePhotosClientUtils.CollectionFromSearch(Docs.Create.MasonryDocument, { included: [GooglePhotosClientUtils.ContentCategories.LANDSCAPES] }); @@ -546,6 +552,7 @@ export class MainView extends React.Component { [React.createRef<HTMLDivElement>(), "file", "Add Document Dragger", addDragboxNode], // [React.createRef<HTMLDivElement>(), "object-group", "Test Google Photos Search", googlePhotosSearch], [React.createRef<HTMLDivElement>(), "cloud-upload-alt", "Import Directory", addImportCollectionNode], //remove at some point in favor of addImportCollectionNode + [React.createRef<HTMLDivElement>(), "video", "Add WebCam", addWebCam] //[React.createRef<HTMLDivElement>(), "play", "Add Youtube Searcher", addYoutubeSearcher], ]; if (!ClientUtils.RELEASE) btns.unshift([React.createRef<HTMLDivElement>(), "cat", "Add Cat Image", addImageNode]); diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 75dd27f46..f1c10f2f2 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -30,6 +30,20 @@ import { PresElementBox } from "../presentationview/PresElementBox"; import { VideoBox } from "./VideoBox"; import { WebBox } from "./WebBox"; import React = require("react"); +<<<<<<< HEAD +import { FieldViewProps } from "./FieldView"; +import { Without, OmitKeys } from "../../../Utils"; +import { Cast, StrCast, NumCast } from "../../../new_fields/Types"; +import { List } from "../../../new_fields/List"; +import { Doc } from "../../../new_fields/Doc"; +import DirectoryImportBox from "../../util/Import & Export/DirectoryImportBox"; +import { ScriptField } from "../../../new_fields/ScriptField"; +import { fromPromise } from "mobx-utils"; +import { DashWebCam } from "../../views/webcam/DashWebCam"; +import { DashWebRTC } from "../webcam/DashWebRTC"; + +======= +>>>>>>> 69e4a936c4eb0cc2e35e4e7f3258aed1f72b8da7 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? type BindingProps = Without<FieldViewProps, 'fieldKey'>; @@ -101,7 +115,11 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & { if (!this.layout && this.props.layoutKey !== "overlayLayout") return (null); return <ObserverJsxParser blacklistedAttrs={[]} +<<<<<<< HEAD + components={{ FormattedTextBox, ImageBox, IconBox, DirectoryImportBox, DragBox, ButtonBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox, PresBox, YoutubeBox, LinkFollowBox, DashWebCam, DashWebRTC }} +======= components={{ FormattedTextBox, ImageBox, IconBox, DirectoryImportBox, DragBox, ButtonBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox, PresBox, YoutubeBox, LinkFollowBox, PresElementBox }} +>>>>>>> 69e4a936c4eb0cc2e35e4e7f3258aed1f72b8da7 bindings={this.CreateBindings()} jsx={this.finalLayout} showWarnings={true} diff --git a/src/client/views/webcam/DashWebCam.tsx b/src/client/views/webcam/DashWebCam.tsx new file mode 100644 index 000000000..175bbe595 --- /dev/null +++ b/src/client/views/webcam/DashWebCam.tsx @@ -0,0 +1,400 @@ +import React = require("react"); +import { observer } from "mobx-react"; +import { FieldViewProps, FieldView } from "../nodes/FieldView"; +import { observable, action } from "mobx"; +import { DocumentDecorations } from "../DocumentDecorations"; +import { InkingControl } from "../InkingControl"; +import { CollectionFreeFormDocumentViewProps } from "../nodes/CollectionFreeFormDocumentView"; +import "../../views/nodes/WebBox.scss"; + + +//https://github.com/mozmorris/react-webcam is the source code used for the bigger part of this implementation. It's only modified to fit our current system. + + +function hasGetUserMedia() { + return !!( + (navigator.mediaDevices && navigator.mediaDevices.getUserMedia)); +} + +interface WebcamProps { + audio: boolean; + audioConstraints?: MediaStreamConstraints["audio"]; + imageSmoothing: boolean; + minScreenshotHeight?: number; + minScreenshotWidth?: number; + onUserMedia: () => void; + onUserMediaError: (error: string) => void; + screenshotFormat: "image/webp" | "image/png" | "image/jpeg"; + screenshotQuality: number; + videoConstraints?: MediaStreamConstraints["video"]; +} + +interface WebcamState { + hasUserMedia: boolean; + src?: string; +} + +@observer +export class DashWebCam extends React.Component<CollectionFreeFormDocumentViewProps & FieldViewProps & WebcamProps & React.HTMLAttributes<HTMLVideoElement> & { + layoutKey: string, +}, WebcamState> { + static defaultProps = { + audio: true, + imageSmoothing: true, + onUserMedia: () => { }, + onUserMediaError: () => { }, + screenshotFormat: "image/webp", + screenshotQuality: 0.92 + }; + + private static mountedInstances: DashWebCam[] = []; + private static userMediaRequested = false; + private canvas: HTMLCanvasElement | undefined; + private ctx: CanvasRenderingContext2D | null = null; + private stream: MediaStream | undefined; + private video: HTMLVideoElement | null | undefined; + + // @observable private hasUserMedia: boolean | undefined; + // @observable private src: string | undefined; + + constructor(props: any) { + super(props); + this.state = { + hasUserMedia: false + }; + } + + componentDidMount() { + if (!hasGetUserMedia()) return; + + const { state } = this; + + DashWebCam.mountedInstances.push(this); + + if (!state.hasUserMedia && !DashWebCam.userMediaRequested) { + this.requestUserMedia(); + } + } + + componentDidUpdate(nextProps: WebcamProps) { + const { props } = this; + if ( + JSON.stringify(nextProps.audioConstraints) !== + JSON.stringify(props.audioConstraints) || + JSON.stringify(nextProps.videoConstraints) !== + JSON.stringify(props.videoConstraints) + ) { + this.requestUserMedia(); + } + } + + componentWillUnmount() { + const { state } = this; + const index = DashWebCam.mountedInstances.indexOf(this); + DashWebCam.mountedInstances.splice(index, 1); + + DashWebCam.userMediaRequested = false; + if (DashWebCam.mountedInstances.length === 0 && state.hasUserMedia) { + if (this.stream!.getVideoTracks && this.stream!.getAudioTracks) { + this.stream!.getVideoTracks().map(track => track.stop()); + this.stream!.getAudioTracks().map(track => track.stop()); + } else { + ((this.stream as unknown) as MediaStreamTrack).stop(); + } + + if (state.src) { + window.URL.revokeObjectURL(state.src); + } + } + } + + //These are for screenshot if wanted. + + // getScreenshot() { + // const { state, props } = this; + + // if (!state.hasUserMedia) return null; + + // const canvas = this.getCanvas(); + // return ( + // canvas && + // canvas.toDataURL(props.screenshotFormat, props.screenshotQuality) + // ); + // } + + // getCanvas() { + // const { state, props } = this; + + // if (!this.video) { + // return null; + // } + + // if (!state.hasUserMedia || !this.video.videoHeight) return null; + + // if (!this.ctx) { + // const canvas = document.createElement("canvas"); + // const aspectRatio = this.video.videoWidth / this.video.videoHeight; + + // let canvasWidth = props.minScreenshotWidth || this.video.clientWidth; + // let canvasHeight = canvasWidth / aspectRatio; + + // if ( + // props.minScreenshotHeight && + // canvasHeight < props.minScreenshotHeight + // ) { + // canvasHeight = props.minScreenshotHeight; + // canvasWidth = canvasHeight * aspectRatio; + // } + + // canvas.width = canvasWidth; + // canvas.height = canvasHeight; + + // this.canvas = canvas; + // this.ctx = canvas.getContext("2d"); + // } + + // const { ctx, canvas } = this; + + // if (ctx) { + // ctx.imageSmoothingEnabled = props.imageSmoothing; + // ctx.drawImage(this.video, 0, 0, canvas!.width, canvas!.height); + // } + + // return canvas; + // } + + requestUserMedia() { + const { props } = this; + + navigator.getUserMedia = + navigator.mediaDevices.getUserMedia; + + const sourceSelected = (audioConstraints: any, videoConstraints: any) => { + const constraints: MediaStreamConstraints = { + video: typeof videoConstraints !== "undefined" ? videoConstraints : true + }; + + if (props.audio) { + constraints.audio = + typeof audioConstraints !== "undefined" ? audioConstraints : true; + } + + navigator.mediaDevices + .getUserMedia(constraints) + .then(stream => { + DashWebCam.mountedInstances.forEach(instance => + instance.handleUserMedia(null, stream) + ); + }) + .catch(e => { + DashWebCam.mountedInstances.forEach(instance => + instance.handleUserMedia(e) + ); + }); + }; + + if ("mediaDevices" in navigator) { + sourceSelected(props.audioConstraints, props.videoConstraints); + } else { + const optionalSource = (id: any) => ({ optional: [{ sourceId: id }] }); + + const constraintToSourceId = (constraint: any) => { + const { deviceId } = constraint; + + if (typeof deviceId === "string") { + return deviceId; + } + + if (Array.isArray(deviceId) && deviceId.length > 0) { + return deviceId[0]; + } + + if (typeof deviceId === "object" && deviceId.ideal) { + return deviceId.ideal; + } + + return null; + }; + + // @ts-ignore: deprecated api + MediaStreamTrack.getSources(sources => { + let audioSource = null; + let videoSource = null; + + sources.forEach((source: { kind: string; id: any; }) => { + if (source.kind === "audio") { + audioSource = source.id; + } else if (source.kind === "video") { + videoSource = source.id; + } + }); + + const audioSourceId = constraintToSourceId(props.audioConstraints); + if (audioSourceId) { + audioSource = audioSourceId; + } + + const videoSourceId = constraintToSourceId(props.videoConstraints); + if (videoSourceId) { + videoSource = videoSourceId; + } + + sourceSelected( + optionalSource(audioSource), + optionalSource(videoSource) + ); + }); + } + + DashWebCam.userMediaRequested = true; + } + + handleUserMedia(err: string | null, stream?: MediaStream) { + const { props } = this; + + if (err || !stream) { + this.setState({ hasUserMedia: false }); + // action(() => this.hasUserMedia = false); + props.onUserMediaError(err!); + + return; + } + + this.stream = stream; + + console.log("Stream done: ", stream); + + try { + if (this.video) { + this.video.srcObject = stream; + console.log("Source object: ", stream); + + } + this.setState({ hasUserMedia: true }); + // action(() => this.hasUserMedia = true); + + } catch (error) { + this.setState({ + hasUserMedia: true, + src: window.URL.createObjectURL(stream) + }); + console.log("State src set: ", this.state.src); + + // action(() => this.hasUserMedia = true); + // action(() => this.src = window.URL.createObjectURL(stream)); + } + + props.onUserMedia(); + } + + _ignore = 0; + onPreWheel = (e: React.WheelEvent) => { + this._ignore = e.timeStamp; + } + onPrePointer = (e: React.PointerEvent) => { + this._ignore = e.timeStamp; + } + onPostPointer = (e: React.PointerEvent) => { + if (this._ignore !== e.timeStamp) { + e.stopPropagation(); + } + } + onPostWheel = (e: React.WheelEvent) => { + if (this._ignore !== e.timeStamp) { + e.stopPropagation(); + } + } + + + + + public static LayoutString() { return FieldView.LayoutString(DashWebCam); } + + + render() { + const { state, props } = this; + + + + const { + audio, + onUserMedia, + onUserMediaError, + screenshotFormat, + screenshotQuality, + minScreenshotWidth, + minScreenshotHeight, + audioConstraints, + videoConstraints, + imageSmoothing, + fieldKey, + fieldExt, + leaveNativeSize, + fitToBox, + ContainingCollectionView, + Document, + DataDoc, + onClick, + isSelected, + select, + renderDepth, + addDocument, + addDocTab, + pinToPres, + removeDocument, + moveDocument, + ScreenToLocalTransform, + active, + whenActiveChanged, + focus, + PanelWidth, + PanelHeight, + setVideoBox, + setPdfBox, + ContentScaling, + ChromeHeight, + jitterRotation, + backgroundColor, + bringToFront, + zoomToScale, + getScale, + animateBetweenIcon, + layoutKey, + ...rest + } = props; + + console.log("Source produced: ", state.src); + + + + let content = + <div className="webcam-cont" style={{ width: "100%", height: "100%" }} onWheel={this.onPostWheel} onPointerDown={this.onPostPointer} onPointerMove={this.onPostPointer} onPointerUp={this.onPostPointer}> + <video + autoPlay + src={state.src} + muted={!audio} + playsInline + ref={ref => { + console.log("Source produced: ", state.src); + console.log("HasUser Media produced: ", state.hasUserMedia); + + this.video = ref; + }} + {...rest} + /> + </div>; + + + let frozen = !this.props.isSelected() || DocumentDecorations.Instance.Interacting; + let classname = "webBox-cont" + (this.props.isSelected() && !InkingControl.Instance.selectedTool && !DocumentDecorations.Instance.Interacting ? "-interactive" : ""); + + return ( + <> + <div className={classname} > + {content} + </div> + {!frozen ? (null) : <div className="webBox-overlay" onWheel={this.onPreWheel} onPointerDown={this.onPrePointer} onPointerMove={this.onPrePointer} onPointerUp={this.onPrePointer} />} + </>); + + } +}
\ No newline at end of file diff --git a/src/client/views/webcam/DashWebRTC.tsx b/src/client/views/webcam/DashWebRTC.tsx new file mode 100644 index 000000000..9c289b40f --- /dev/null +++ b/src/client/views/webcam/DashWebRTC.tsx @@ -0,0 +1,138 @@ +import { observer } from "mobx-react"; +import React = require("react"); +import { CollectionFreeFormDocumentViewProps } from "../nodes/CollectionFreeFormDocumentView"; +import { FieldViewProps, FieldView } from "../nodes/FieldView"; +import { observable, trace } from "mobx"; +import { DocumentDecorations } from "../DocumentDecorations"; +import { InkingControl } from "../InkingControl"; +import "../../views/nodes/WebBox.scss"; +import adapter from 'webrtc-adapter'; + + + + +const mediaStreamConstaints = { + video: true, +}; + +const offerOptions = { + offerToReceiveVideo: 1, +}; + + +@observer +export class DashWebRTC extends React.Component<CollectionFreeFormDocumentViewProps & FieldViewProps> { + + @observable private localVideoEl: HTMLVideoElement | undefined; + @observable private peerVideoEl: HTMLVideoElement | undefined; + @observable private localStream: MediaStream | undefined; + @observable private startTime = null; + @observable private remoteStream: MediaStream | undefined; + @observable private localPeerConnection: any; + @observable private remotePeerConnection: any; + private callButton: HTMLButtonElement | undefined; + private startButton: HTMLButtonElement | undefined; + private hangupButton: HTMLButtonElement | undefined; + + + componentDidMount() { + this.callButton!.disabled = true; + this.hangupButton!.disabled = true; + navigator.mediaDevices.getUserMedia(mediaStreamConstaints).then(this.gotLocalMediaStream).catch(this.handleLocalMediaStreamError); + this.localVideoEl!.addEventListener('loadedmetadata', this.logVideoLoaded); + this.peerVideoEl!.addEventListener('loadedmetadata', this.logVideoLoaded); + this.peerVideoEl!.addEventListener('onresize', this.logResizedVideo); + } + + + gotLocalMediaStream = (mediaStream: MediaStream) => { + this.localStream = mediaStream; + if (this.localVideoEl) { + this.localVideoEl.srcObject = mediaStream; + } + trace('Received local stream.'); + this.callButton!.disabled = false; + + } + + gotRemoteMediaStream = (event: MediaStreamEvent) => { + let mediaStream = event.stream; + this.peerVideoEl!.srcObject = mediaStream; + this.remoteStream = mediaStream!; + + } + + handleLocalMediaStreamError = (error: string) => { + //console.log("navigator.getUserMedia error: ", error); + trace(`navigator.getUserMedia error: ${error.toString()}.`); + + } + + logVideoLoaded(event: any) { + let video = event.target; + trace(`${video!.id} videoWidth: ${video!.videoWidth}px, ` + + `videoHeight: ${video!.videoHeight}px.`); + } + + logResizedVideo(event: any) { + this.logVideoLoaded(event); + + if (this.startTime) { + let elapsedTime = window.performance.now() - this.startTime!; + this.startTime = null; + trace(`Setup time: ${elapsedTime.toFixed(3)}ms.`); + } + + } + + + + + public static LayoutString() { return FieldView.LayoutString(DashWebRTC); } + + + _ignore = 0; + onPreWheel = (e: React.WheelEvent) => { + this._ignore = e.timeStamp; + } + onPrePointer = (e: React.PointerEvent) => { + this._ignore = e.timeStamp; + } + onPostPointer = (e: React.PointerEvent) => { + if (this._ignore !== e.timeStamp) { + e.stopPropagation(); + } + } + onPostWheel = (e: React.WheelEvent) => { + if (this._ignore !== e.timeStamp) { + e.stopPropagation(); + } + } + + + + render() { + let content = + <div className="webcam-cont" style={{ width: "100%", height: "100%" }} onWheel={this.onPostWheel} onPointerDown={this.onPostPointer} onPointerMove={this.onPostPointer} onPointerUp={this.onPostPointer}> + <video id="localVideo" autoPlay playsInline ref={(e) => this.localVideoEl = e!}></video> + <video id="remoteVideo" autoPlay playsInline ref={(e) => this.peerVideoEl = e!}></video> + <button id="startButton" ref={(e) => this.startButton = e!}>Start</button> + <button id="callButton" ref={(e) => this.callButton = e!}>Call</button> + <button id="hangupButton" ref={(e) => this.hangupButton = e!}>Hang Up</button> + </div>; + + let frozen = !this.props.isSelected() || DocumentDecorations.Instance.Interacting; + let classname = "webBox-cont" + (this.props.isSelected() && !InkingControl.Instance.selectedTool && !DocumentDecorations.Instance.Interacting ? "-interactive" : ""); + + + return ( + <> + <div className={classname} > + {content} + </div> + {!frozen ? (null) : <div className="webBox-overlay" onWheel={this.onPreWheel} onPointerDown={this.onPrePointer} onPointerMove={this.onPrePointer} onPointerUp={this.onPrePointer} />} + </>); + } + + +}
\ No newline at end of file diff --git a/src/new_fields/URLField.ts b/src/new_fields/URLField.ts index b9ad96450..eebd15ecd 100644 --- a/src/new_fields/URLField.ts +++ b/src/new_fields/URLField.ts @@ -44,3 +44,5 @@ export abstract class URLField extends ObjectField { @scriptingGlobal @Deserializable("pdf") export class PdfField extends URLField { } @scriptingGlobal @Deserializable("web") export class WebField extends URLField { } @scriptingGlobal @Deserializable("youtube") export class YoutubeField extends URLField { } +@scriptingGlobal @Deserializable("webcam") export class WebCamField extends URLField { } + diff --git a/src/typings/index.d.ts b/src/typings/index.d.ts index 36d828fdb..26fc5a0b8 100644 --- a/src/typings/index.d.ts +++ b/src/typings/index.d.ts @@ -2,6 +2,9 @@ declare module 'googlephotos'; +declare module 'webrtc-adapter'; + + declare module '@react-pdf/renderer' { import * as React from 'react'; |