Skip to content

Commit 3488aba

Browse files
Merge pull request #12 from apivideo/add-image-support
Refacto & add image support
2 parents f7bc876 + 6dd87d3 commit 3488aba

File tree

16 files changed

+2107
-1637
lines changed

16 files changed

+2107
-1637
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changelog
22
All changes to this project will be documented in this file.
3+
4+
## [0.2.0] - 2023-01-02
5+
- Add Image streams
6+
- Refactor code
7+
38
## [0.1.8] - 2022-10-11
49
- Allow user to customize video name
510

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
- [Instantiation](#instantiation)
2121
- [Options](#options)
2222
- [Methods](#methods)
23-
- [`addStream(mediaStream: MediaStream, options: StreamOptions): string`](#addstreammediastream-mediastream-options-streamoptions-string)
23+
- [`addStream(mediaStream: MediaStream | HTMLImageElement, options: StreamOptions): string`](#addstreammediastream-mediastream--htmlimageelement-options-streamoptions-string)
2424
- [Options](#options-1)
2525
- [`updateStream(streamId: string, options: StreamOptions): void`](#updatestreamstreamid-string-options-streamoptions-void)
2626
- [`removeStream(id: string): void`](#removestreamid-string-void)
@@ -142,9 +142,11 @@ If the `resolution` option is not provided, the canvas will be created with this
142142
## Methods
143143

144144

145-
### `addStream(mediaStream: MediaStream, options: StreamOptions): string`
145+
### `addStream(mediaStream: MediaStream | HTMLImageElement, options: StreamOptions): string`
146146

147-
The addStream() method adds a stream to the composition. It takes a `MediaStream` and an `StreamOptions` parameter.
147+
The addStream() method adds a stream to the composition. A stream can be either a `MediaStream` (for example, the webcam, the screen, or a window capture) or an `HTMLImageElement` (for example, a logo).
148+
149+
It takes a `MediaStream | HTMLImageElement` and an `StreamOptions` parameter.
148150

149151
#### Options
150152

examples/record.a.video/package-lock.json

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

examples/record.a.video/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"lint": "next lint"
1010
},
1111
"dependencies": {
12-
"@api.video/media-stream-composer": "^0.1.5",
12+
"@api.video/media-stream-composer": "../..",
1313
"@mui/icons-material": "^5.8.4",
1414
"material-ui-popup-state": "^3.1.1",
1515
"next": "12.1.6",
Lines changed: 5 additions & 0 deletions
Loading

examples/record.a.video/src/components/StreamDialog.tsx

Lines changed: 48 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
1-
import { StreamMask, StreamPosition } from '@api.video/media-stream-composer'
2-
import CircleIcon from '@mui/icons-material/AccountCircle'
3-
import PhotoCameraFrontIcon from '@mui/icons-material/PhotoCameraFront'
4-
import ScreenshotMonitorIcon from '@mui/icons-material/ScreenshotMonitor'
5-
import { FormControl, FormControlLabel, FormGroup, FormLabel, InputLabel, MenuItem, Radio, RadioGroup, Select, Slider, Switch, ToggleButton, ToggleButtonGroup } from '@mui/material'
6-
import Button from '@mui/material/Button'
7-
import Dialog from '@mui/material/Dialog'
8-
import DialogActions from '@mui/material/DialogActions'
9-
import DialogContent from '@mui/material/DialogContent'
10-
import DialogTitle from '@mui/material/DialogTitle'
11-
import { useEffect, useState } from 'react'
12-
import { ContainDimentionIcon, CoverDimentionIcon, FixedDimensionIcon } from './Icons'
13-
import styles from '../../styles/Home.module.css'
1+
import { StreamMask, StreamPosition } from '@api.video/media-stream-composer';
2+
import CircleIcon from '@mui/icons-material/AccountCircle';
3+
import ImageIcon from '@mui/icons-material/Image';
4+
import PhotoCameraFrontIcon from '@mui/icons-material/PhotoCameraFront';
5+
import ScreenshotMonitorIcon from '@mui/icons-material/ScreenshotMonitor';
6+
import { FormControl, FormControlLabel, FormGroup, FormLabel, InputLabel, MenuItem, Radio, RadioGroup, Select, Slider, Switch, ToggleButton, ToggleButtonGroup } from '@mui/material';
7+
import Button from '@mui/material/Button';
8+
import Dialog from '@mui/material/Dialog';
9+
import DialogActions from '@mui/material/DialogActions';
10+
import DialogContent from '@mui/material/DialogContent';
11+
import DialogTitle from '@mui/material/DialogTitle';
12+
import { createRef, useEffect, useState } from 'react';
13+
import styles from '../../styles/Home.module.css';
14+
import { ContainDimentionIcon, CoverDimentionIcon, FixedDimensionIcon } from './Icons';
1415

1516
export interface StreamFormValues {
1617
type: StreamType;
17-
position: StreamPosition;
18+
position: StreamPosition;
1819
mask: StreamMask;
1920
width?: string;
2021
height?: string;
@@ -24,6 +25,7 @@ export interface StreamFormValues {
2425
resizable: boolean;
2526
deviceId?: string;
2627
opacity?: number;
28+
imageUrl?: string;
2729
}
2830

2931
interface StreamDialogProps {
@@ -33,7 +35,7 @@ interface StreamDialogProps {
3335
onClose?: () => void;
3436
}
3537

36-
type StreamType = "screen" | "webcam";
38+
type StreamType = "screen" | "webcam" | "image";
3739

3840

3941
const StreamDialog = (props: StreamDialogProps) => {
@@ -48,7 +50,9 @@ const StreamDialog = (props: StreamDialogProps) => {
4850
const [draggable, setDraggable] = useState(false);
4951
const [resizable, setResizable] = useState(false);
5052
const [deviceId, setDeviceId] = useState<string>();
53+
const [imageUrl, setImageUrl] = useState<string>("");
5154
const [screencastAvailable, setScreencastAvailable] = useState(typeof navigator !== "undefined" && !!navigator.mediaDevices.getDisplayMedia)
55+
const fileInputRef = createRef<HTMLInputElement>();
5256

5357
// select the first device when the devices are loaded
5458
useEffect(() => {
@@ -90,11 +94,32 @@ const StreamDialog = (props: StreamDialogProps) => {
9094
value={type}
9195
exclusive
9296
onChange={(v, w) => w && setType(w)}>
93-
<ToggleButton disabled={!screencastAvailable} value="screen"><ScreenshotMonitorIcon className={styles.toogleButtonIcon} /> Screencast</ToggleButton>
94-
<ToggleButton disabled={props.devices.length === 0} value="webcam"><PhotoCameraFrontIcon className={styles.toogleButtonIcon} /> Webcam</ToggleButton>
97+
<ToggleButton disabled={!screencastAvailable} value="screen"><ScreenshotMonitorIcon className={styles.toogleButtonIcon} /> Screencast</ToggleButton>
98+
<ToggleButton disabled={props.devices.length === 0} value="webcam"><PhotoCameraFrontIcon className={styles.toogleButtonIcon} /> Webcam</ToggleButton>
99+
<ToggleButton value="image"><ImageIcon className={styles.toogleButtonIcon} /> Image</ToggleButton>
95100
</ToggleButtonGroup>
96101
</FormGroup>
97102
</FormControl>
103+
{type === "image" && <div>
104+
<FormControl style={{width: "100%"}}>
105+
<FormLabel component="legend" id="device-radio-buttons-group-label">Image</FormLabel>
106+
<Button variant="contained" onClick={() => fileInputRef.current && fileInputRef.current.click()}>Select an image</Button>
107+
<input style={{display: "none"}} ref={fileInputRef} type="file" accept="image/*" onChange={(e) => {
108+
if (e.target.files && e.target.files.length > 0) {
109+
const file = e.target.files[0];
110+
const reader = new FileReader();
111+
reader.onload = (e) => {
112+
if (e.target && e.target.result) {
113+
setImageUrl(e.target.result as string);
114+
}
115+
}
116+
reader.readAsDataURL(file);
117+
}
118+
}} />
119+
<img src={imageUrl} style={{width: "100%", marginTop: "10px"}} />
120+
</FormControl>
121+
</div>
122+
}
98123
{type === "webcam" && <div>
99124
<FormControl>
100125
<FormLabel component="legend" id="device-radio-buttons-group-label">Device</FormLabel>
@@ -122,9 +147,9 @@ const StreamDialog = (props: StreamDialogProps) => {
122147
value={position}
123148
exclusive
124149
onChange={(v, w) => w && setPosition(w)}>
125-
<ToggleButton value="cover"><CoverDimentionIcon className={styles.toogleButtonIcon} /> cover</ToggleButton>
126-
<ToggleButton value="contain"><ContainDimentionIcon className={styles.toogleButtonIcon} />contain</ToggleButton>
127-
<ToggleButton value="fixed"><FixedDimensionIcon className={styles.toogleButtonIcon} />fixed</ToggleButton>
150+
<ToggleButton value="cover"><CoverDimentionIcon className={styles.toogleButtonIcon} /> cover</ToggleButton>
151+
<ToggleButton value="contain"><ContainDimentionIcon className={styles.toogleButtonIcon} />contain</ToggleButton>
152+
<ToggleButton value="fixed"><FixedDimensionIcon className={styles.toogleButtonIcon} />fixed</ToggleButton>
128153
</ToggleButtonGroup>
129154
</FormGroup>
130155
</FormControl>
@@ -224,7 +249,7 @@ const StreamDialog = (props: StreamDialogProps) => {
224249
onChange={(v, w) => w && setMask(w)}
225250
>
226251
<ToggleButton value="none">none</ToggleButton>
227-
<ToggleButton value="circle"><CircleIcon className={styles.toogleButtonIcon} /> circle</ToggleButton>
252+
<ToggleButton value="circle"><CircleIcon className={styles.toogleButtonIcon} /> circle</ToggleButton>
228253
</ToggleButtonGroup>
229254
</FormGroup>
230255
</FormControl>
@@ -253,8 +278,9 @@ const StreamDialog = (props: StreamDialogProps) => {
253278
</DialogContent>
254279
<DialogActions>
255280
<Button onClick={() => props.onClose && props.onClose()}>Cancel</Button>
256-
<Button disabled={!screencastAvailable && props.devices.length === 0} onClick={() => props.onSubmit && props.onSubmit({
281+
<Button disabled={!screencastAvailable && props.devices.length === 0 || (type === "image" && imageUrl === "")} onClick={() => props.onSubmit && props.onSubmit({
257282
type,
283+
imageUrl,
258284
position: position!,
259285
mask: mask!,
260286
width,

0 commit comments

Comments
 (0)