Skip to content

Commit

Permalink
chore: restructure
Browse files Browse the repository at this point in the history
  • Loading branch information
Sec-ant committed Jan 4, 2024
1 parent b28a7b0 commit 2450cb3
Show file tree
Hide file tree
Showing 11 changed files with 208 additions and 231 deletions.
7 changes: 5 additions & 2 deletions main.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/// <reference types="./src/react/camera/media.d.ts" />
import { startCamera } from "./src/react/camera/useCamera";
/// <reference types="./src/camera/media.d.ts" />
import { startCamera } from "./src/camera/index";

const videoElement = document.querySelector("video");

Expand All @@ -23,4 +23,7 @@ if (videoElement) {
});
console.log("video track", videoTrack);
console.log("audio track", audioTrack);

console.log("video track settings", videoTrack.getSettings());
console.log("audio track settings", audioTrack.getSettings());
}
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@
},
"dependencies": {
"@types/emscripten": "^1.39.10",
"just-clone": "^6.2.0",
"webrtc-adapter": "^8.2.3"
}
}
File renamed without changes.
1 change: 1 addition & 0 deletions src/camera/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./startCamera.js";
2 changes: 1 addition & 1 deletion src/react/camera/media.d.ts → src/camera/media.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// TODO: complete constraints and capabilities
// TODO: export type declarations
// TODO: export type declarations in package.json

declare interface MediaTrackSupportedConstraints {
brightness?: boolean;
Expand Down
186 changes: 186 additions & 0 deletions src/camera/startCamera.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import clone from "just-clone";

const GET_CAPABILITIES_TIMEOUT = 500;
const IOS_PWA_WAIT_TIMEOUT = 3000;

export interface Constraints {
initConstraints?:
| MediaStreamConstraints
| ((
supportedConstraints: MediaTrackSupportedConstraints,
) => MediaStreamConstraints | Promise<MediaStreamConstraints>);
videoConstraints?:
| MediaTrackConstraints
| ((
capabilities: MediaTrackCapabilities,
) => MediaTrackConstraints | Promise<MediaTrackConstraints>);
audioConstraints?:
| MediaTrackConstraints
| ((
capabilities: MediaTrackCapabilities,
) => MediaTrackConstraints | Promise<MediaTrackConstraints>);
}

const defaultConstraints: Required<Constraints> = {
initConstraints: {
video: true,
audio: false,
},
videoConstraints: {},
audioConstraints: {},
};
const _ = clone(defaultConstraints);
export { _ as defaultConstraints };

export async function startCamera(
videoElement: HTMLVideoElement,
{
initConstraints = defaultConstraints.initConstraints,
videoConstraints = defaultConstraints.videoConstraints,
audioConstraints = defaultConstraints.audioConstraints,
}: Constraints = defaultConstraints,
) {
// check if we can use the getUserMedia API at first
if (navigator?.mediaDevices?.getUserMedia === undefined) {
throw new DOMException(
"Cannot get user media." +
"Is this page loaded in a secure context (HTTPS or LOCALHOST)?" +
" Or is getUserMedia supported in the runtime?",
"NotSupportedError",
);
}

// shim WebRTC APIs in the client runtime
await import("webrtc-adapter");

// callback constraints
if (typeof initConstraints === "function") {
initConstraints = await initConstraints(
navigator.mediaDevices.getSupportedConstraints(),
);
}

// apply constraints and get the media stream
const stream = await navigator.mediaDevices.getUserMedia(initConstraints);

// attach the stream to the video element
attachStreamToVideoElement(videoElement, stream);

// in the WeChat browser on iOS,
// 'loadeddata' event won't get fired
// unless the video is explictly triggered by play()
videoElement.play();

// wait for the loadeddata event
// so we can safely use the video element later
await Promise.race([
new Promise<void>((resolve) => {
videoElement.addEventListener(
"loadeddata",
() => {
resolve();
},
{ once: true },
);
}),
new Promise<void>((_, reject) => {
setTimeout(() => {
reject(
new DOMException("The video can not be loaded.", "InvalidStateError"),
);
}, IOS_PWA_WAIT_TIMEOUT);
}),
]);

// get the first video track
const [videoTrack] = stream.getVideoTracks();

// get the first audio track
const [audioTrack] = stream.getAudioTracks();

// apply media track constraints
await Promise.all([
(async () => {
// apply video constraints
if (videoTrack) {
if (typeof videoConstraints === "function") {
videoConstraints = await videoConstraints(
await getCapabilities(videoTrack),
);
}
await videoTrack.applyConstraints(videoConstraints);
}
})(),
(async () => {
// apply audio constraints
if (audioTrack) {
if (typeof audioConstraints === "function") {
audioConstraints = await audioConstraints(
await getCapabilities(audioTrack),
);
}
await audioTrack.applyConstraints(audioConstraints);
}
})(),
]);

// return tracks
return {
videoTrack,
audioTrack,
};
}

function attachStreamToVideoElement(
videoElement: HTMLVideoElement,
stream: MediaStream,
) {
// attach the stream to the video element
if (videoElement.srcObject !== undefined) {
videoElement.srcObject = stream;
} else if (videoElement.mozSrcObject !== undefined) {
videoElement.mozSrcObject = stream;
} else if (window.URL.createObjectURL) {
videoElement.src = (window.URL.createObjectURL as CreateObjectURLCompat)(
stream,
);
} else if (window.webkitURL) {
videoElement.src = (
window.webkitURL.createObjectURL as CreateObjectURLCompat
)(stream);
} else {
videoElement.src = stream.id;
}
}

async function getCapabilities(
track: MediaStreamTrack,
timeout = GET_CAPABILITIES_TIMEOUT,
): Promise<MediaTrackCapabilities> {
return new Promise((resolve) => {
// timeout, return empty capabilities
let timeoutId: number | undefined = setTimeout(() => {
resolve({});
timeoutId = undefined;
return;
}, timeout);

// not supported, return empty capabilities
if (!track.getCapabilities) {
clearTimeout(timeoutId);
resolve({});
timeoutId = undefined;
return;
}

// poll to check capabilities
let capabilities: MediaTrackCapabilities = {};
while (Object.keys(capabilities).length === 0 && timeoutId !== undefined) {
capabilities = track.getCapabilities();
}
clearTimeout(timeoutId);
resolve(capabilities);
timeoutId = undefined;
return;
});
}
1 change: 1 addition & 0 deletions src/react/camera/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./useCamera.js";
28 changes: 0 additions & 28 deletions src/react/camera/shimGetUserMedia.ts

This file was deleted.

Loading

0 comments on commit 2450cb3

Please sign in to comment.