Skip to content

Commit 0cc99f8

Browse files
Merge pull request #30 from CameraKit/v0.3.1
v0.3.1
2 parents 0b1faaf + ee08884 commit 0cc99f8

File tree

10 files changed

+162
-83
lines changed

10 files changed

+162
-83
lines changed

example/pages/example.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import * as React from "react";
22
import * as CameraKitWeb from "../../src";
3-
import { CaptureSource } from "../../src/types";
4-
import { CaptureStream } from "../../src";
3+
import { CaptureStream, CaptureSource } from "../../src";
54

65
type Props = {};
76

package-lock.json

Lines changed: 8 additions & 21 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"private": true,
33
"name": "camerakit-web",
4-
"version": "0.2.0",
4+
"version": "0.3.1",
55
"description": "CameraKit for web and javascript projects.",
66
"main": "./dist/compiled/index.js",
77
"types": "./dist/compiled/index.d.ts",
@@ -16,11 +16,10 @@
1616
"author": "Alterac, Inc.",
1717
"license": "MIT",
1818
"dependencies": {
19-
"@mattiasbuelens/web-streams-polyfill": "^0.2.1",
2019
"@types/dom-mediacapture-record": "^1.0.0",
2120
"ogv": "^1.6.0",
2221
"ts-ebml": "^2.0.2",
23-
"webm-media-recorder": "^0.8.0",
22+
"webm-media-recorder": "^0.8.1",
2423
"webrtc-adapter": "^7.1.1"
2524
},
2625
"devDependencies": {

src/entity/CaptureSource.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export class CaptureSource {
2+
device: MediaDeviceInfo;
3+
label: string;
4+
5+
constructor({ device, label }: { device: MediaDeviceInfo; label: string }) {
6+
this.device = device;
7+
this.label = label || "Unnamed source";
8+
}
9+
}

src/entity/CaptureStream.ts

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
import { Shutter } from "./Shutter";
22
import { Recorder } from "./Recorder";
3-
import { CaptureSource, FallbackMediaRecorderConfig } from "../types";
3+
import { CaptureSource } from "./CaptureSource";
4+
import { FallbackMediaRecorderConfig } from "../types";
5+
import { toTrackConstraints, createVideoElement } from "../util";
46

57
export class CaptureStream {
68
private mediaStream: MediaStream;
79
private previewStream: MediaStream;
810

9-
private videoSource: CaptureSource | undefined;
10-
private audioSource: CaptureSource | undefined;
11+
private videoSource:
12+
| CaptureSource
13+
| MediaTrackConstraints
14+
| "front"
15+
| "back"
16+
| undefined;
17+
private audioSource: CaptureSource | MediaTrackConstraints | undefined;
1118
private previewVideoSource = this.videoSource;
1219
private previewAudioSource = this.audioSource;
1320

@@ -19,17 +26,17 @@ export class CaptureStream {
1926
/**
2027
* CaptureStream provides access to streaming related functions
2128
* @param {Object} opts
22-
* @param {CaptureSource} [opts.video] - Video source to create CaptureStream from
23-
* @param {CaptureSource} [opts.audio] - Audio source to create CaptureStream from
29+
* @param {CaptureSource | MediaTrackConstraints | "front" | "back"} [opts.video] - Video source to create CaptureStream from
30+
* @param {CaptureSource | MediaTrackConstraints} [opts.audio] - Audio source to create CaptureStream from
2431
* @param {Partial<FallbackMediaRecorderConfig>} [opts.fallbackConfig] - Optional config for FallbackMediaRecorder
2532
*/
2633
constructor({
2734
video,
2835
audio,
2936
fallbackConfig
3037
}: {
31-
video?: CaptureSource;
32-
audio?: CaptureSource;
38+
video?: CaptureSource | MediaTrackConstraints | "front" | "back";
39+
audio?: CaptureSource | MediaTrackConstraints;
3340
fallbackConfig?: Partial<FallbackMediaRecorderConfig>;
3441
}) {
3542
this.previewVideoSource = this.videoSource = video;
@@ -50,19 +57,17 @@ export class CaptureStream {
5057
private generateConstraints({
5158
source
5259
}: { source?: "original" | "preview" } = {}): MediaStreamConstraints {
53-
const video = (source === "preview"
54-
? this.previewVideoSource
55-
: this.videoSource)!.device!.deviceId;
56-
const audio = (source === "preview"
57-
? this.previewAudioSource
58-
: this.audioSource)!.device!.deviceId;
59-
if (!video && !audio) {
60+
const videoSource =
61+
source === "preview" ? this.previewVideoSource : this.videoSource;
62+
const audioSource =
63+
source === "preview" ? this.previewAudioSource : this.audioSource;
64+
if (videoSource && !audioSource) {
6065
throw new Error("No compatible media sources");
6166
}
6267

6368
return {
64-
video: { deviceId: video ? { exact: video } : undefined },
65-
audio: { deviceId: audio ? { exact: audio } : undefined }
69+
video: toTrackConstraints(videoSource),
70+
audio: toTrackConstraints(audioSource)
6671
};
6772
}
6873

@@ -98,15 +103,8 @@ export class CaptureStream {
98103
* @returns {Promise<Shutter>} Newly created shutter instance
99104
*/
100105
private async initalizeShutter(): Promise<Shutter> {
101-
const original = document.createElement("video");
102-
const preview = document.createElement("video");
103-
104-
original.srcObject = this.mediaStream;
105-
original.play();
106-
original.muted = true;
107-
preview.srcObject = this.previewStream;
108-
preview.play();
109-
preview.muted = true;
106+
const original = createVideoElement(this.mediaStream);
107+
const preview = createVideoElement(this.previewStream);
110108

111109
this.shutter = new Shutter({ original, preview });
112110
return this.shutter;
@@ -181,8 +179,8 @@ export class CaptureStream {
181179
/**
182180
* Re-sets the audio/video source for the specified stream
183181
* @param {Object} [opts={}]
184-
* @param {CaptureSource} [opts.video] - New video source
185-
* @param {CaptureSource} [opts.audio] - New audio source
182+
* @param {CaptureSource | MediaTrackConstraints | "front" | "back"} [opts.video] - New video source
183+
* @param {CaptureSource | MediaTrackConstraints} [opts.audio] - New audio source
186184
* @param {("original" | "preview")} [opts.source=original] - Which stream the to set the new sources
187185
* @returns {Promise<MediaStream>} The new stream with updated source
188186
*/

src/entity/FallbackMediaRecorder.ts

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { getVideoSpecs, injectMetadata } from "../util";
1+
import { getVideoSpecs, injectMetadata, closeStream } from "../util";
22
import { FallbackMediaRecorderConfig } from "../types";
33
import * as path from "path";
44
import FediaRecorder = require("webm-media-recorder");
@@ -33,7 +33,8 @@ export class FallbackMediaRecorder {
3333
stream: MediaStream,
3434
config?: Partial<FallbackMediaRecorderConfig>
3535
) {
36-
this.stream = stream;
36+
// Stream must be cloned, otherwise there are audio recording issues
37+
this.stream = stream.clone();
3738
this.blobs = [];
3839
this.mediaRecorder = null;
3940

@@ -44,24 +45,32 @@ export class FallbackMediaRecorder {
4445
};
4546
}
4647

47-
private destroy() {
48-
// TODO: any needed cleanup
48+
private async destroy() {
49+
if (this.stream) {
50+
// Fallback recorder is responsible for closing the cloned stream
51+
await closeStream(this.stream);
52+
}
4953
}
5054

5155
private createRecorder() {
5256
this.mediaRecorder = null;
5357

5458
try {
55-
this.mediaRecorder = new FediaRecorder(this.stream.clone(), {
56-
mimeType: this.mimeType,
57-
videoBitsPerSecond: this.config.bitrate,
58-
width: this.config.width,
59-
height: this.config.height,
60-
framerate: this.config.framerate
61-
}, {
62-
encoderWorkerFactory: () => new Worker(path.join(this.config.base, WORKER_NAME)),
63-
WebmOpusEncoderWasmPath: path.join(this.config.base, WASM_NAME)
64-
});
59+
this.mediaRecorder = new FediaRecorder(
60+
this.stream.clone(),
61+
{
62+
mimeType: this.mimeType,
63+
videoBitsPerSecond: this.config.bitrate,
64+
width: this.config.width,
65+
height: this.config.height,
66+
framerate: this.config.framerate
67+
},
68+
{
69+
encoderWorkerFactory: () =>
70+
new Worker(path.join(this.config.base, WORKER_NAME)),
71+
WebmOpusEncoderWasmPath: path.join(this.config.base, WASM_NAME)
72+
}
73+
);
6574
} catch (e) {
6675
console.error("Exception while creating MediaRecorder:", e);
6776
return;
@@ -83,7 +92,7 @@ export class FallbackMediaRecorder {
8392
private async stopAndAwait() {
8493
return new Promise(resolve => {
8594
if (this.mediaRecorder) {
86-
this.mediaRecorder.addEventListener("stop", async (e: BlobEvent) => {
95+
this.mediaRecorder.addEventListener("stop", async () => {
8796
resolve();
8897
});
8998
this.mediaRecorder.stop();

src/entity/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export * from "./CaptureSource";
12
export * from "./CaptureStream";
23
export * from "./Shutter";
34
export * from "./Recorder";

src/main/index.ts

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,44 @@
1-
import { CaptureStream } from "../entity";
2-
import {
3-
CaptureSource,
4-
StorageMethod,
5-
FallbackMediaRecorderConfig
6-
} from "../types";
1+
import { CaptureStream, CaptureSource } from "../entity";
2+
import { StorageMethod, FallbackMediaRecorderConfig } from "../types";
73
import settings from "../main/settings";
4+
import { requestAndCloseStream } from "../util";
85

96
/**
107
* Returns media devices available to browser
8+
* @param {Object} [opts]
9+
* @param {boolean} [opts.noRequest] - Return devices without requesting audio/video permissions
1110
* @returns {Promise<{audio: Array<CaptureSource>, video: Array<CaptureSource>}>} Available audio and video sources
1211
*/
13-
export async function getDevices() {
12+
export async function getDevices(
13+
opts: {
14+
noRequest?: boolean;
15+
} = {}
16+
) {
17+
if (!opts.noRequest) {
18+
await requestAndCloseStream();
19+
}
20+
1421
const video: Array<CaptureSource> = [];
1522
const audio: Array<CaptureSource> = [];
1623

1724
const devices = await navigator.mediaDevices.enumerateDevices();
1825
devices.forEach(device => {
1926
switch (device.kind) {
2027
case "videoinput":
21-
video.push({ device, label: device.label || "Unnamed video input" });
28+
video.push(
29+
new CaptureSource({
30+
device,
31+
label: device.label || "Unnamed video input"
32+
})
33+
);
2234
break;
2335
case "audioinput":
24-
audio.push({ device, label: device.label || "Unnamed audio input" });
36+
audio.push(
37+
new CaptureSource({
38+
device,
39+
label: device.label || "Unnamed audio input"
40+
})
41+
);
2542
break;
2643
default:
2744
console.log("Other input type detected:", device.kind);
@@ -33,14 +50,17 @@ export async function getDevices() {
3350

3451
/**
3552
* Creates capture stream via chosen CaptureSource's
53+
* @param {Object} [opts]
54+
* @param {CaptureSource | "front" | "back"} [opts.video] - Video source to create CaptureStream from
55+
* @param {CaptureSource} [opts.video] - Audio source to create CaptureStream from
3656
* @returns {Promise<CaptureStream>} Freshly created CaptureStream from sources
3757
*/
3858
export async function createCaptureStream({
3959
video,
4060
audio,
4161
fallbackConfig
4262
}: {
43-
video?: CaptureSource;
63+
video?: CaptureSource | "front" | "back";
4464
audio?: CaptureSource;
4565
fallbackConfig?: Partial<FallbackMediaRecorderConfig>;
4666
}) {

src/types/index.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,3 @@
1-
export type CaptureSource = {
2-
device: MediaDeviceInfo;
3-
label: string;
4-
};
5-
61
export type StorageMethod = "localStorage" | "sessionStorage" | null;
72

83
export type CKSettings = {

0 commit comments

Comments
 (0)