Flutter视频模糊预览插件blurhash_video的使用

发布于 1周前 作者 ionicwang 来自 Flutter

Flutter视频模糊预览插件blurhash_video的使用

关于

Blurhash 算法应用于从视频文件中提取的一系列图像。

一个包含7秒视频文件(每秒16帧)的哈希列表大小为6KB(未压缩)!

预览

开始使用

pubspec.yaml 文件中添加最新版本的 blurhash_video 插件:

dependencies:
  blurhash_video: ^x.x.x

使用方法

示例代码

import 'dart:io';
import 'package:ffmpeg_kit_flutter_full_gpl/ffmpeg_kit.dart';
import 'package:ffmpeg_kit_flutter_full_gpl/return_code.dart';
import 'package:flutter/material.dart';
import 'package:blurhash_video/blurhash_video.dart';
import 'package:file_picker/file_picker.dart';
import 'package:blurhash_dart/blurhash_dart.dart' as dart;
import 'package:image/image.dart';
import 'package:path_provider/path_provider.dart';
import 'package:image_sequence_animator/image_sequence_animator.dart';
import 'package:video_player/video_player.dart';

void main() {
  runApp(const App());
}

class App extends StatelessWidget {
  const App({super.key});

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "Blurhash Video Demo",
      theme: ThemeData(primarySwatch: Colors.deepOrange),
      home: const HomePage(),
      debugShowCheckedModeBanner: false,
    );
  }
}

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

  [@override](/user/override)
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  Directory? _directory;
  int _count = 0;

  VideoPlayerController? _gblurController;
  VideoPlayerController? _sequenceController;

  int _hashesSize = 0;
  int _gblurSize = 0;
  int _sequenceSize = 0;

  [@override](/user/override)
  void dispose() async {
    super.dispose();
    _gblurController?.dispose();
    _sequenceController?.dispose();
    if (_directory?.existsSync() == true) {
      await _directory?.delete(recursive: true);
    }
  }

  void _process() async {
    // 重置
    if (_directory?.existsSync() == true) {
      await _directory?.delete(recursive: true);
    }
    setState(() {
      _count = 0;
      _directory = null;
    });

    final temp = await getTemporaryDirectory();
    // print(temp.path);

    // 清理所有先前生成的缓存目录
    // await BlurhashVideo.cleanUp(workingDirectory: temp);

    final result = await FilePicker.platform.pickFiles(type: FileType.video);
    final path = result?.files.first.path;
    if (path == null) return;

    final hashes = await BlurhashVideo.generateBlurHashes(
      path: path,
      workingDirectory: temp,
      fps: 24, // 视频帧率默认为24
      duration: 7, // 持续时间7秒
      resolution: 64, // 像素分辨率
      quality: 75, // 质量范围0到100,越大越好
    );

    // 临时目录用于存储模糊后的图像
    final dir = Directory(
        "${temp.path}/blurhash_video_demo_${DateTime.now().millisecondsSinceEpoch}");
    await dir.create();

    // 从每个哈希生成模糊图像
    for (var i = 0; i < hashes.length; i++) {
      final blurHash = dart.BlurHash.decode(hashes[i]);
      final image = blurHash.toImage(64, 36);
      await File("${dir.path}/blur${i.toString().padLeft(5, "0")}.png")
          .writeAsBytes(encodePng(image));
    }

    setState(() {
      _count = hashes.length;
      _directory = dir;
    });

    // 将哈希保存为txt文件,每行一个哈希
    final contents =
        hashes.fold<String>("", (content, item) => "$content$item\n");
    final hashesFile = File("${dir.path}/hashes.txt");
    await hashesFile.writeAsString(contents);
    // 文件可以通过bzip压缩,可以减小到原来的三分之一
    final hashesSize = await hashesFile.length() ~/ 1024;
    setState(() => _hashesSize = hashesSize);

    // 使用gaussian模糊滤镜生成mp4视频
    final gblurFile = File("${dir.path}/gblur.mp4");
    final gblurSession = await FFmpegKit.execute(
        "-hide_banner -i $path -s 64x36 -t 7 -vf gblur=sigma=128:steps=4 -lossless 1 -quality 100 -an ${gblurFile.path}");
    if (!ReturnCode.isSuccess(await gblurSession.getReturnCode())) {
      // print("ffmpeg error gblur: ${await gblurSession.getOutput()}");
      _gblurController?.dispose();
      _gblurController = null;
    } else {
      setState(() {
        _gblurController = VideoPlayerController.file(gblurFile)
          ..initialize().then((_) => _gblurController!.play());
      });
    }
    final gblurSize = await gblurFile.length() ~/ 1024;
    setState(() => _gblurSize = gblurSize);

    // 从模糊的png生成mp4视频
    final sequenceFile = File("${dir.path}/blurred.mp4");
    final sequenceSession = await FFmpegKit.execute(
        "-hide_banner -framerate 24 -i ${dir.path}/blur%05d.png -vf fps=24 -t 7 -pix_fmt yuv420p ${sequenceFile.path}");
    if (!ReturnCode.isSuccess(await sequenceSession.getReturnCode())) {
      // print("ffmpeg error sequence: ${await sequenceSession.getOutput()}");
      _sequenceController?.dispose();
      _sequenceController = null;
    } else {
      setState(() {
        _sequenceController = VideoPlayerController.file(sequenceFile)
          ..initialize().then((_) => _sequenceController!.play());
      });
    }
    final sequenceSize = await sequenceFile.length() ~/ 1024;
    setState(() => _sequenceSize = sequenceSize);
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("Blurhash Video")),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          if (_directory != null) ...[
            // PNG序列
            ...[
              Text("PNG序列: $_hashesSize KB"),
              SizedBox(
                width: MediaQuery.of(context).size.width,
                height: MediaQuery.of(context).size.width * 0.5625,
                child: Transform.scale(
                  scale: MediaQuery.of(context).size.width / 64,
                  child: ImageSequenceAnimator(
                    _directory!.path,
                    "blur",
                    0,
                    5,
                    "png",
                    _count.toDouble(),
                    fps: 24,
                    isLooping: true,
                    isBoomerang: false,
                    isAutoPlay: true,
                  ),
                ),
              )
            ],

            // 从图像序列生成的MP4
            if (_sequenceController?.value.isInitialized == true) ...[
              Text("MP4模糊哈希: $_sequenceSize KB"),
              AspectRatio(
                aspectRatio: _sequenceController!.value.aspectRatio,
                child: VideoPlayer(_sequenceController!),
              ),
            ],

            // 使用FFmpeg模糊效果生成的MP4
            if (_gblurController?.value.isInitialized == true) ...[
              Text("FFmpeg高斯模糊: $_gblurSize KB"),
              AspectRatio(
                aspectRatio: _gblurController!.value.aspectRatio,
                child: VideoPlayer(_gblurController!),
              ),
            ]
          ] else
            const Center(
                child: Text("选择一个视频",
                    style:
                        TextStyle(fontWeight: FontWeight.w500, fontSize: 18))),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _process,
        tooltip: "选择文件",
        child: const Icon(Icons.file_present_rounded),
      ),
    );
  }
}

更多关于Flutter视频模糊预览插件blurhash_video的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter视频模糊预览插件blurhash_video的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,下面是一个关于如何在Flutter中使用blurhash_video插件来实现视频模糊预览的示例代码。这个插件允许你在视频播放前显示一个基于Blurhash编码的模糊预览图像,从而提升用户体验。

首先,你需要在你的pubspec.yaml文件中添加blurhash_video依赖:

dependencies:
  flutter:
    sdk: flutter
  blurhash_video: ^最新版本号  # 请替换为实际的最新版本号

然后运行flutter pub get来安装依赖。

接下来,你可以在你的Flutter项目中创建一个视频播放器页面,并使用BlurhashVideo组件来显示模糊预览。以下是一个完整的示例代码:

import 'package:flutter/material.dart';
import 'package:blurhash_video/blurhash_video.dart';
import 'package:chewie_flutter/chewie_flutter.dart';  // 一个流行的视频播放器库

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: VideoPlayerScreen(),
    );
  }
}

class VideoPlayerScreen extends StatefulWidget {
  @override
  _VideoPlayerScreenState createState() => _VideoPlayerScreenState();
}

class _VideoPlayerScreenState extends State<VideoPlayerScreen> {
  ChewieController? _chewieController;
  late String blurHash;
  late String videoUrl;

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

    // 示例Blurhash和视频URL
    blurHash = "LXYXA:#111,r99v#y+8.2+XmpbZMxf*kM4-j*X8+Xms8Yc*5";  // 替换为你的Blurhash
    videoUrl = "https://www.example.com/path/to/your/video.mp4";  // 替换为你的视频URL

    // 初始化ChewieController
    _chewieController = ChewieController.fromSource(
      ChewieSource.network(videoUrl),
      aspectRatio: 16 / 9,
      autoPlay: false,
      looping: false,
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Video Player with Blurhash Preview'),
      ),
      body: Center(
        child: Stack(
          alignment: Alignment.center,
          children: [
            // 显示Blurhash预览
            BlurhashVideo(
              blurHash: blurHash,
              width: double.infinity,
              height: double.infinity,
              fit: BoxFit.cover,
              placeholder: Container(
                color: Colors.grey.shade200,
              ),
            ),
            // 视频播放器(初始隐藏,加载视频时显示)
            if (_chewieController != null)
              Positioned.fill(
                child: Chewie(
                  controller: _chewieController!,
                ),
              ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // 播放视频
          if (_chewieController != null) {
            _chewieController!.play();
          }
        },
        tooltip: 'Play',
        child: Icon(Icons.play_arrow),
      ),
    );
  }

  @override
  void dispose() {
    _chewieController?.dispose();
    super.dispose();
  }
}

在这个示例中,我们使用了Chewie库来作为视频播放器。BlurhashVideo组件用于显示基于Blurhash编码的视频模糊预览。当你点击浮动操作按钮(FAB)时,视频将开始播放。

请注意:

  1. 你需要替换blurHashvideoUrl变量中的占位符值为你的实际Blurhash编码和视频URL。
  2. 确保你已经添加了chewie_flutter依赖,并正确配置了所有必要的依赖项。

这个示例展示了如何在Flutter中结合使用blurhash_videochewie_flutter来实现视频模糊预览和播放功能。

回到顶部