utd_video_effects_kit 0.3.0 copy "utd_video_effects_kit: ^0.3.0" to clipboard
utd_video_effects_kit: ^0.3.0 copied to clipboard

Real-time video filters and beauty effects (LUT color grading, skin smoothing, whitening, background blur, plus MediaPipe face effects — eye color, makeup, accessories) for LiveKit Flutter apps, as a [...]

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:livekit_client/livekit_client.dart';
import 'package:utd_video_effects_kit/utd_video_effects_kit.dart';

/// Demo: open the camera with a [VideoEffectsProcessor] attached and drive every
/// effect — color/beauty sliders, a LUT picker, makeup color pickers, accessory
/// toggles, and background blur.
///
/// Run on a REAL DEVICE — simulators/emulators hit the I420/CPU fallback and the
/// MediaPipe face pass won't be representative. Platform runners (android/ ios/)
/// are generated with `flutter create .` in this directory.
void main() => runApp(const _EffectsDemoApp());

class _EffectsDemoApp extends StatelessWidget {
  const _EffectsDemoApp();

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: _EffectsDemoPage(),
    );
  }
}

class _EffectsDemoPage extends StatefulWidget {
  const _EffectsDemoPage();

  @override
  State<_EffectsDemoPage> createState() => _EffectsDemoPageState();
}

class _EffectsDemoPageState extends State<_EffectsDemoPage> {
  final VideoEffectsProcessor _fx = VideoEffectsProcessor.create();
  LocalVideoTrack? _track;

  // Small palettes for the makeup / eye-color pickers.
  static const _eyePalette = ['#3366CC', '#2E8B57', '#7B3FA0', '#8C6239'];
  static const _lipPalette = ['#CC2244', '#B5179E', '#E0584F', '#7A1F3D'];
  static const _shadowPalette = ['#8844AA', '#C06C84', '#355C7D', '#6C5B7B'];
  static const _blushPalette = ['#E87070', '#F4A6A6', '#D98880', '#E59866'];

  @override
  void initState() {
    super.initState();
    _start();
  }

  Future<void> _start() async {
    final track = await LocalVideoTrack.createCameraTrack(
      CameraCaptureOptions(processor: _fx),
    );
    await _fx.setEnabled(true);
    if (mounted) setState(() => _track = track);
  }

  @override
  void dispose() {
    _track?.stop();
    _fx.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final track = _track;
    return Scaffold(
      appBar: AppBar(title: const Text('Video Effects Demo')),
      body: Column(
        children: [
          Expanded(
            flex: 3,
            child: track == null
                ? const Center(child: CircularProgressIndicator())
                : VideoTrackRenderer(track,
                    mirrorMode: VideoViewMirrorMode.mirror),
          ),
          if (!_fx.isSupported)
            const Padding(
              padding: EdgeInsets.all(8),
              child: Text(
                'Native effects pipeline not available — running passthrough.',
                style: TextStyle(color: Colors.orange),
                textAlign: TextAlign.center,
              ),
            ),
          Expanded(
            flex: 4,
            child: ValueListenableBuilder<EffectsState>(
              valueListenable: _fx.state,
              builder: (_, s, __) => ListView(
                padding: const EdgeInsets.all(12),
                children: [
                  _section('Beauty'),
                  _slider('Smoothing', s.smoothing, _fx.setSmoothing),
                  _slider('Whitening', s.whitening, _fx.setWhitening),
                  _slider('Background blur', s.backgroundBlur,
                      _fx.setBackgroundBlur),
                  const Divider(),
                  _section('LUT filter'),
                  _filterPicker(s.filterKey),
                  const Divider(),
                  _section('Makeup looks'),
                  Wrap(
                    spacing: 8,
                    children: [
                      ActionChip(
                        label: const Text('Clear'),
                        onPressed: () => _fx.setMakeupLook(null),
                      ),
                      for (final look in VideoEffectsMakeup.looks)
                        ActionChip(
                          label: Text(look.label),
                          onPressed: () => _fx.setMakeupLook(look),
                        ),
                    ],
                  ),
                  _section('Lip finish'),
                  Wrap(
                    spacing: 8,
                    children: [
                      for (final f in const ['matte', 'satin', 'gloss'])
                        ChoiceChip(
                          label: Text(f),
                          selected: s.lipFinish == f,
                          onSelected: (_) => _fx.setLipstick(s.lipstick,
                              opacity: s.lipstickOpacity, finish: f),
                        ),
                    ],
                  ),
                  const Divider(),
                  _section('Eye color'),
                  _colorPicker(_eyePalette, s.eyeColor,
                      (c) => _fx.setEyeColor(c, opacity: s.eyeColorOpacity)),
                  _section('Lipstick'),
                  _colorPicker(_lipPalette, s.lipstick,
                      (c) => _fx.setLipstick(c,
                          opacity: s.lipstickOpacity, finish: s.lipFinish)),
                  _section('Eyeshadow'),
                  _colorPicker(_shadowPalette, s.eyeshadow,
                      (c) => _fx.setEyeshadow(c, opacity: s.eyeshadowOpacity)),
                  _section('Blush'),
                  _colorPicker(_blushPalette, s.blush,
                      (c) => _fx.setBlush(c, opacity: s.blushOpacity)),
                  const Divider(),
                  _section('Accessories'),
                  Wrap(
                    spacing: 8,
                    children: [
                      FilterChip(
                        label: const Text('Glasses'),
                        selected: s.glasses,
                        onSelected: _fx.setGlasses,
                      ),
                      FilterChip(
                        label: const Text('Crown'),
                        selected: s.crown,
                        onSelected: _fx.setCrown,
                      ),
                      FilterChip(
                        label: const Text('Cat ears'),
                        selected: s.catEars,
                        onSelected: _fx.setCatEars,
                      ),
                      FilterChip(
                        label: const Text('Sunglasses'),
                        selected: s.sunglasses,
                        onSelected: _fx.setSunglasses,
                      ),
                      FilterChip(
                        label: const Text('Flower crown'),
                        selected: s.flowerCrown,
                        onSelected: _fx.setFlowerCrown,
                      ),
                      FilterChip(
                        label: const Text('Party hat'),
                        selected: s.partyHat,
                        onSelected: _fx.setPartyHat,
                      ),
                      FilterChip(
                        label: const Text('Bunny ears'),
                        selected: s.bunnyEars,
                        onSelected: _fx.setBunnyEars,
                      ),
                      FilterChip(
                        label: const Text('Mustache'),
                        selected: s.mustache,
                        onSelected: _fx.setMustache,
                      ),
                      FilterChip(
                        label: const Text('Halo'),
                        selected: s.halo,
                        onSelected: _fx.setHalo,
                      ),
                    ],
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }

  Widget _section(String label) => Padding(
        padding: const EdgeInsets.only(top: 4, bottom: 4),
        child: Text(label, style: const TextStyle(fontWeight: FontWeight.bold)),
      );

  Widget _slider(String label, double value, ValueChanged<double> onChanged) {
    return Row(
      children: [
        SizedBox(width: 130, child: Text(label)),
        Expanded(child: Slider(value: value, onChanged: onChanged)),
      ],
    );
  }

  /// A row of color swatches. Tapping a swatch sets that hex; tapping the active
  /// one again clears the effect (passes null).
  Widget _colorPicker(
      List<String> palette, String? active, ValueChanged<String?> onChanged) {
    return Row(
      children: [
        for (final hex in palette)
          Padding(
            padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 4),
            child: GestureDetector(
              onTap: () => onChanged(active == hex ? null : hex),
              child: Container(
                width: 36,
                height: 36,
                decoration: BoxDecoration(
                  color: _hexToColor(hex),
                  shape: BoxShape.circle,
                  border: Border.all(
                    color: active == hex ? Colors.white : Colors.black26,
                    width: active == hex ? 3 : 1,
                  ),
                ),
              ),
            ),
          ),
        TextButton(onPressed: () => onChanged(null), child: const Text('Off')),
      ],
    );
  }

  Widget _filterPicker(String? active) {
    return SizedBox(
      height: 40,
      child: ListView(
        scrollDirection: Axis.horizontal,
        children: [
          for (final f in VideoEffectsFilters.all)
            Padding(
              padding: const EdgeInsets.symmetric(horizontal: 4),
              child: ChoiceChip(
                label: Text(f.label),
                selected: active == f.key,
                onSelected: (sel) => _fx.setFilter(sel ? f.key : null),
              ),
            ),
        ],
      ),
    );
  }

  Color _hexToColor(String hex) {
    final clean = hex.replaceAll('#', '');
    final v = int.parse(clean.length == 6 ? 'FF$clean' : clean, radix: 16);
    return Color(v);
  }
}
0
likes
140
points
260
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Real-time video filters and beauty effects (LUT color grading, skin smoothing, whitening, background blur, plus MediaPipe face effects — eye color, makeup, accessories) for LiveKit Flutter apps, as a livekit_client TrackProcessor.

Repository (GitHub)
View/report issues

Topics

#livekit #video #webrtc #filters #beauty

License

MIT (license)

Dependencies

flutter, flutter_webrtc, livekit_client

More

Packages that depend on utd_video_effects_kit

Packages that implement utd_video_effects_kit