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];}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/128532.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

自动化运维——ansible (五十二) (01)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 一、概述 1.1 为什么要用自动化运维软件 1.2 自动化运维 1.3 自动化运维要注意的方面 1.4 自动化运维主要关注的方面 1.5 常见的开源自动化运维软件 1.6 自动化运维软件…

K8S:kubectl陈述式及声明式资源管理

文章目录 一.陈述式资源管理方法1.陈述式资源管理概念2.基本信息查看&#xff08;1&#xff09;查看版本信息&#xff08;2&#xff09;查看资源对象简写&#xff08;3&#xff09;查看集群信息&#xff08;4&#xff09;配置kubectl自动补全&#xff08;5&#xff09;node节点…

外传-Midjourney的局部重绘功能

今天在抄袭。。。啊不&#xff0c;借鉴 midjourney 官网教程的时候&#xff0c;发现多了一个 局部重绘的功能&#xff0c;意外发觉还不错&#xff0c;分享一下用法。 先给大家说一下&#xff0c;我这段时间都在学习 SD&#xff0c;局部重绘是基操&#xff0c;而 MJ 一直是次次…

为什么要使用设计模式,以及使用设计模式的好处

在软件开发中&#xff0c;衡量软件质量只要包含如下指标&#xff1a; 正确性可维护性可读性可扩展性简洁性可测试性健壮性灵活性可复用性 然而&#xff0c;对于一些刚入行的新程序员来说&#xff0c;往往会注意不到上面这些问题&#xff0c;从而产生了一些让人头皮发麻的烂代…

【动态规划刷题 13】最长递增子序列 摆动序列

300. 最长递增子序列 链接: 300. 最长递增子序列 1.状态表示* dp[i] 表⽰&#xff1a;以 i 位置元素为结尾的「所有⼦序列」中&#xff0c;最⻓递增⼦序列的⻓度。 2.状态转移方程 对于 dp[i] &#xff0c;我们可以根据「⼦序列的构成⽅式」&#xff0c;进⾏分类讨论&#…

生成树协议 STP(spanning-tree protocol)

一、STP作用 1、消除环路&#xff1a;通过阻断冗余链路来消除网络中可能存在的环路。 2、链路备份&#xff1a;当活动路径发生故障时&#xff0c;激活备份链路&#xff0c;及时恢复网络连通性。 二、STP选举机制 1、目的&#xff1a;找到阻塞的端口 2、STP交换机的角色&am…

MAC M1芯片安装mounty读写移动硬盘中的文件

因为移动硬盘中的文件是微软公司NTFS格式&#xff0c;MAC只支持自己的APFS或者HFS&#xff0c;与微软的NTFS不兼容&#xff0c;所以需要第三方的软件来支持读写硬盘中的文件&#xff0c;经过一上午的折腾&#xff0c;最终选择安装mounty这个免费的第三方软件 工具网址连接&am…

YOLO目标检测——棉花病虫害数据集+已标注txt格式标签下载分享

实际项目应用&#xff1a;目标检测棉花病虫害数据集的应用场景涵盖了棉花病虫害的识别与监测、研究与防治策略制定、农业智能决策支持以及农业教育和培训等领域。这些应用场景可以帮助农业从业者更好地管理棉花病虫害&#xff0c;提高棉花产量和质量&#xff0c;推动农业的可持…

线性代数的本质(二)

文章目录 线性变换与矩阵线性变换与二阶方阵常见的线性变换复合变换与矩阵乘法矩阵的定义列空间与基矩阵的秩逆变换与逆矩阵 线性变换与矩阵 线性变换与二阶方阵 本节从二维平面出发学习线性代数。通常选用平面坐标系 O x y Oxy Oxy &#xff0c;基向量为 i , j \mathbf i,…

指针进阶(一)

指针进阶 1. 字符指针面试题 2. 指针数组3. 数组指针3.1 数组指针的定义3.2 &数组名VS数组名 3.3 数组指针的使用4. 数组传参和指针传参4.1 一维数组传参4.2 二维数组传参4.3 一级指针传参4.4 二级指针传参 前言 指针的主题&#xff0c;我们在初级阶段的《指针》章节已经接…

用滑动条做调色板---cv2.getTrackbarPos(),cv2.creatTrackbar()

滑动轨迹栏作调色板 cv.createTrackbar(‘R’, ‘image’, 0, 255, nothing) 参数&#xff1a;哪个滑动轨迹栏&#xff0c;哪个窗口&#xff0c;最小值&#xff0c;最大值&#xff0c;回调函数 cv.getTrackbarPos(‘R’, ‘image’) 参数&#xff1a;轨迹栏名&#xff0c;窗口…

Nodejs 第十五章(child_process)

child_process 子进程 子进程是Nodejs核心API&#xff0c;如果你会shell命令&#xff0c;他会有非常大的帮助&#xff0c;或者你喜欢编写前端工程化工具之类的&#xff0c;他也有很大的用处&#xff0c;以及处理CPU密集型应用。 创建子进程 Nodejs创建子进程共有7个API Sync…

controller接口上带@PreAuthorize的注解如何访问 (postman请求示例)

1. 访问接口 /*** 查询时段列表*/RateLimiter(time 10,count 10)ApiOperation("查询时段列表")PreAuthorize("ss.hasPermi(ls/sy:time:list)")GetMapping("/list")public TableDataInfo list(LsTime lsTime){startPage();List<LsTime> l…

【实践篇】Redis最强Java客户端(四)之Ression分布式集合使用指南

文章目录 0. 前言1.Ression分布式集合1.1 分布式列表1.1.1 使用场景和实现原理&#xff1a;1.1.2 基本概念和使用方法&#xff1a; 1.2 分布式集合1.2.1 使用场景和实现原理&#xff1a;1.2.2 基本概念和使用方法&#xff1a; 1.3 分布式映射1.3.1 使用场景和实现原理&#xff…

rv1126之isp黑电平(BLC)校准!

前言&#xff1a; 大家好&#xff0c;今天我们继续来讲解isp第二期内容&#xff0c;这期内容主要分三个部分&#xff1a; 1、tunning的工作流程 2、利用RKISP2.x_Tuner来创建tunning工程&#xff0c;并连接上rv1126开发板进行抓图 3、BLC(黑电平校准)的原理和校准方法以及实战…

Mojo安装使用初体验

一个声称比python块68000倍的语言 蹭个热度&#xff0c;安装试试 系统配置要求&#xff1a; 不支持Windows系统 配置要求: 系统&#xff1a;Ubuntu 20.04/22.04 LTSCPU&#xff1a;x86-64 CPU (with SSE4.2 or newer)内存&#xff1a;8 GiB memoryPython 3.8 - 3.10g or cla…

tcp连接+套接字编程

tcp头部 tcp端口号 TCP的连接是需要四个要素确定唯一一个连接&#xff1a;&#xff08;源IP&#xff0c;源端口号&#xff09; &#xff08;目地IP&#xff0c;目的端口号&#xff09; 所以TCP首部预留了两个16位作为端口号的存储&#xff0c;而IP地址由上一层IP协议负责传递 源…

idea的GsonFormatPlus插件教程

1 安装 插件 打开idea, File—>Setting—>Plugins,搜索 GsonFormatPlus 直接安装 2 json 转化为 实体类 2.1 新建一个类 2.2 点击右键 2.4 点击Format 生成注释

拓展外部SRAM

外部拓展芯片 IS62WV51216A 芯片手册 支持高速时钟通道时间为45、55ns 芯片引脚定义 通道时序 读定义表 一个纵列表示当前使用的高速通道的时间&#xff0c;选一个纵列作为参数标准。 地址控制读时序 如图&#xff0c;大概需要三个参数 写时序定义表 还是选择55ns参数 写…

解决table 操作栏塌陷的问题

1. el-table 塌陷 2. 解决办法 是通过查看官网,看见有一个重新布局的方法 https://element.eleme.cn/#/zh-CN/component/table 3. 代码实现 先将table 绑定ref 调用ref 方法 就ok了