Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow start/pause of streaming image input. Only access the webcam while it's needed #7228

Merged
merged 9 commits into from Feb 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/major-rats-like.md
@@ -0,0 +1,6 @@
---
"@gradio/image": minor
"gradio": minor
---

feat:Allow start/pause of streaming image input. Only access the webcam while it's needed
3 changes: 3 additions & 0 deletions gradio/interface.py
Expand Up @@ -649,9 +649,11 @@ def attach_submit_events(
)
else:
events: list[Callable] = []
streaming_event = False
for component in self.input_components:
if component.has_event("stream") and component.streaming: # type: ignore
events.append(component.stream) # type: ignore
streaming_event = True
elif component.has_event("change"):
events.append(component.change) # type: ignore
on(
Expand All @@ -662,6 +664,7 @@ def attach_submit_events(
api_name=self.api_name,
preprocess=not (self.api_mode),
postprocess=not (self.api_mode),
show_progress="hidden" if streaming_event else "full",
)
else:
if _submit_btn is None:
Expand Down
3 changes: 1 addition & 2 deletions js/image/Image.stories.svelte
Expand Up @@ -72,8 +72,7 @@
const webcamButton = await canvas.findByLabelText("Capture from camera");
userEvent.click(webcamButton);

userEvent.click(await canvas.findByTitle("select video source"));
userEvent.click(await canvas.findByLabelText("select source"));
userEvent.click(await canvas.findByTitle("grant webcam access"));
userEvent.click(await canvas.findByLabelText("Upload file"));
userEvent.click(await canvas.findByLabelText("Paste from clipboard"));
}}
Expand Down
63 changes: 54 additions & 9 deletions js/image/shared/Webcam.svelte
@@ -1,9 +1,17 @@
<script lang="ts">
import { createEventDispatcher, onMount } from "svelte";
import { Camera, Circle, Square, DropdownArrow } from "@gradio/icons";
import {
Camera,
Circle,
Square,
DropdownArrow,
Webcam as WebcamIcon
} from "@gradio/icons";
import type { I18nFormatter } from "@gradio/utils";
import type { FileData } from "@gradio/client";
import { prepare_files, upload } from "@gradio/client";
import WebcamPermissions from "./WebcamPermissions.svelte";
import { fade } from "svelte/transition";

let video_source: HTMLVideoElement;
let canvas: HTMLCanvasElement;
Expand Down Expand Up @@ -42,6 +50,7 @@
video_source.srcObject = stream;
video_source.muted = true;
video_source.play();
webcam_accessed = true;
} catch (err) {
if (err instanceof DOMException && err.name == "NotAllowedError") {
dispatch("error", i18n("image.allow_webcam_access"));
Expand All @@ -53,8 +62,11 @@

function take_picture(): void {
var context = canvas.getContext("2d")!;

if (video_source.videoWidth && video_source.videoHeight) {
if (
(!streaming || (streaming && recording)) &&
video_source.videoWidth &&
video_source.videoHeight
) {
canvas.width = video_source.videoWidth;
canvas.height = video_source.videoHeight;
context.drawImage(
Expand Down Expand Up @@ -131,7 +143,23 @@
recording = !recording;
}

access_webcam();
let webcam_accessed = false;

function record_video_or_photo(): void {
if (mode === "image" && streaming) {
recording = !recording;
}
if (mode === "image") {
take_picture();
} else {
take_recording();
}
if (!recording && stream) {
stream.getTracks().forEach((track) => track.stop());
video_source.srcObject = null;
webcam_accessed = false;
}
}

if (streaming && mode === "image") {
window.setInterval(() => {
Expand Down Expand Up @@ -185,14 +213,22 @@
<div class="wrap">
<!-- svelte-ignore a11y-media-has-caption -->
<!-- need to suppress for video streaming https://github.com/sveltejs/svelte/issues/5967 -->
<video bind:this={video_source} class:flip={mirror_webcam} />
{#if !streaming}
<video
bind:this={video_source}
class:flip={mirror_webcam}
class:hide={!webcam_accessed}
/>
{#if !webcam_accessed}
<div in:fade={{ delay: 100, duration: 200 }} title="grant webcam access">
<WebcamPermissions on:click={async () => access_webcam()} />
</div>
{:else}
<div class="button-wrap">
<button
on:click={mode === "image" ? take_picture : take_recording}
on:click={record_video_or_photo}
aria-label={mode === "image" ? "capture photo" : "start recording"}
>
{#if mode === "video"}
{#if mode === "video" || streaming}
{#if recording}
<div class="icon red" title="stop recording">
<Square />
Expand All @@ -208,7 +244,6 @@
</div>
{/if}
</button>

{#if !recording}
<button
on:click={select_source}
Expand Down Expand Up @@ -253,6 +288,10 @@
height: var(--size-full);
}

.hide {
display: none;
}

video {
width: var(--size-full);
height: var(--size-full);
Expand Down Expand Up @@ -353,4 +392,10 @@
height: var(--size-5);
opacity: 0.8;
}

@media (--screen-md) {
.wrap {
font-size: var(--text-lg);
}
}
</style>
46 changes: 46 additions & 0 deletions js/image/shared/WebcamPermissions.svelte
@@ -0,0 +1,46 @@
<script lang="ts">
import { Webcam } from "@gradio/icons";
import { createEventDispatcher } from "svelte";

const dispatch = createEventDispatcher<{
click: undefined;
}>();
</script>

<button style:height="100%" on:click={() => dispatch("click")}>
<div class="wrap">
<span class="icon-wrap">
<Webcam />
</span>
{"Click to Access Webcam"}
</div>
</button>

<style>
button {
cursor: pointer;
width: var(--size-full);
}

.wrap {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height: var(--size-60);
color: var(--block-label-text-color);
height: 100%;
padding-top: var(--size-3);
}

.icon-wrap {
width: 30px;
margin-bottom: var(--spacing-lg);
}

@media (--screen-md) {
.wrap {
font-size: var(--text-lg);
}
}
</style>