blink_shot

A Flutter package for blink-triggered photo capture using camera and Google ML Kit face detection.

blink_shot provides a camera preview widget with blink detection and auto-capture capabilities. Use BlinkShotController for lifecycle control and event handling.

Features

  • Blink detection based on left/right eye-open probability history
  • Ready-to-use camera preview widget with face alignment feedback
  • Automatic capture when a blink is detected
  • Manual capture mode when auto-capture is disabled
  • Low-level APIs for custom camera or detection pipelines

Platform support

  • Android
  • iOS

Desktop and web are not primary targets for this package because the blink flow depends on the mobile camera stack and ML Kit face detection.

Installation

flutter pub add blink_shot

Platform setup

blink_shot uses the device camera and face detection. The host app must provide the required platform permissions.

Android

Add camera permission to your app manifest:

<uses-permission android:name="android.permission.CAMERA" />

Path: android/app/src/main/AndroidManifest.xml

iOS

Add a camera usage description to your app Info.plist:

<key>NSCameraUsageDescription</key>
<string>Camera access is required to capture photos.</string>

Path: ios/Runner/Info.plist

This project is currently configured around iOS 13.0 in the example app and iOS project files.

Quick start

Use BlinkShotController with BlinkShotView for automatic capture, state management, and event handling:

import 'package:blink_shot/blink_shot.dart';
import 'package:flutter/material.dart';

class BlinkCapturePage extends StatefulWidget {
  const BlinkCapturePage({super.key});

  @override
  State<BlinkCapturePage> createState() => _BlinkCapturePageState();
}

class _BlinkCapturePageState extends State<BlinkCapturePage> {
  late final BlinkShotController _controller;
  String _status = 'Initializing camera...';

  @override
  void initState() {
    super.initState();
    _controller = BlinkShotController(
      config: const BlinkShotConfig(
        autoCapture: true,
        showDefaultOverlay: true,
        showDefaultStatusText: true,
      ),
      onCapture: (file) {
        debugPrint('Captured file: ${file.path}');
      },
      onError: (error) {
        if (!mounted) return;
        setState(() {
          _status = 'Error: $error';
        });
      },
      onStateChanged: (state) {
        if (!mounted) return;
        setState(() {
          _status = switch (state) {
            BlinkShotState.initializing => 'Initializing camera...',
            BlinkShotState.ready => 'Blink to capture',
            BlinkShotState.capturing => 'Capturing...',
            BlinkShotState.paused => 'Paused',
            BlinkShotState.error => 'Camera error',
            BlinkShotState.disposed => 'Disposed',
          };
        });
      },
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Blink Shot')),
      body: Column(
        children: [
          Expanded(
            child: BlinkShotView(
              controller: _controller,
              instructionText: 'Align your face and blink once',
            ),
          ),
          Padding(
            padding: const EdgeInsets.all(16),
            child: Text(_status),
          ),
        ],
      ),
    );
  }
}

Configuration

BlinkShotConfig controls capture and UI behavior:

Property Default Description
openThreshold 0.7 Average eye-open score required to treat both eyes as open
closedThreshold 0.25 Average eye-open score required to treat both eyes as closed
historyLength 8 Number of frames used when evaluating a blink
autoCapture true Automatically take a picture when a blink is detected
cameraLensDirection CameraLensDirection.front Preferred camera
showDefaultOverlay true Shows the built-in face alignment frame
showDefaultStatusText true Shows the built-in status label

Example:

final controller = BlinkShotController(
  config: const BlinkShotConfig(
    autoCapture: false,
    openThreshold: 0.8,
    closedThreshold: 0.2,
    historyLength: 10,
    showDefaultOverlay: false,
  ),
);

When autoCapture is false, BlinkShotView shows a capture button instead of taking a photo automatically.

Direct detector usage

If you already have your own face or eye-tracking pipeline, you can use BlinkDetector directly:

final detector = BlinkDetector(
  historyLength: 8,
  openThreshold: 0.6,
  closedThreshold: 0.25,
);

void onFrame(double? leftEyeOpenProbability, double? rightEyeOpenProbability) {
  detector.add(leftEyeOpenProbability, rightEyeOpenProbability);

  if (detector.didBlink) {
    capturePhoto();
    detector.reset();
  }
}

Lower-level APIs

  • BlinkDetector tracks blink state from eye-open probabilities
  • FaceBlinkProcessor combines ML Kit face detection with blink detection
  • BlinkShotController manages camera initialization, preview, frame processing, capture, pause, and resume
  • BlinkShotView renders the preview and optional default UI

Controller state

BlinkShotController can report these states through onStateChanged:

  • initializing
  • ready
  • capturing
  • paused
  • error
  • disposed

Useful controller methods:

  • initialize()
  • capture()
  • pause()
  • resume()
  • resetBlinkDetector()

Notes

  • The built-in blink flow is intended for front-camera selfie capture.
  • BlinkDetector and BlinkShotConfig do not share the same default openThreshold. The controller uses BlinkShotConfig, whose default is 0.7.
  • In controller mode, handle captured files through BlinkShotController.onCapture.

Example app

See example/lib/main.dart for a complete integration example.

License

MIT License. See LICENSE.

Libraries