当前位置: 首页 > article >正文

flutter缓存网络视频到本地,可离线观看

记录一下解决问题的过程,希望自己以后可以参考看看,解决更多的问题。

需求:flutter 缓存网络视频文件,可离线观看。

解决:

1,flutter APP视频播放组件调整;

2,找到视频播放组件,传入url解析的地方;

 _meeduPlayerController.setDataSource(DataSource(//指定是网络类型的数据type: DataSourceType.network,//设置url参数source: widget.videoUrl != ""? widget.videoUrl: "https://movietrailers.apple.com/movies/paramount/the-spongebob-movie-sponge-on-the-run/the-spongebob-movie-sponge-on-the-run-big-game_h720p.mov",httpHeaders: {"Range": "bytes=0-1023"},),autoplay: !background && widget.autoplay,);

3,那也就是无网路的时候播放本地已经缓存了的对应url的对应视频,先在没有缓存的时候,缓存该文件

        3.1,添加缓存(保存视频)功能依赖

                3.1.1,在视频播放依赖包中添加缓存依赖:flutter_cache_manager: ^3.4.1

                3.1.2,添加基于这个新依赖的功能代码:

import 'package:flutter_cache_manager/flutter_cache_manager.dart';class CustomVideoCacheManager {static const key = 'customCacheKey';static final CacheManager instance = CacheManager(Config(key,maxNrOfCacheObjects: 50, // 最多缓存 50 个视频// maxTotalSize: 1024 * 1024 * 1024 * 2, // 最大缓存 2GBstalePeriod: Duration(days: 7), // 缓存保留时间repo: JsonCacheInfoRepository(databaseName: key),fileSystem: IOFileSystem(key),fileService: HttpFileService(),),);
}

 暴露出来新加的dart类

                3.1.3,在video_widget组件中使用缓存工具缓存视频 

        3.2,在没有网的时候使用该缓存视频,改造步骤2中的播放方法:

_meeduPlayerController.setDataSource(//1网络时候的DataSource// DataSource(//   type: DataSourceType.network,//   source: widget.videoUrl != ""//       ? widget.videoUrl//       : "https://movietrailers.apple.com/movies/paramount/the-spongebob-movie-sponge-on-the-run/the-spongebob-movie-sponge-on-the-run-big-game_h720p.mov",//   httpHeaders: {"Range": "bytes=0-1023"},// ),//2本地文件//错误写法:// DataSource(//   type: DataSourceType.file,//   // file: cacheFile,//   source://       "/data/user/0/com.example.client/cache/customCacheKey/9dade030-3153-11f0-b119-93d21292c9e9.mp4",// ),//正确写法// DataSource(//   type: DataSourceType.file,//   // file: cacheFile,//   file: File(//       "/data/user/0/com.example.client/cache/customCacheKey/9dade030-3153-11f0-b119-93d21292c9e9.mp4"),// ),//所以根据条件判断用上边的任一个dataSourcedataSource,autoplay: !background && widget.autoplay,);

        3.2.1,这个步骤中的插曲,就是使用本地文件一直报空,打印了_meeduPlayerController,和cacheFile都不为空,但是还是报空。

可能得问题有:

一,以为是异步写法awiat获得值,会产生后边的代码先于值计算出来,就运行了导致空

二,错误写法只是照搬了网络视频的写法,更换了一下type的参数,并没有多想,以为也是根据source来写;

解决问题一:

1看下await是怎么产生的。

        1.1,拿本地的缓存文件就有异步

        1.2,判断文件是否完成,是否可播也有await 

2如何避免;如果避免不了await,如何等值完全算完,不为空了再进行下一步的调用。

判断内容长度,是否下载完成,获取sp,判断是否可播都需要异步,就是不能直接拿到值。

Future<int?> getCachedContentLength(String url) async {final prefs = await SharedPreferences.getInstance();return prefs.getInt('video_content_length_$url');}Future<void> cacheContentLength(String url, int length) async {final prefs = await SharedPreferences.getInstance();prefs.setInt('video_content_length_$url', length);}Future<bool> isVideoFileComplete(String url) async {// 获取之前缓存的原始大小final expectedLength = await getCachedContentLength(url);if (expectedLength == null) return false;// 获取本地缓存文件FileInfo? fileInfo = await DefaultCacheManager().getFileFromCache(url);final file = fileInfo?.file;if (file == null || !file.existsSync()) return false;final localLength = file.lengthSync();bool isSame = (localLength == expectedLength);print("video_widget 是否下载完成:$isSame");return isSame;}Future<bool> isVideoPlayable(String filePath) async {final controller = VideoPlayerController.file(File(filePath));try {await controller.initialize();await controller.dispose();print("video_widget 可以 正常播放");return true;} catch (e) {print("video_widget 不能 正常播放");return false;}}

所以用到这几个方法的设置setDataSource()方法也必定是异步的

// 单独封装异步判断逻辑Future<DataSource> _getDataSource(File? cachedFile, String url) async {if (cachedFile != null) {final exists = cachedFile.existsSync();final playable = await isVideoPlayable(cachedFile.path);final complete = await isVideoFileComplete(cachedFile.path);print("video_widget: cachedFile != null: ${cachedFile != null}");print("video_widget: existsSync: $exists");print("video_widget: isVideoPlayable: $playable");print("video_widget: isVideoFileComplete: $complete");if (exists && playable && complete) {print("video_widget:即将使用缓存视频");return DataSource(type: DataSourceType.file,source: cachedFile.path,httpHeaders: {"Range": "bytes=0-1023"},);}}// 如果没有命中缓存或缓存不完整,则走网络加载File? cacheFile;try {cacheFile = await CustomVideoCacheManager.instance.getSingleFile(url);} catch (e) {print("video_widget:网络文件获取失败: $e");}final networkSource = DataSource(type: DataSourceType.network,source: widget.videoUrl.isNotEmpty? widget.videoUrl: "https://movietrailers.apple.com/movies/paramount/the-spongebob-movie-sponge-on-the-run/the-spongebob-movie-sponge-on-the-run-big-game_h720p.mov",httpHeaders: {"Range": "bytes=0-1023"},);return cacheFile != null? DataSource(type: DataSourceType.file,file: cacheFile,): networkSource;}

使用这种方法,就不用在_meeduPlayerController设置参数的时候使用一个异步返回的dataSource了,代码如下,这样就可以使用一个看似同步的代码,完成了一个异步的操作。(并不会因为看起来像是同步的写法,就会发生dataSource的值还没回来的时候就执行了后边的代码,导致null产生。这个就是典型的支持异步操作的代码,不然就得像Java一样写回调了。)

// 封装异步获取 DataSource 的逻辑
dataSource = await _getDataSource(cachedFile, lastUrl);_meeduPlayerController.setDataSource(//所以根据条件判断用上边的任一个dataSourcedataSource,autoplay: !background && widget.autoplay,);

不用特意写then来完成这个异步操作,以下代码不推荐:

await _getDataSource(cachedFile, lastUrl).then((dataSource) {if (dataSource != null) {_meeduPlayerController.setDataSource(dataSource,autoplay: !background && widget.autoplay,);} else {print("video_widget:dataSource为空");}});

解决问题二:

1更换本地缓存文件的地址来写死dataSource参数,还是不行

2想到看下这个依赖包的说明文件是否支持本地文件播放,flutter_meedu_videoplayer example | Flutter package 看到是支持的,

3看依赖包的例子是怎么写的,没有具体写怎么播放本地视频

4看依赖包的源码是怎么使用

4.1,dataSource源码怎么使用的(只是设置参数,没有使用这个参数的逻辑)

4.2,那就找使用这个参数的源码:_meeduPlayerController,有怎么设置本地文件的DataSource方法,最终调整成正确的参数设置方式。

最后,以下是video_widget.dart的完整代码,仅供参考

import 'dart:async';
import 'dart:io';import 'package:audio_session/audio_session.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_meedu_videoplayer/meedu_player.dart';
import 'package:game_lib/common/common_page.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:wakelock_plus/wakelock_plus.dart';class VideoWidget extends StatefulWidget {String videoUrl;Function? onVideoEnd;bool autoplay;Function(MeeduPlayerController)? onInit;Function(PlayerStatus)? onVideoStatusChanged;Function(Duration)? onVideoPositionChanged;bool closeFullscreenOnEnd;BoxFit fit;bool fullscreen;Function(bool)? onBackground;VideoWidget({super.key,required this.videoUrl,this.onVideoEnd,this.onInit,this.autoplay = true,this.fullscreen = true,this.fit = BoxFit.contain,this.onVideoStatusChanged,this.closeFullscreenOnEnd = true,this.onVideoPositionChanged,this.onBackground});@overrideState<VideoWidget> createState() => _VideoWidgetState();
}class _VideoWidgetState extends State<VideoWidget>with WidgetsBindingObserver, RouteAware {late final _meeduPlayerController = MeeduPlayerController(controlsStyle: ControlsStyle.primary,screenManager: const ScreenManager(orientations: [DeviceOrientation.landscapeLeft,]),enabledButtons: EnabledButtons(videoFit: false, muteAndSound: false, fullscreen: widget.fullscreen),fits: [BoxFit.contain],initialFit: widget.fit);StreamSubscription? _playerEventSubs;int lastPosition = 0;String lastUrl = "";bool background = false; // 是否处于后台@overridevoid initState() {WidgetsBinding.instance.addObserver(this);WidgetsBinding.instance.addPostFrameCallback((_) {Future.delayed(const Duration(milliseconds: 500), () {_init();});});widget.onInit?.call(_meeduPlayerController);_meeduPlayerController.onPositionChanged.listen((event) {if (event.inSeconds != lastPosition) {lastPosition = event.inMilliseconds;widget.onVideoPositionChanged?.call(event);//print("onPositionChanged: $event ${event.inSeconds}");}});_playerEventSubs = _meeduPlayerController.onPlayerStatusChanged.listen((PlayerStatus status) async {widget.onVideoStatusChanged?.call(status);print("onPlayerStatusChanged: $status");if (status == PlayerStatus.playing) {WakelockPlus.enable();Future.delayed(const Duration(milliseconds: 100), () {if (widget.fit == BoxFit.contain) {_meeduPlayerController.toggleVideoFit();}});} else {WakelockPlus.disable();final session = await AudioSession.instance;if (await session.setActive(false)) {print("AudioSession setActive abandon");}}if (status == PlayerStatus.completed) {if (widget.closeFullscreenOnEnd &&_meeduPlayerController.fullscreen.value &&Navigator.canPop(context)) {
//            Navigator.pop(context);
//            注释上面代码,播放完后不退出全屏}if (widget.onVideoEnd != null) {widget.onVideoEnd!();}}},);Timer? timer;_meeduPlayerController.onDataStatusChanged.listen((DataStatus status) {if (status == DataStatus.error) {setState(() {_meeduPlayerController.errorText = "";});print("============= video widget onDataStatusChanged: $status videoUrl: ${widget.videoUrl}");if (widget.videoUrl.isNotEmpty) {timer?.cancel();timer = Timer(const Duration(milliseconds: 1), () {setSource();});}}});super.initState();}@overridevoid dispose() {_playerEventSubs?.cancel();_meeduPlayerController.dispose();WidgetsBinding.instance.removeObserver(this);AppRouteObserver().routeObserver.unsubscribe(this);super.dispose();}@overrideFuture<void> didChangeAppLifecycleState(AppLifecycleState state) async {print("video widget didChangeAppLifecycleState: $state");final session = await AudioSession.instance;if (state == AppLifecycleState.resumed) {background = false;widget.onBackground?.call(background);_meeduPlayerController.play();} else if (state == AppLifecycleState.paused) {background = true;widget.onBackground?.call(background);_meeduPlayerController.pause();}}@overridevoid didChangeDependencies() {// TODO: implement didChangeDependenciessuper.didChangeDependencies();AppRouteObserver().routeObserver.subscribe(this, ModalRoute.of(context)!);}@overridevoid didPushNext() {Future.delayed(const Duration(milliseconds: 500), () {if (!_meeduPlayerController.fullscreen.value) {_meeduPlayerController.pause();}});}_init() {print("autoplay: ${widget.autoplay}");setSource();}Future<void> setSource() async {if (widget.videoUrl == lastUrl) {return;}lastUrl = widget.videoUrl;File? cachedFile;DataSource? dataSource;try {print("video_widget:设置视频资源,lastUrl:$lastUrl");FileInfo? fileInfo =await CustomVideoCacheManager.instance.getFileFromCache(lastUrl);cachedFile = fileInfo?.file;print("video_widget:缓存文件地址${cachedFile?.path}");} catch (e) {print("video_widget:未找到缓存视频");}// 封装异步获取 DataSource 的逻辑dataSource = await _getDataSource(cachedFile, lastUrl);// await _getDataSource(cachedFile, lastUrl).then((dataSource) {//   print(//       "=====video_widget:_meeduPlayerController是否为空:${_meeduPlayerController == null}");//   print("=====video_widget:dataSource是否为空:${dataSource == null}");//   if (dataSource != null) {//     _meeduPlayerController.setDataSource(//       // DataSource(//       //   type: DataSourceType.network,//       //   source: widget.videoUrl != ""//       //       ? widget.videoUrl//       //       : "https://movietrailers.apple.com/movies/paramount/the-spongebob-movie-sponge-on-the-run/the-spongebob-movie-sponge-on-the-run-big-game_h720p.mov",//       //   httpHeaders: {"Range": "bytes=0-1023"},//       // ),////       dataSource,////       autoplay: !background && widget.autoplay,//     );//   } else {//     print("video_widget:dataSource为空");//   }// });//清除缓存//await CustomVideoCacheManager.instance.emptyCache();_meeduPlayerController.setDataSource(// DataSource(//   type: DataSourceType.network,//   source: widget.videoUrl != ""//       ? widget.videoUrl//       : "https://movietrailers.apple.com/movies/paramount/the-spongebob-movie-sponge-on-the-run/the-spongebob-movie-sponge-on-the-run-big-game_h720p.mov",//   httpHeaders: {"Range": "bytes=0-1023"},// ),// DataSource(//   type: DataSourceType.file,//   // file: cacheFile,//   file: File(//       "/data/user/0/com.example.client/cache/customCacheKey/9dade030-3153-11f0-b119-93d21292c9e9.mp4"),// ),dataSource,autoplay: !background && widget.autoplay,);}// 单独封装异步判断逻辑Future<DataSource> _getDataSource(File? cachedFile, String url) async {if (cachedFile != null) {final exists = cachedFile.existsSync();final playable = await isVideoPlayable(cachedFile.path);final complete = await isVideoFileComplete(cachedFile.path);print("video_widget: cachedFile != null: ${cachedFile != null}");print("video_widget: existsSync: $exists");print("video_widget: isVideoPlayable: $playable");print("video_widget: isVideoFileComplete: $complete");if (exists && playable && complete) {print("video_widget:即将使用缓存视频");return DataSource(type: DataSourceType.file,source: cachedFile.path,httpHeaders: {"Range": "bytes=0-1023"},);}}// 如果没有命中缓存或缓存不完整,则走网络加载File? cacheFile;try {cacheFile = await CustomVideoCacheManager.instance.getSingleFile(url);} catch (e) {print("video_widget:网络文件获取失败: $e");}final networkSource = DataSource(type: DataSourceType.network,source: widget.videoUrl.isNotEmpty? widget.videoUrl: "https://movietrailers.apple.com/movies/paramount/the-spongebob-movie-sponge-on-the-run/the-spongebob-movie-sponge-on-the-run-big-game_h720p.mov",httpHeaders: {"Range": "bytes=0-1023"},);return cacheFile != null? DataSource(type: DataSourceType.file,file: cacheFile,): networkSource;}Future<int?> getCachedContentLength(String url) async {final prefs = await SharedPreferences.getInstance();return prefs.getInt('video_content_length_$url');}Future<void> cacheContentLength(String url, int length) async {final prefs = await SharedPreferences.getInstance();prefs.setInt('video_content_length_$url', length);}Future<bool> isVideoFileComplete(String url) async {// 获取之前缓存的原始大小final expectedLength = await getCachedContentLength(url);if (expectedLength == null) return false;// 获取本地缓存文件FileInfo? fileInfo = await DefaultCacheManager().getFileFromCache(url);final file = fileInfo?.file;if (file == null || !file.existsSync()) return false;final localLength = file.lengthSync();bool isSame = (localLength == expectedLength);print("video_widget 是否下载完成:$isSame");return isSame;}Future<bool> isVideoPlayable(String filePath) async {final controller = VideoPlayerController.file(File(filePath));try {await controller.initialize();await controller.dispose();print("video_widget 可以 正常播放");return true;} catch (e) {print("video_widget 不能 正常播放");return false;}}@overrideWidget build(BuildContext context) {setSource();return AspectRatio(aspectRatio: 16 / 9,child: MeeduVideoPlayer(key: UniqueKey(),controller: _meeduPlayerController,),);}
}

相关文章:

flutter缓存网络视频到本地,可离线观看

记录一下解决问题的过程&#xff0c;希望自己以后可以参考看看&#xff0c;解决更多的问题。 需求&#xff1a;flutter 缓存网络视频文件&#xff0c;可离线观看。 解决&#xff1a; 1&#xff0c;flutter APP视频播放组件调整&#xff1b; 2&#xff0c;找到视频播放组件&a…...

Kotlin 中 infix 关键字的原理和使用场景

在 Kotlin 中&#xff0c;使用 infix 关键字修饰的函数称为中缀函数&#xff0c;使用是可以省略 . 和 ()&#xff0c;允许以更自然&#xff08;类似自然语言&#xff09;的语法调用函数&#xff0c;这种特性可以使代码更具可读性。 1 infix 的原理 中缀函数必须满足以下条件&…...

c++从入门到精通(五)--异常处理,命名空间,多继承与虚继承

异常处理 栈展开过程&#xff1a; 栈展开过程沿着嵌套函数的调用链不断查找&#xff0c;直到找到了与异常匹配的catch子句为止&#xff1b;也可能一直没找到匹配的catch&#xff0c;则退出主函数后查找过程终止。栈展开过程中的对象被自动销毁。 在栈展开的过程中&#xff0c…...

mock 数据( json-server )

json-server 实现数据 mock 实现步骤&#xff1a; 1. 在项目中安装 json-server npm install -D json-server 2. 准备一个 json 文件 server/data.json {"posts": [{ "id": "1", "title": "a title", "views"…...

Java多线程编程中的常见问题与陷阱汇总

线程安全问题 多线程环境下&#xff0c;多个线程同时访问共享资源时&#xff0c;可能会导致数据不一致或程序行为异常。常见的线程安全问题包括竞态条件、死锁、活锁等。 public class Counter {private int count 0;public void increment() {count;}public int getCount()…...

ARP Detection MAC-Address Static

一、ARP Detection&#xff08;ARP检测&#xff09; ✅ 定义&#xff1a; ARP检测是一种防止ARP欺骗攻击的安全机制。它通过监控或验证网络中的ARP报文&#xff0c;来判断是否存在伪造的ARP信息。 &#x1f50d; 工作原理&#xff1a; 网络设备&#xff08;如交换机&#xf…...

gcc/g++常用参数

1.介绍 gcc用于编译c语言&#xff0c;g用于编译c 源代码生成可执行文件过程&#xff0c;预处理-编译-汇编-链接。https://zhuanlan.zhihu.com/p/476697014 2.常用参数说明 2.1编译过程控制 参数作用-oOutput&#xff0c;指定输出名字-cCompile&#xff0c;编译源文件生成对…...

nginx配置之负载均衡

版权声明&#xff1a;原创作品&#xff0c;请勿转载&#xff01; 1.实验环境准备 准备3台linux服务器&#xff08;ubuntu和centos均可&#xff0c;本文使用centos7.9&#xff09;&#xff0c;两台web和一台负载均衡服务器&#xff0c;均安装nginx服务 主机名IP软件lb0110.0.0…...

相机Camera日志分析之十一:高通相机Camx hal预览1帧logcat日志process_capture_result详解

【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:高通相机Camx 日志分析之五:camx hal预览1帧logcat日志process_capture_request详解 这一篇我们开始讲: 高通相机Camx 日志分析之十一:camx hal预览1帧logcat日志process_capture_result详解,这里我…...

Python函数库调用实战:以数据分析为例

一、引言 Python之所以在编程领域广受欢迎&#xff0c;很大程度上得益于其丰富且强大的函数库。这些函数库涵盖了从数据分析、科学计算到Web开发、机器学习等众多领域&#xff0c;极大地提高了开发效率。本文将以数据分析为例&#xff0c;介绍如何调用Python的一些常用函数库。…...

去年开发一款鸿蒙Next Os的window工具箱

持拖载多个鸿蒙应用 批量签名安装 运行 http://dl.lozn.top/lozn/HarmonySignAndFileManagerTool_2024-11-26.zip 同类型安卓工具箱以及其他软件下载地址汇总 http://dl.lozn.top/lozn/ 怎么个玩法呢&#xff0c;比如要启动某app, 拖载识别到包名 点启动他能主动读取包名 然后…...

顶层设计-IM系统架构

一、系统总体架构概览 即时通讯&#xff08;IM&#xff09;系统的核心目标&#xff0c;是让用户可以随时随地稳定地发送和接收消息。为了支撑成千上万用户同时在线交流&#xff0c;我们需要将整个系统划分成多个专职模块&#xff0c;每个模块只负责一件事情&#xff0c;彼此协同…...

信任的进阶:LEI与vLEI协同推进跨境支付体系变革

在全球经济版图加速重构的背景下&#xff0c;跨境支付体系正经历着前所未有的变革。2022年全球跨境支付规模突破150万亿美元&#xff0c;但平均交易成本仍高达6.04%&#xff0c;支付延迟超过2.7天。 这种低效率背后&#xff0c;隐藏着复杂的身份识别困境&#xff1a;超过40%的…...

安全性(三):信息安全的五要素及其含义

五要素及其含义 序号要素英文缩写含义说明1保密性Confidentiality仅授权用户才能访问信息&#xff0c;防止信息被非法获取或泄露&#xff08;例如&#xff1a;加密、访问控制&#xff09;2完整性Integrity信息在传输、存储和处理过程中保持准确、完整、未被篡改&#xff08;例…...

PHP 与 面向对象编程(OOP)

PHP 是一种支持面向对象编程&#xff08;OOP&#xff09;的多范式语言&#xff0c;但其面向对象特性是逐步演进而非原生设计。以下是关键分析&#xff1a; 1. PHP 对面向对象编程的支持 核心 OOP 特性&#xff1a; 类和对象&#xff1a; PHP 支持通过 class 关键字定义类&…...

Axios全解析:从基础到高级实战技巧

目录 核心特性 安装配置 基础使用 高级功能 错误处理 拦截器机制 请求取消 TypeScript支持 最佳实践 常见问题 1. 核心特性 1.1 核心优势 全平台支持&#xff1a;浏览器 & Node.js 双环境 自动转换&#xff1a;JSON数据自动序列化 拦截器系统&#xff1a;请求…...

EXO 可以将 Mac M4 和 Mac Air 连接起来,并通过 Ollama 运行 DeepSeek 模型

EXO 可以将 Mac M4 和 Mac Air 连接起来&#xff0c;并通过 Ollama 运行 DeepSeek 模型。以下是具体实现方法&#xff1a; 1. EXO 的分布式计算能力 EXO 是一个支持 分布式 AI 计算 的开源框架&#xff0c;能够将多台 Mac 设备&#xff08;如 M4 和 Mac Air&#xff09;组合成…...

数据库故障排查指南

数据库连接问题 检查数据库服务是否正常运行&#xff0c;确认网络连接是否畅通&#xff0c;验证数据库配置文件的正确性&#xff0c;确保用户名和密码无误。 性能问题 分析慢查询日志&#xff0c;优化SQL语句&#xff0c;检查索引使用情况&#xff0c;调整数据库参数配置&am…...

RBTree的模拟实现

1&#xff1a;红黑树的概念 红⿊树是⼀棵⼆叉搜索树&#xff0c;他的每个结点增加⼀个存储位来表⽰结点的颜⾊&#xff0c;可以是红⾊或者⿊⾊。通过对任何⼀条从根到叶⼦的路径上各个结点的颜⾊进⾏约束&#xff0c;红⿊树确保没有⼀条路径会⽐其他路径⻓出2倍&#xff0c;因…...

docker-compose——安装mongo

编写docker-compose.yml version : 3.8services:zaomeng-mongodb:container_name: zaomeng-mongodbimage: mongo:latestrestart: alwaysports:- 27017:27017environment:- MONGO_INITDB_ROOT_USERNAMEroot- MONGO_INITDB_ROOT_PASSWORDpssw0rdvolumes:- ./mongodb/data:/data/…...

Vue 3.0中响应式依赖和更新

响应式依赖和更新是Vue 3.0中最重要的机制&#xff0c;其核心代码如下&#xff0c;本文将结合代码对这个设计机制作出一些解释。 // 全局依赖存储&#xff1a;WeakMap<target, Map<key, Set<effect>>> const targetMap new WeakMap();// 当前活动的副作用函…...

uniapp|实现获取手机摄像头权限,调用相机拍照实现人脸识别相似度对比,拍照保存至相册,多端兼容(APP/微信小程序)

基于uniapp以及微信小程序实现移动端人脸识别相似度对比,实现摄像头、相册权限获取、相机模块交互、第三方识别集成等功能,附完整代码。 目录 核心功能实现流程摄像头与相册权限申请权限拒绝后的引导策略摄像头调用拍照事件处理人脸识别集成图片预处理(Base64编码/压缩)调用…...

JavaScript【7】BOM模型

1.概述&#xff1a; BOM&#xff08;Browser Object Model&#xff0c;浏览器对象模型&#xff09;是 JavaScript 中的一个重要概念&#xff0c;它提供了一系列对象来访问和操作浏览器的功能和信息。与 DOM&#xff08;Document Object Model&#xff09;主要关注文档结构不同&…...

[强化学习的数学原理—赵世钰老师]学习笔记02-贝尔曼方程

本人为强化学习小白&#xff0c;为了在后续科研的过程中能够较好的结合强化学习来做相关研究&#xff0c;特意买了西湖大学赵世钰老师撰写的《强化学习数学原理》中文版这本书&#xff0c;并结合赵老师的讲解视频来学习和更深刻的理解强化学习相关概念&#xff0c;知识和算法技…...

使用Spring Boot与Spring Security构建安全的RESTful API

使用Spring Boot与Spring Security构建安全的RESTful API 引言 在现代Web应用开发中&#xff0c;安全性是不可忽视的重要环节。Spring Boot和Spring Security作为Java生态中的主流框架&#xff0c;为开发者提供了强大的工具来构建安全的RESTful API。本文将详细介绍如何结合S…...

深入理解构造函数,析构函数

目录 1.引言 2.构造函数 1.概念 2.特性 3.析构函数 1.概念 2.特性 1.引言 如果一个类中什么都没有&#xff0c;叫作空类. class A {}; 那么我们这个类中真的是什么都没有吗?其实不是,如果我们类当中上面都不写.编译器会生成6个默认的成员函数。 默认成员函数:用户没有显…...

Day 16

目录 1.JZ79 判断是不是平衡二叉树1.1 解析1.2 代码 2.DP10 最大子矩阵2.1 解析2.2 代码 1.JZ79 判断是不是平衡二叉树 JZ79 判断是不是平衡二叉树 dfs 1.1 解析 1.2 代码 /*** struct TreeNode {* int val;* struct TreeNode *left;* struct TreeNode *right;* TreeNode(in…...

摄影构图小节

1、三分构图法 三分构图法即将画面横竖各分为三份&#xff0c;即九宫格形式。 将画面用两条竖线和两条横线分割&#xff0c;就如同是书写中文的【井】字。这样就可以得到4个交叉点&#xff0c;然后再将需要表现的重点放置在4个交叉点中的一个附近即可。 拍摄自然风光时&#xf…...

DAY 28 类的定义和方法

知识点回顾&#xff1a; 类的定义pass占位语句类的初始化方法类的普通方法类的继承&#xff1a;属性的继承、方法的继承 比如def、class这些定义的关键词后&#xff0c;必须有一个有占据缩进位置的代码块。 还有下面这些依赖缩进的语句&#xff0c;都可以用pass语句来占位 x 1…...

RAG数据处理:PDF/HTML

RAG而言用户输入的数据通常是各种各样文档&#xff0c;本文主要采用langchain实现PDF/HTML文档的处理方法 PDF文档解析 PDF文档很常见格式&#xff0c;但内部结构常常较复杂&#xff1a; 复杂的版式布局多样的元素&#xff08;段落、表格、公式、图片等&#xff09;文本流无…...