iOS IdiotAVplayer实现视频分片缓存
文章目录
- IdiotAVplayer 实现视频切片缓存
- 一 iOS视频边下边播原理
- 一 分片下载的实现
- 1 分片下载的思路
- 2 IdiotAVplayer 实现架构
- 三 IdiotAVplayer 代码解析
- IdiotPlayer
- IdiotResourceLoader
- IdiotDownLoader
IdiotAVplayer 实现视频切片缓存
一 iOS视频边下边播原理
初始化AVURLAsset 的时候,将资源链接中的http替换成其他字符串,并且将AVURLAsset的resourceLoader 设置代理对象,然后该代理对象实现AVAssetResourceLoaderDelegate 的代理方法
#pragma mark - AVAssetResourceLoaderDelegate
- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest {return YES;
}- (void)resourceLoader:(AVAssetResourceLoader *)resourceLoader didCancelLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest {
}
在代理方法中实现资源的下载,保存, 并将下载好的资源塞给 loadingRequest, 实现视频的播放
一 分片下载的实现
简单的实现方案,就是将一个视频从头开始下载,或者从当前下载到的位置开始下载,然后下载到结束 这种方案对于短视频是可以的,因为短视频总共也没有多大,即使我们快进,从头下载开始到快进的地方也没有多少流量,用户体验影响不大,但是仍然浪费了中间的流量。
如果一个视频比较大,用户进行快进操作的话,从开头下载到用户快进的地方需要的时间很长,这时候,如果能根据用户快进的进度,根据用户的需要进行资源下载,那就是一个好的方案了。
1 分片下载的思路
步骤
1 首先根据链接获取本地资源
2 根据获取到的本地资源和视频请求request对比,计算需要新下载的资源 片段。
3 将本地的资源或者下载好的资源分片塞给请求对象request
2 IdiotAVplayer 实现架构
IdiotAVPlayer 负责实现视频播放功能
IdiotResourceLoader
负责实现
AVAssetResourceLoaderDelegate代理 方法,
负责将数据塞给AVAssetResourceLoadingRequest 请求,并管理AVAssetResourceLoadingRequest 请求,添加,移除,塞数据,快进的处理
IdiotDownLoader 负责 资源片段的获取,需要下载的片段的计算
NSURLSessionDelegate 代理方法的实现,并将下载好的数据传给IdiotResourceLoader, 还负责在读取本地数据的时候,将占用内存较大的视频资源分片读取到内存中传给 IdiotResourceLoader,避免造成因为资源较大而产生的内存撑爆问题
IdiotFileManager 负责管理下载的资源
三 IdiotAVplayer 代码解析
创建播放器,并设置resouceLoader代理
IdiotPlayer
_resourceLoader = [[IdiotResourceLoader alloc] init];_resourceLoader.delegate = self;AVURLAsset * playerAsset = [AVURLAsset URLAssetWithURL:[_currentUrl idiotSchemeURL] options:nil];[playerAsset.resourceLoader setDelegate:_resourceLoader queue:_queue];_playerItem = [AVPlayerItem playerItemWithAsset:playerAsset];
IdiotResourceLoader
- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest {[self addLoadingRequest:loadingRequest];DLogDebug(@"loadingRequest == %@",loadingRequest)return YES;
}- (void)resourceLoader:(AVAssetResourceLoader *)resourceLoader didCancelLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest {[self removeLoadingRequest:loadingRequest];
}- (void)removeLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest {dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);NSArray * temptaskList = [NSArray arrayWithArray:self.taskList];dispatch_semaphore_signal(semaphore);IdiotResourceTask * deleteTask = nil;for (IdiotResourceTask * task in temptaskList) {if ([task.loadingRequest isEqual:loadingRequest]) {deleteTask = task;break;}}if (deleteTask) {dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);[self.taskList removeObject:deleteTask];dispatch_semaphore_signal(semaphore);}}- (void)addLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest {if (self.currentResource) {if (loadingRequest.dataRequest.requestedOffset >= self.currentResource.requestOffset &&loadingRequest.dataRequest.requestedOffset <= self.currentResource.requestOffset + self.currentResource.cacheLength) {IdiotResourceTask * task = [[IdiotResourceTask alloc] init];task.loadingRequest = loadingRequest;task.resource = self.currentResource;dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);[self.taskList addObject:task];dispatch_semaphore_signal(semaphore);[self processRequestList];}else{if (self.seek) {[self newTaskWithLoadingRequest:loadingRequest];}else{IdiotResourceTask * task = [[IdiotResourceTask alloc] init];task.loadingRequest = loadingRequest;task.resource = self.currentResource;NSLog(@"哈哈哈哈哈啊哈哈这里这里这里添加22222 %lld %lld %p\n", loadingRequest.dataRequest.requestedOffset, loadingRequest.dataRequest.currentOffset, task);dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);[self.taskList addObject:task];dispatch_semaphore_signal(semaphore);}}}else {[self newTaskWithLoadingRequest:loadingRequest];}
}- (void)newTaskWithLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest {long long fileLength = 0;if (self.currentResource) {fileLength = self.currentResource.fileLength;self.currentResource.cancel = YES;}IdiotResource * resource = [[IdiotResource alloc] init];resource.requestURL = loadingRequest.request.URL;resource.requestOffset = loadingRequest.dataRequest.requestedOffset;resource.resourceType = IdiotResourceTypeTask;if (fileLength > 0) {resource.fileLength = fileLength;}IdiotResourceTask * task = [[IdiotResourceTask alloc] init];task.loadingRequest = loadingRequest;task.resource = resource;self.currentResource = resource;dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);[self.taskList addObject:task];dispatch_semaphore_signal(semaphore);printf("哈哈哈这里事创建的这里事创建的%lld %lld %lld %p %p\n", resource.requestOffset, loadingRequest.dataRequest.requestedOffset, loadingRequest.dataRequest.currentOffset, loadingRequest, task);[IdiotDownLoader share].delegate = self;[[IdiotDownLoader share] start:self.currentResource];self.seek = NO;
}- (void)stopResourceLoader{[[IdiotDownLoader share] cancel];
}- (void)processRequestList {@synchronized (self) {dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);NSArray * temptaskList = [NSArray arrayWithArray:self.taskList];dispatch_semaphore_signal(semaphore);for (IdiotResourceTask * task in temptaskList) {NSInvocationOperation * invoke = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(finishLoadingWithLoadingRequest:) object:task];[_playQueue addOperation:invoke];}}
}- (void)finishLoadingWithLoadingRequest:(IdiotResourceTask *)task {//填充信息task.loadingRequest.contentInformationRequest.contentType = @"video/mp4";task.loadingRequest.contentInformationRequest.byteRangeAccessSupported = YES;task.loadingRequest.contentInformationRequest.contentLength = task.resource.fileLength;if (task.resource.fileLength <= 0) {DLogDebug(@"requestTask.fileLength <= 0");}//读文件,填充数据long long cacheLength = task.resource.cacheLength;long long requestedOffset = task.loadingRequest.dataRequest.requestedOffset;if (task.loadingRequest.dataRequest.currentOffset != 0) {requestedOffset = task.loadingRequest.dataRequest.currentOffset;}printf("哈哈哈1111执行执行执行%lld点 %lld 一 %lld %p %p\n", task.loadingRequest.dataRequest.requestedOffset,task.loadingRequest.dataRequest.currentOffset, task.resource.requestOffset, task.loadingRequest, task);printf("哈哈哈数量数量%ld\n", self.taskList.count);for (IdiotResourceTask *task1 in self.taskList) {printf("哈哈哈啦啊啦这里这里数组里的%p %lld\n",task1, task.resource.requestOffset);}if (requestedOffset < task.resource.requestOffset) {printf("哈哈哈1111返回%lld点 %lld 一 %lld %p %p\n", task.loadingRequest.dataRequest.requestedOffset,task.loadingRequest.dataRequest.currentOffset, task.resource.requestOffset, task.loadingRequest, task);return;}long long paddingOffset = requestedOffset - task.resource.requestOffset;long long canReadLength = cacheLength - paddingOffset;printf("哈哈哈能获取到的能获取到的%lld \n", canReadLength);if (canReadLength <= 0) {printf("哈哈哈返回222222 %lld\n", canReadLength);return;}long long respondLength = MIN(canReadLength, task.loadingRequest.dataRequest.requestedLength);NSFileHandle * handle = [IdiotFileManager fileHandleForReadingAtPath:task.resource.cachePath];[handle seekToFileOffset:paddingOffset];[task.loadingRequest.dataRequest respondWithData:[handle readDataOfLength:[[NSNumber numberWithLongLong:respondLength] unsignedIntegerValue]]];printf("哈哈哈匹配到匹配到%lld \n",respondLength);[handle closeFile];//如果完全响应了所需要的数据,则完成long long nowendOffset = requestedOffset + canReadLength;long long reqEndOffset = task.loadingRequest.dataRequest.requestedOffset + task.loadingRequest.dataRequest.requestedLength;printf("哈哈哈差别差别%lld\n",reqEndOffset - nowendOffset);if (nowendOffset >= reqEndOffset) {[task.loadingRequest finishLoading];printf("哈哈哈移除移除移除%lld %lld\n", nowendOffset, reqEndOffset);[self removeLoadingRequest:task.loadingRequest];return;}}#pragma mark - DownLoaderDataDelegate
- (void)didReceiveData:(IdiotDownLoader *__weak)downLoader{[self processRequestList];if (self.delegate&&[self.delegate respondsToSelector:@selector(didCacheProgressChange:)]) {__weak typeof(self) weakself = self;dispatch_async(dispatch_get_main_queue(), ^{__strong typeof(weakself) strongself = weakself;NSMutableArray * caches = [downLoader.resources mutableCopy];[caches addObject:self.currentResource];[strongself.delegate didCacheProgressChange:caches];});}}
下面单独介绍各个方法的实现
if (self.currentResource) {if (loadingRequest.dataRequest.requestedOffset >= self.currentResource.requestOffset &&loadingRequest.dataRequest.requestedOffset <= self.currentResource.requestOffset + self.currentResource.cacheLength) {IdiotResourceTask * task = [[IdiotResourceTask alloc] init];task.loadingRequest = loadingRequest;task.resource = self.currentResource;dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);[self.taskList addObject:task];dispatch_semaphore_signal(semaphore);[self processRequestList];}else{if (self.seek) {[self newTaskWithLoadingRequest:loadingRequest];}else{IdiotResourceTask * task = [[IdiotResourceTask alloc] init];task.loadingRequest = loadingRequest;task.resource = self.currentResource;dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);[self.taskList addObject:task];dispatch_semaphore_signal(semaphore);}}}else {[self newTaskWithLoadingRequest:loadingRequest];}
}
上面方法中,的判断条件 self.currentResource 说明执行过newTaskWithLoadingRequest 方法了,因为在该方法中设置了self.currentResource,说明就不是第一次执行addLoadingRequest 添加request了,loadingRequest.dataRequest.requestedOffset >= self.currentResource.requestOffset &&
loadingRequest.dataRequest.requestedOffset <= self.currentResource.requestOffset + self.currentResource.cacheLength 该判断条件说明
新请求的offset 是大于当前的offset, 但是小于当前的offset + cachelength ,说明
当前的的本地资源是有一部分是可以塞给当前的 request的 ,所以在创建了新的任务task的同时,还执行了 [self processRequestList];
方法。下面的 else中 if (self.seek) 说明当前的request是因为用户拖拽进度条触发的,所以要重新创建一个source ,因为一个拖拽就会引起一个不连续的下载片段,而在IdiotAvplayer的设计中,每一个资源片段都要有一个resouce,
所以要执行newTaskWithLoadingRequest 方法
else说明不是拖拽的,则直接添加新的任务即可,等到新的下载好的资源到来,就会去塞给新添加的请求,而新的下载是不会停止的,直到到达资源的最后。
- (void)finishLoadingWithLoadingRequest:(IdiotResourceTask *)task {//填充信息task.loadingRequest.contentInformationRequest.contentType = @"video/mp4";task.loadingRequest.contentInformationRequest.byteRangeAccessSupported = YES;task.loadingRequest.contentInformationRequest.contentLength = task.resource.fileLength;if (task.resource.fileLength <= 0) {DLogDebug(@"requestTask.fileLength <= 0");}//读文件,填充数据long long cacheLength = task.resource.cacheLength;long long requestedOffset = task.loadingRequest.dataRequest.requestedOffset;if (task.loadingRequest.dataRequest.currentOffset != 0) {requestedOffset = task.loadingRequest.dataRequest.currentOffset;}if (requestedOffset < task.resource.requestOffset) {/*task.resource 是第一次播放或者拖拽才会创建的对象,其 requestOffset就是对应的那次请求的offset,这里的判断条件 requestedOffset < task.resource.requestOffset 说明 该request是 创建 resouce 之前的request,那么该resouce 对应的资源中满足该request,所以就返回*/ return;}long long paddingOffset = requestedOffset - task.resource.requestOffset;long long canReadLength = cacheLength - paddingOffset;if (canReadLength <= 0) {如果该resouce offset+ resouce的资源长度,仍然小与request 的offset,说明该资源完全在request的前面,无法满足该request,返回return;}long long respondLength = MIN(canReadLength, task.loadingRequest.dataRequest.requestedLength);NSFileHandle * handle = [IdiotFileManager fileHandleForReadingAtPath:task.resource.cachePath];[handle seekToFileOffset:paddingOffset];[task.loadingRequest.dataRequest respondWithData:[handle readDataOfLength:[[NSNumber numberWithLongLong:respondLength] unsignedIntegerValue]]];[handle closeFile];//如果完全响应了所需要的数据,则完成long long nowendOffset = requestedOffset + canReadLength;long long reqEndOffset = task.loadingRequest.dataRequest.requestedOffset + task.loadingRequest.dataRequest.requestedLength;if (nowendOffset >= reqEndOffset) {[task.loadingRequest finishLoading];[self removeLoadingRequest:task.loadingRequest];return;}}
如下图,分片缓存的资源在沙盒中的保存形式,是根据offset 分别保存的

IdiotDownLoader
- (void)start:(IdiotResource *)task {if (self.currentDataTask) {[self.currentDataTask cancel];}[self.taskDic setObject:task forKey:[NSString stringWithFormat:@"%zd",task.requestOffset]];//获取本地资源BOOL refresh = NO;while (!self.writing&&!refresh) {self.resources = [IdiotFileManager getResourceWithUrl:task.requestURL];refresh = YES;}IdiotResource * resource = nil;//找出对应的资源if (!self.resources.count) {//本地无资源resource = [[IdiotResource alloc] init];resource.requestURL = task.requestURL;resource.requestOffset = task.requestOffset;resource.fileLength = task.fileLength;resource.cachePath = task.cachePath;resource.cacheLength = 0;resource.resourceType = IdiotResourceTypeNet;//网络资源[self.resources addObject:resource];}else{//本地有资源for (IdiotResource * obj in self.resources) {if (task.requestOffset >= obj.requestOffset&&task.requestOffset < obj.requestOffset+obj.cacheLength) {/*该判断条件说明当前任务offset比获取的本地分片资源offset大,比本地分片资源offset+cachelength小,在本地资源中间,有重合的地方*/resource = obj;break;}}if (task.requestOffset > resource.requestOffset&&resource.resourceType == IdiotResourceTypeNet) {/*该resouce 是从上面的判断条件中获取的该判断说明当前任务比获取到的本地resouce offset大,并且是网路请求资源,说明本地没有资源,需要重新下载,这里新建一个IdiotResource,并且设置offset=task.offset就是为了从当前任务的offset开始下载,否则会中本得resouce 的offset开始下载,这样就会导致下载的比我们需要的多,并且用户会有一个卡住的体验,因为下载的不是用户需要的offset,这里这样写,保证下载的offset就是用户需要的,并且避免流量浪费 */long long adjustCacheLength = task.requestOffset - resource.requestOffset;IdiotResource * net = [[IdiotResource alloc] init];net.requestURL = task.requestURL;net.requestOffset = task.requestOffset;net.fileLength = task.fileLength;net.cachePath = task.cachePath;net.cacheLength = resource.cacheLength - adjustCacheLength;net.resourceType = IdiotResourceTypeNet;//网络资源resource.cacheLength = adjustCacheLength;NSInteger index = [self.resources indexOfObject:resource]+1;[self.resources insertObject:net atIndex:index];resource = net;}}self.currentResource = resource;[self fetchDataWith:task Resource:self.currentResource];}
- (void)fetchFromLocal:(IdiotResource *)sliceRequest withResource:(IdiotResource *)resource{if (sliceRequest.requestOffset == resource.requestOffset) {sliceRequest.cachePath = resource.cachePath;sliceRequest.fileLength = resource.fileLength;sliceRequest.cacheLength = resource.cacheLength;//直接开始下一个资源获取if (self.delegate && [self.delegate respondsToSelector:@selector(didReceiveData:)]) {[self.delegate didReceiveData:self];}[self willNextResource:sliceRequest];return;}NSFileHandle * readHandle = [IdiotFileManager fileHandleForReadingAtPath:resource.cachePath];unsigned long long seekOffset = sliceRequest.requestOffset < resource.requestOffset?0:sliceRequest.requestOffset-resource.requestOffset;[readHandle seekToFileOffset:seekOffset];//文件过大可分次读取long long canReadLength = resource.cacheLength-seekOffset;NSUInteger bufferLength = 5242880; //长度大于5M分次返回数据/*如果本地资源比较大,就分片塞数据,如果一下将整个资源读取到内存中,就会造成内存撑爆,导致严重的卡顿*/while (canReadLength >= bufferLength) {//长度大于1M分次返回数据canReadLength -= bufferLength;NSData * responseData = [readHandle readDataOfLength:bufferLength];[self didReceiveLocalData:responseData requestTask:sliceRequest complete:canReadLength==0?YES:NO];}if (canReadLength != 0) {NSData * responseData = [readHandle readDataOfLength:[[NSNumber numberWithLongLong:canReadLength] unsignedIntegerValue]];[readHandle closeFile];[self didReceiveLocalData:responseData requestTask:sliceRequest complete:YES];}else{[readHandle closeFile];}}相关文章:
iOS IdiotAVplayer实现视频分片缓存
文章目录 IdiotAVplayer 实现视频切片缓存一 iOS视频边下边播原理一 分片下载的实现1 分片下载的思路2 IdiotAVplayer 实现架构 三 IdiotAVplayer 代码解析IdiotPlayerIdiotResourceLoaderIdiotDownLoader IdiotAVplayer 实现视频切片缓存 一 iOS视频边下边播原理 初始化AVUR…...
SpringBootWeb请求-响应
HTTP请求 前后端分离 在这种模式下,前端技术人员基于"接口文档",开发前端程序;后端技术人员也基于"接口文档",开发后端程序。 由于前后端分离,对我们后端技术人员来讲,在开发过程中&a…...
List集合详解
目录 1、集合是什么? 1.1、集合与集合之间的关系 2、List集合的特点 3、遍历集合的三种方式 3.1、foreach(增强佛如循环遍历) 3.2、for循环遍历 3.3、迭代器遍历 4、LinkedList和ArrayList的区别 4.1、为什么ArrayList查询会快一些? 4.2、为什么LinkedLi…...
投稿指南【NO.12_8】【极易投中】核心期刊投稿(组合机床与自动化加工技术)
近期有不少同学咨询投稿期刊的问题,大部分院校的研究生都有发学术论文的要求,少部分要求高的甚至需要SCI或者多篇核心期刊论文才可以毕业,但是核心期刊要求论文质量高且审稿周期长,所以本博客梳理一些计算机特别是人工智能相关的期…...
解决git无法上传大文件(50MB)
解决方法 使用LFS解决GitHub无法上传大于50MB的文件 LFS简介 Git LFS(Large File Storage)是 Git 的一个扩展,用于管理大型文件,如二进制文件、图像、音频和视频文件等。它的主要目的是解决 Git 对大型二进制文件的版本控制和存…...
用递归实现字符串逆序(不使用库函数)
文章目录 前言一、题目要求二、解题步骤1.大概框架2.如何反向排列?3.模拟实现strlen4.实现反向排列5.递归实现反向排列 总结 前言 嗨,亲爱的读者们!我是艾老虎尤,今天,我们将探索一个题目,这个题目对新手非…...
初学python(一)
一、python的背景和前景 二、 python的一些小事项 1、在Java、C中,2 / 3 0,也就是整数 / 整数 整数,会把小数部分舍掉。而在python中2 / 3 0.66666.... 不会舍掉小数部分。 在编程语言中,浮点数遵循IEEE754标准,不…...
Excel VSTO开发8 -相关控件
版权声明:本文为博主原创文章,转载请在显著位置标明本文出处以及作者网名,未经作者允许不得用于商业目的。 8 相关控件 在VSTO开发中,Ribbon(或称为Ribbon UI)是指Office应用程序中的那个位于顶部的带有选…...
华为数据管理——《华为数据之道》
数据分析与开发 元数据是描述数据的数据,用于打破业务和IT之间的语言障碍,帮助业务更好地理解数据。 元数据是数据中台的重要的基础设施,元数据治理贯彻数据产生、加工、消费的全过程,沉淀了数据资产,搭建了技术和业务…...
Flink CDC 菜鸟教程 -环境篇
本教程将介绍如何使用 Flink CDC 来实现这个需求, 在 Flink SQL CLI 中进行,只涉及 SQL,无需一行 Java/Scala 代码,也无需安装 IDE。 系统的整体架构如下图所示: 环境篇 1、 准备一台Linux 2、准备教程所需要的组件 下载 flink-1.13.2 并将其解压至目录 flink-1.13.2 …...
【线上问题】linux部署docker应用docker-compose启动报端口占用问题(感觉上没有被占用)
目录 一、问题说明二、排查过程 一、问题说明 1.linux服务器使用的不是root用户权限 2.docker应用服务没有关闭的情况下,做了些重装docker,重启docker等操作 3.docker-compose up -d然后docker logs查看日志报端口被占用 4.netstat -ntpl | grep 端口 也…...
解决虚拟机克隆后IP和命名冲突问题
目录 解决IP冲突问题 解决命名冲突 解决IP冲突问题 克隆后的虚拟机和硬件地址和ip和我们原虚拟机的相同,我们需要重新生成硬件地址和定义ip,步骤如下: (1)进入 /etc/sysconfig/network-scripts/ifcfg-ens33 配置文件…...
分享一个python基于数据可视化的智慧社区服务平台源码
💕💕作者:计算机源码社 💕💕个人简介:本人七年开发经验,擅长Java、Python、PHP、.NET、Node.js、微信小程序、爬虫、大数据等,大家有这一块的问题可以一起交流! …...
[密码学入门]凯撒密码
单表代换 单表:英文26字母的顺序 代换:替换为别的字母并保证解密的唯一性 假如我们让加密方式为所有字母顺序移动3位 import stringstring.ascii_lowercase abcdefghijklmnopqrstuvwxyz b3 加密算法y(xb)mod26 解密算法为x(y-b)mod26 密钥空间26 …...
博客之QQ登录功能(一)
流程图 上图spring social 封装了1-8步需要的工作 1、新建包和书写配置文件 public class QQProperties {//App唯一标 识private String appId "100550231";private String appSecret "69b6ab57b22f3c2fe6a6149274e3295e";//QQ供应商private String…...
Redis多机数据库实现
Redis多机数据库实现 为《Redis设计与实现》笔记 复制 客户端可以使用SLAVEOF命令将指定服务器设置为该服务器的主服务器 127.0.0.1:12345> SLAVEOF 127.0.0.1 6379127.0.0.1:6379将被设置为127.0.0.1:123456的主服务器 旧版复制功能的实现 Redis的复制功能分为同步&a…...
Leangoo领歌 -敏捷任务管理软件,任务管理更轻松更透明
任务管理,简单易懂,就是对任务进行管理。那怎么可以更好进行任务管理呢?怎么样样可以让任务进度可视化,一目了然呢?有效的管理可以让我们事半功倍。 接下来我们看一下如何借助任务管理软件高效的做任务管理。 首先…...
go的iris框架进行本地资源映射到服务端
我这里使用的是HandleDirapi,有其他的请补充 package mainimport ("github.com/kataras/iris/v12" )type Hello struct{Status int json:"status"Message string json:"message" }func main(){app : iris.New()//第一个api:相当于首页app.Get(&q…...
代码随想录day46|139. 单词拆分
139. 单词拆分 class Solution:def wordBreak(self, s: str, wordDict: List[str]) -> bool:dp [False]*(len(s)1)dp[0]Truefor i in range(len(s)1):for j in wordDict:if i>len(j) and (s[i-len(j):i] in wordDict) and dp[i-len(j)]:dp[i] Truereturn dp[len(s)]多…...
MATLAB实现函数拟合
目录 一.理论知识 1.拟合与插值的区别 2.几何意义 3.误差分析 二.操作实现 1.数据准备 2.使用cftool——拟合工具箱 三.函数拟合典例 四.代码扩展 一.理论知识 1.拟合与插值的区别 通俗的说,插值的本质是根据现有离散点的信息创建出更多的离散点…...
LangGraph Platform本地部署实战:用Docker和CLI快速搭建你的第一个AI Agent微服务
LangGraph Platform本地部署实战:从开发到生产的AI Agent微服务架构 在AI应用开发领域,快速将原型转化为可部署的服务是每个开发者面临的挑战。LangGraph Platform作为LangChain生态中的工作流编排工具,其本地部署能力为开发者提供了从开发环…...
Qwen3-32B-Chat镜像深度优化:OpenClaw任务执行效率提升30%
Qwen3-32B-Chat镜像深度优化:OpenClaw任务执行效率提升30% 1. 为什么需要深度优化? 去年冬天,当我第一次在本地部署OpenClaw对接Qwen3-32B模型时,遇到了一个尴尬的问题:一个简单的"截图识别鼠标点击"任务链…...
解决90%部署难题:TVM模型序列化全流程解析与最佳实践
解决90%部署难题:TVM模型序列化全流程解析与最佳实践 你是否还在为深度学习模型部署时的兼容性问题头疼?当需要将训练好的模型从开发环境迁移到生产服务器,或是在不同硬件设备间移植时,是否经常遇到格式不兼容、性能下降或依赖冲…...
央国企稳岗扩岗新举措解读
近日,国家层面再次强调了就业优先战略的重要性,并推动相关政策措施进一步升级。在这一宏观背景下,中央企业和国有企业作为国民经济的重要支柱,其在稳就业、扩岗位方面的举措备受关注。一系列新的行动方案正陆续出台,旨…...
5分钟搞定!AI股票分析师daily_stock_analysis镜像一键启动与使用教程
5分钟搞定!AI股票分析师daily_stock_analysis镜像一键启动与使用教程 1. 引言 想体验AI帮你分析股票,但又担心数据隐私和复杂的配置流程?今天介绍的这款AI股票分析师镜像,完美解决了这两个痛点。它基于Ollama框架,将…...
无需复杂配置:Ollama一键运行EmbeddingGemma-300m嵌入模型教程
无需复杂配置:Ollama一键运行EmbeddingGemma-300m嵌入模型教程 1. 为什么选择EmbeddingGemma-300m 在当今AI应用蓬勃发展的时代,文本嵌入技术已成为构建智能系统的核心组件。然而,大多数嵌入模型要么体积庞大难以部署,要么性能不…...
RAPIDMP3嵌入式音频模块:UART控制的高保真MP3/WAV协处理器
1. RAPIDMP3 模块深度技术解析:面向嵌入式系统的高保真音频处理方案1.1 模块定位与工程价值RAPIDMP3(即 RAPID_MP3_V1)并非通用音频解码库,而是一款硬件级立体声 MP3 播放与 WAV 录音模块,其核心价值在于将复杂的音频编…...
想拥有专属的桌面宠物伙伴吗?DyberPet开源框架让个性化养成触手可及
想拥有专属的桌面宠物伙伴吗?DyberPet开源框架让个性化养成触手可及 【免费下载链接】DyberPet Desktop Cyber Pet Framework based on PySide6 项目地址: https://gitcode.com/GitHub_Trending/dy/DyberPet 你是否曾希望电脑桌面上能有一个可爱的虚拟伙伴&a…...
OpenClaw隐私保护:百川2-13B本地化部署下的数据全生命周期管理
OpenClaw隐私保护:百川2-13B本地化部署下的数据全生命周期管理 1. 为什么需要关注OpenClaw的隐私保护? 去年我在整理公司财报时,曾不小心把包含敏感数据的Excel表格上传到了公有云AI助手的聊天窗口。虽然及时删除了记录,但那种&…...
Nitrox模组:如何将Subnautica的单人深海恐惧变为团队协作冒险
Nitrox模组:如何将Subnautica的单人深海恐惧变为团队协作冒险 【免费下载链接】Nitrox An open-source, multiplayer modification for the game Subnautica. 项目地址: https://gitcode.com/gh_mirrors/ni/Nitrox 当你第一次潜入4546B行星的海洋时ÿ…...
