本片为转载内容,主要是以后自己看起来方便一些

原文地址:iOS音视频实现边下载边播放

其实音视频本地缓存的思想都差不多,都需要一个中间对象来连接播放器和服务器。

近段时间制作视频播放社区的功能,期间查找了不少资料,做过很多尝试,现在来整理一下其中遇到的一些坑.由于考虑到AVPlayer对视频有更高自由度的控制,而且能够使用它自定义视频播放界面,iOS中所使用的视频播放控件为AVPlayer,而抛弃了高层次的MediaPlayer框架,现在想想挺庆幸当初使用了AVPlayer。

AVPlayer的基本知识

AVPlayer本身并不能显示视频,而且它也不像MPMoviePlayerController有一个view属性。如果AVPlayer要显示必须创建一个播放器层AVPlayerLayer用于展示,播放器层继承于CALayer,有了AVPlayerLayer之添加到控制器视图的layer中即可。要使用AVPlayer首先了解一下几个常用的类:

AVAsset:主要用于获取多媒体信息,是一个抽象类,不能直接使用。

AVURLAsset:AVAsset的子类,可以根据一个URL路径创建一个包含媒体信息的AVURLAsset对象。

AVPlayerItem:一个媒体资源管理对象,管理者视频的一些基本信息和状态,一个AVPlayerItem对应着一个视频资源。

iOS视频实现边下载边播放的几种实现

1.本地实现http server

在iOS本地开启Local Server服务,然后使用播放控件请求本地Local Server服务,本地的服务再不断请求视频地址获取视频流,本地服务请求的过程中把视频缓存到本地,这种方法在网上有很多例子,有兴趣了解的人可自己下载例子查看。

2.使用AVPlayer的方法开启下载服务

  1. .AVURLAsset *urlAsset = [[AVURLAsset alloc]initWithURL:url options:nil];
  2. .AVPlayerItem *item = [AVPlayerItem playerItemWithAsset:urlAsset];
  3. .[self.avPlayer replaceCurrentItemWithPlayerItem:item];
  4. .[self addObserverToPlayerItem:item];

但由于AVPlayer是没有提供方法给我们直接获取它下载下来的数据,所以我们只能在视频下载完之后自己去寻找缓存视频数据的办法,AVFoundation框架中有一种从多媒体信息类AVAsset中提取视频数据的类AVMutableComposition和AVAssetExportSession。
其中AVMutableComposition的作用是能够从现有的asset实例中创建出一个新的AVComposition(它也是AVAsset的字类),使用者能够从别的asset中提取他们的音频轨道或视频轨道,并且把它们添加到新建的Composition中。
AVAssetExportSession的作用是把现有的自己创建的asset输出到本地文件中。
为什么需要把原先的AVAsset(AVURLAsset)实现的数据提取出来后拼接成另一个AVAsset(AVComposition)的数据后输出呢,由于通过网络url下载下来的视频没有保存视频的原始数据(或者苹果没有暴露接口给我们获取),下载后播放的avasset不能使用AVAssetExportSession输出到本地文件,要曲线地把下载下来的视频通过重构成另外一个AVAsset实例才能输出。代码例子如下:

  1. NSString *documentDirectory = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[];
  2. NSString *myPathDocument = [documentDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.mp4",[_source.videoUrl MD5]]];
  3.  
  4. NSURL *fileUrl = [NSURL fileURLWithPath:myPathDocument];
  5.  
  6. if (asset != nil) {
  7. AVMutableComposition *mixComposition = [[AVMutableComposition alloc]init];
  8. AVMutableCompositionTrack *firstTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
  9. [firstTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, asset.duration) ofTrack:[[asset tracksWithMediaType:AVMediaTypeVideo]objectAtIndex:] atTime:kCMTimeZero error:nil];
  10.  
  11. AVMutableCompositionTrack *audioTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
  12. [audioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, asset.duration) ofTrack:[[asset tracksWithMediaType:AVMediaTypeAudio]objectAtIndex:] atTime:kCMTimeZero error:nil];
  13.  
  14. AVAssetExportSession *exporter = [[AVAssetExportSession alloc]initWithAsset:mixComposition presetName:AVAssetExportPresetHighestQuality];
  15. exporter.outputURL = fileUrl;
  16. if (exporter.supportedFileTypes) {
  17. exporter.outputFileType = [exporter.supportedFileTypes objectAtIndex:] ;
  18. exporter.shouldOptimizeForNetworkUse = YES;
  19. [exporter exportAsynchronouslyWithCompletionHandler:^{
  20.  
  21. }];
  22.  
  23. }
  24. }

3.使用AVAssetResourceLoader回调下载,也是最终决定使用的技术

AVAssetResourceLoader通过你提供的委托对象去调节AVURLAsset所需要的加载资源。而很重要的一点是,AVAssetResourceLoader仅在AVURLAsset不知道如何去加载这个URL资源时才会被调用,就是说你提供的委托对象在AVURLAsset不知道如何加载资源时才会得到调用。所以我们又要通过一些方法来曲线解决这个问题,把我们目标视频URL地址的scheme替换为系统不能识别的scheme,然后在我们调用网络请求去处理这个URL时把scheme切换为原来的scheme。

实现边下边播功能AVResourceLoader的委托对象必须要实现AVAssetResourceLoaderDelegate下五个协议的其中两个:

  1. //在系统不知道如何处理URLAsset资源时回调
  2. - (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest NS_AVAILABLE(10_9, 6_0);
  3. //在取消加载资源后回调
  4. - (void)resourceLoader:(AVAssetResourceLoader *)resourceLoader didCancelLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest NS_AVAILABLE(10_9, 7_0);

以下来说说具体要怎么做处理

第一步,创建一个AVURLAsset,并且用它来初始化一个AVPlayerItem

  1. #define kCustomVideoScheme @"yourScheme"
  2. NSURL *currentURL = [NSURL URLWithString:@"http://***.***.***"];
  3. NSURLComponents *components = [[NSURLComponents alloc]initWithURL:currentURL resolvingAgainstBaseURL:NO];
  4. ////注意,不加这一句不能执行到回调操作
  5. components.scheme = kCustomVideoScheme;
  6. AVURLAsset *urlAsset = [AVURLAsset URLAssetWithURL:components.URL
  7. options:nil];
  8. //_resourceManager在接下来讲述
  9. [urlAsset.resourceLoader setDelegate:_resourceManager queue:dispatch_get_main_queue()];
  10. AVPlayerItem *item = [AVPlayerItem playerItemWithAsset:urlAsset];
  11. _playerItem = item;
  12.  
  13. if (IOS9_OR_LATER) {
  14. item.canUseNetworkResourcesForLiveStreamingWhilePaused = YES;
  15. }
  16. [self.avPlayer replaceCurrentItemWithPlayerItem:item];
  17. self.playerLayer.player = self.avPlayer;
  18. [self addObserverToPlayerItem:item];**

第二步,创建AVResourceManager实现AVResourceLoader协议

  1. @interface AVAResourceLoaderManager : NSObject < AVAssetResourceLoaderDelegate >

第三步,实现两个必须的回调协议,实现中有几件需要做的事情

  1. - (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest
  2. {
  3. //获取系统中不能处理的URL
  4. NSURL *resourceURL = [loadingRequest.request URL];
  5. //判断这个URL是否遵守URL规范和其是否是我们所设定的URL
  6. if ([self checkIsLegalURL:resourceURL] && [resourceURL.scheme isEqualToString:kCustomVideoScheme]){
  7. //判断当前的URL网络请求是否已经被加载过了,如果缓存中里面有URL对应的网络加载器(自己封装,也可以直接使用NSURLRequest),则取出来添加请求,每一个URL对应一个网络加载器,loader的实现接下来会说明
  8. AVResourceLoaderForASI *loader = [self asiresourceLoaderForRequest:loadingRequest];
  9. if (loader == nil){
  10. loader = [[AVResourceLoaderForASI alloc] initWithResourceURL:resourceURL];
  11. loader.delegate = self;
  12. //缓存网络加载器
  13. [self.resourceLoaders setObject:loader forKey:[self keyForResourceLoaderWithURL:resourceURL]];
  14. }
  15. //加载器添加请求
  16. [loader addRequest:loadingRequest];
  17. //返回YES则表明使用我们的代码对AVAsset中请求网络资源做处理
  18. return YES;
  19. }else{
  20. return NO;
  21. }
  22.  
  23. }
  1. - (void)resourceLoader:(AVAssetResourceLoader *)resourceLoader didCancelLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest
  2. {
  3. //如果用户在下载的过程中调用者取消了获取视频,则从缓存中取消这个请求
  4. NSURL *resourceURL = [loadingRequest.request URL];
  5. NSString *actualURLString = [self actualURLStringWithURL:resourceURL];
  6. AVResourceLoaderForASI *loader = [_resourceLoaders objectForKey:actualURLString];
  7. [loader removeRequest:loadingRequest];
  8. }

第四步,判断缓存中是否已下载完视频

  1. - (void)addRequest:(AVAssetResourceLoadingRequest *)loadingRequest
  2. {
  3. //1判断自身是否已经取消加载
  4. if(self.isCancelled==NO){
  5. //2判断本地中是否已经有文件的缓存,如果有,则直接从缓存中读取数据,文件保存和读取这里不做详述,使用者可根据自身情况创建文件系统
  6. AVAResourceFile *resourceFile = [self.resourceFileManager resourceFileWithURL:self.resourceURL];
  7. if (resourceFile) {
  8. //3若本地文件存在,则从文件中获取以下属性
  9. loadingRequest.contentInformationRequest.byteRangeAccessSupported = YES;
  10. //3.1contentType
  11. loadingRequest.contentInformationRequest.contentType = resourceFile.contentType;
  12. //3.2数据长度
  13. loadingRequest.contentInformationRequest.contentLength = resourceFile.contentLength;
  14. //3.3请求的偏移量
  15. long long requestedOffset = loadingRequest.dataRequest.requestedOffset;
  16. //3.4请求总长度
  17. NSInteger requestedLength = loadingRequest.dataRequest.requestedLength;
  18. //3.5取出本地文件中从偏移量到请求长度的数据
  19. NSData *subData = [resourceFile.data subdataWithRange:NSMakeRange(@(requestedOffset).unsignedIntegerValue, requestedLength)];
  20. //3.6返回数据给请求
  21. [loadingRequest.dataRequest respondWithData:subData];
  22. [loadingRequest finishLoading];
  23. }else{
  24. //4如果没有本地文件,则开启网络请求,从网络中获取 ,见第五步
  25. [self startWithRequest:loadingRequest];
  26. }
  27. }
  28. else{
  29. //5如果已经取消请求,并且请求没有完成,则封装错误给请求,可自己实现
  30. if(loadingRequest.isFinished==NO){
  31. [loadingRequest finishLoadingWithError:[self loaderCancelledError]];
  32. }
  33. }
  34. }

第五步,添加loadingRequest到网络文件加载器,这部分的操作比较长

  1. - (void)startWithRequest:(AVAssetResourceLoadingRequest *)loadingRequest
  2. {
  3. //判断当前请求是否已经开启,由于苹果系统原因,会有两次回调到AVResourceLoaderDelegate,我们对其进行判断,只开启一次请求
  4. if (self.dataTask == nil){
  5. //根据loadingRequest中的URL创建NSURLRequest,注意在此把URL中的scheme修改为原先的scheme
  6. NSURLRequest *request = [self requestWithLoadingRequest:loadingRequest];
  7. __weak __typeof(self)weakSelf = self;
  8. //获取url的绝对路径,并使用ASIHttpRequest进行网络请求,下面的请求方法经过封装,就不详说如何对ASI进行封装了,但是每一步需要做的事情能以block的形式更好说明
  9. NSString *urlString = request.URL.absoluteString;
  10. self.dataTask = [self GET:urlString requestBlock:^(Request *req) {
  11. NSLog(@"### %s %@ ###", __func__, req);
  12. //在接受到请求头部信息时,说明链接成功,数据开始传输
  13. if (req.recvingHeader//意思是请求接受到头部信息状态){
  14. NSLog(@"### %s recvingHeader ###", __func__);
  15. __strong __typeof(weakSelf)strongSelf = weakSelf;
  16. if ([urlString isEqualToString:req.originalURL.absoluteString]) {
  17. 4.1//,创建临时数据保存网络下载下来的视频信息
  18. strongSelf.tempData = [NSMutableData data];
  19. }
  20. 4.2//把头部信息内容写入到AVAssetResourceLoadingRequest,即loadingRequest中
  21. [strongSelf processPendingRequests];
  22. }
  23. else if (req.recving//请求接受中状态){
  24. NSLog(@"### %s recving ###", __func__);
  25. __strong __typeof(weakSelf)strongSelf = weakSelf;
  26. //此处需多次调用把请求的信息写入到loadingRequest的步骤,实现下载的过程中数据能输出到loadingRequest播放
  27. if (urlString == req.originalURL.absoluteString) {
  28. 5.1//这个处理是判断此时返回的头部信息是重定向还是实际视频的头部信息,如果是重定向信息,则不作处理
  29. if (!_contentInformation && req.responseHeaders) {
  30. if ([req.responseHeaders objectForKey:@"Location"] ) {
  31. NSLog(@" ### %s redirection URL ###", __func__);
  32. }else{
  33. //5.2如果不是重定向信息,则把需要用到的信息提取出来
  34. _contentInformation = [[RLContentInformationForASI alloc]init];
  35. long long numer = [[req.responseHeaders objectForKey:@"Content-Length"]longLongValue];
  36. _contentInformation.contentLength = numer;
  37. _contentInformation.byteRangeAccessSupported = YES;
  38. _contentInformation.contentType = [req.responseHeaders objectForKey:@"Content-type"];
  39. }
  40. }
  41.  
  42. //5.3开始从请求中获取返回数据
  43. NSLog(@"### %s before tempData length = %lu ###", __FUNCTION__, (unsigned long)self.tempData.length);
  44. strongSelf.tempData = [NSMutableData dataWithData:req.rawResponseData];
  45. NSLog(@"### %s after tempData length = %lu ###",__FUNCTION__, (unsigned long)self.tempData.length);
  46. //5.4把返回数据输出到loadingRequest中
  47. [strongSelf processPendingRequests];
  48. }
  49. }else if (req.succeed){
  50. //请求返回成功,在这里做最后一次把数据输出到loadingRequest,且做一些成功后的事情
  51. NSLog(@"### %s succeed ###", __func__);
  52. NSLog(@"### %s tempData length = %lu ###", __func__, (unsigned long)self.tempData.length);
  53. __strong __typeof(weakSelf)strongSelf = weakSelf;
  54. if (strongSelf) {
  55. [strongSelf processPendingRequests];
  56.  
  57. //保存缓存文件,我在保存文件这里做了一次偷懒,如果有人参考我写的文件可对保存文件作改进,在每次返回数据时把数据追加写到文件,而不是下载成功之后才保存,这请求时也可以使用这个来实现断点重输的功能
  58. AVAResourceFile *resourceFile = [[AVAResourceFile alloc]initWithContentType:strongSelf.contentInformation.contentType date:strongSelf.tempData];
  59. [strongSelf.resourceFileManager saveResourceFile:resourceFile withURL:self.resourceURL];
  60. //在此做一些清理缓存、释放对象和回调到上层的操作
  61. [strongSelf complete];
  62. if (strongSelf.delegate && [strongSelf.delegate respondsToSelector:@selector(resourceLoader:didLoadResource:)]) {
  63. [strongSelf.delegate resourceLoader:strongSelf didLoadResource:strongSelf.resourceURL];
  64. }
  65. }
  66. }else if (req.failed){
  67. //9如果请求返回失败,则向上层抛出错误,且清理缓存等操作
  68. NSLog(@"### %s failed ###" , __func__);
  69. [self completeWithError:req.error];
  70. }
  71. }];
  72. }
  73. [self.pendingRequests addObject:loadingRequest];
  74. }

第六步,把请求返回数据输出到loadingRequest的操作

  1. - (void)processPendingRequests
  2. {
  3. __weak __typeof(self)weakSelf = self;
  4. dispatch_async(dispatch_get_main_queue(), ^{
  5. __strong __typeof(weakSelf)strongSelf = weakSelf;
  6. NSMutableArray *requestsCompleted = [NSMutableArray array];
  7. //从缓存信息中找出当前正在请求中的loadingRequest
  8. for (AVAssetResourceLoadingRequest *loadingRequest in strongSelf.pendingRequests){
  9. //把头部信息输出到loadingRequest中
  10. [strongSelf fillInContentInformation:loadingRequest.contentInformationRequest];
  11. //把视频数据输出到loadingRequest中
  12. BOOL didRespondCompletely = [strongSelf respondWithDataForRequest:loadingRequest.dataRequest];
  13. //在success状态中做最后一次调用的时候,检测到请求已经完成,则从缓存信息中清除loadingRequest,并且把loadingRequest标志为完成处理状态
  14. if (didRespondCompletely){
  15. [requestsCompleted addObject:loadingRequest];
  16. [loadingRequest finishLoading];
  17. }
  18. }
  19. //清理缓存
  20. [strongSelf.pendingRequests removeObjectsInArray:requestsCompleted];
  21. });
  22. }
  23.  
  24. //把提取出来的头部信息输出到loadingRequest中,可以优化
  25. - (void)fillInContentInformation:(AVAssetResourceLoadingContentInformationRequest *)contentInformationRequest
  26. {
  27. if (contentInformationRequest == nil || self.contentInformation == nil){
  28. return;
  29. }
  30. contentInformationRequest.byteRangeAccessSupported = self.contentInformation.byteRangeAccessSupported;
  31. contentInformationRequest.contentType = self.contentInformation.contentType;
  32. contentInformationRequest.contentLength = self.contentInformation.contentLength;
  33. }
  34.  
  35. //把缓存数据输出到loadingRequest中
  36. - (BOOL)respondWithDataForRequest:(AVAssetResourceLoadingDataRequest *)dataRequest
  37. {
  38. long long startOffset = dataRequest.requestedOffset;
  39. if (dataRequest.currentOffset != ){
  40. startOffset = dataRequest.currentOffset;
  41. }
  42.  
  43. // Don't have any data at all for this request
  44. if (self.tempData.length < startOffset){
  45. return NO;
  46. }
  47.  
  48. // This is the total data we have from startOffset to whatever has been downloaded so far
  49. NSUInteger unreadBytes = self.tempData.length - (NSUInteger)startOffset;
  50.  
  51. // Respond with whatever is available if we can't satisfy the request fully yet
  52. NSUInteger numberOfBytesToRespondWith = MIN((NSUInteger)dataRequest.requestedLength, unreadBytes);
  53.  
  54. [dataRequest respondWithData:[self.tempData subdataWithRange:NSMakeRange((NSUInteger)startOffset, numberOfBytesToRespondWith)]];
  55.  
  56. long long endOffset = startOffset + dataRequest.requestedLength;
  57. BOOL didRespondFully = self.tempData.length >= endOffset;
  58.  
  59. return didRespondFully;
  60. }

视频边下边播的流程大致上已经描述完毕,本博文中没有说到的代码有错误处理方式、缓存文件的读写和保存格式、部分内存缓存使用说明、

参考链接:
http://www.codeproject.com/Articles/875105/Audio-streaming-and-caching-in-iOS-using
http://www.cnblogs.com/kenshincui/p/4186022.html#mpMoviePlayerController

补充:
在开发过程中遇到的一些坑在这里补充一下
1.在iOS9后,AVPlayer的replaceCurrentItemWithPlayerItem方法在切换视频时底层会调用信号量等待然后导致当前线程卡顿,如果在UITableViewCell中切换视频播放使用这个方法,会导致当前线程冻结几秒钟。遇到这个坑还真不好在系统层面对它做什么,后来找到的解决方法是在每次需要切换视频时,需重新创建AVPlayer和AVPlayerItem。
2.iOS9后,AVFoundation框架还做了几点修改,如果需要切换视频播放的时间,或需要控制视频从头播放调用seekToDate方法,需要保持视频的播放rate大于0才能修改,还有canUseNetworkResourcesForLiveStreamingWhilePaused这个属性,在iOS9前默认为YES,之后默认为NO。
3.AVPlayer的replaceCurrentItemWithPlayerItem方法正常是会引用住参数AVPlayerItem的,但在某些情况下导致视频播放失败,它会马上释放对这个对象的持有,假如你对AVPlayerItem的实例对象添加了监听,但是自己没有对item的计数进行管理,不知道什么时候释放这个监听,则会导致程序崩溃。
4.为什么我选择第三种方法实现边下边播,第一种方法需要程序引入LocalServer库,需增加大量app包大小,且需要开启本地服务,从性能方面考虑也是不合适。第二种方式存在的缺陷很多,一来只能播放网络上返回格式contentType为public/mpeg4等视频格式的url视频地址,若保存下来之后,文件的格式也需要保存为.mp4或.mov等格式的本地文件才能从本地中读取,三来使用AVMutableComposition对视频进行重构后保存,经过检验会对视频源数据产生变化,对于程序开发人员来说,需要保证各端存在的视频数据一致。第三种边下边播的方法其实是对第二种方法的扩展,能够解决上面所说的三种问题,可操控的自由度更高。

ios 音视频实现边播边缓存的思路和解决方案 (转)的更多相关文章

  1. Android设备广告投放解决方案——大量网络图片、多个网络视频的轮播、缓存与更新

    转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/7742996.html 一:业务场景 基于Android系统的设备上投放广告,诸如:地铁广告屏.自助服务机器上的 ...

  2. iOS 音视频播放

    播放控制切换为: ijkplayer wiki: https://github.com/changsanjiang/SJVideoPlayer/wiki/Use-ijkplayer 播放控制切换为: ...

  3. ios上视频与音乐合成后出现播放兼容问题的解决方法

    近期EasyDarwin开源流媒体团队EasyVideoRecorder小组同学Carl在支持一款短视频应用上线时,遇到一个问题:我们在IOS上合成"图片+音乐"成为视频之后,在P ...

  4. iOS视频边下边播--缓存播放数据流

    实现视频边下边播,这里的边下边播不是单独开一个子线程去下载,而是把视频播放的数据给保存到本地.简而言之,就是使用一遍的流量,既播放了视频,也保存了视频. 用到的框架:<AVFoundation/ ...

  5. iOS ksyhttpcache音视频缓存

    pod 'ksyhttpcache' 桥接文件 引入 #import <KSYHTTPCache/KSYHTTPProxyService.h> 带appdelegate里初始化 KSYHT ...

  6. ios ktvhttpcache 音视频缓存插件使用

    1.PodFile 文件增加 pod 'KTVHTTPCache',  '~> 2.0.0' 2.在终端 需要先cd到podfile文件所在目录  执行pod install 3.在header ...

  7. Android IOS WebRTC 音视频开发总结(五七)-- 网络传输上的一种QoS方案

    本文主要介绍一种QoS的解决方案,文章来自博客园RTC.Blacker,欢迎关注微信公众号blacker,更多详见www.rtc.help QoS出现的背景: 而当网络发生拥塞的时候,所有的数据流都有 ...

  8. 视频边下边播--缓存播放数据流-b

    google搜索“iOS视频变下边播”,有好几篇博客写到了实现方法,其实只有一篇,其他都是copy的,不过他们都是使用的本地代理服务器的方式. 原理很简单,但是缺点也很明显,需要自己写一个本地代理服务 ...

  9. 转:Android IOS WebRTC 音视频开发总结 (系列文章集合)

    随笔分类 - webrtc   Android IOS WebRTC 音视频开发总结(七八)-- 为什么WebRTC端到端监控很关键? 摘要: 本文主要介绍WebRTC端到端监控(我们翻译和整理的,译 ...

随机推荐

  1. Apache 与 php的环境搭建

    Apache和PHP的版本分别为: httpd-2.4.9-win64-VC11.zip php-5.6.9-Win32-VC11-x64.zip 下载地址: php-5.6.9-Win32-VC11 ...

  2. Redis/HBase/Tair比较

    KV系统对比表 对比维度 Redis Redis Cluster Medis Hbase Tair 访问模式    支持Value大小 理论上不超过1GB(建议不超过1MB) 理论上可配置(默认配置1 ...

  3. 【开源】.Net 分布式服务中心

    分布式服务中心 开源地址: http://git.oschina.net/chejiangyi/Dyd.BaseService.ServiceCenter 当垂直应用越来越多,应用之间交互不可避免,将 ...

  4. ie6 ie7 ie8 ie9兼容问题终极解决方案

    放下包袱,解决低版本兼容问题   这是一个老生常谈的问题,自然解决这个问题的方案也比较多,下面整理了一些解决方法: 1.强制使用高版本渲染模式. 强制使用Edge模式来解析网页代码 <meta ...

  5. Atitit.项目修改补丁打包工具 使用说明

    Atitit.项目修改补丁打包工具 使用说明 1.1. 打包工具已经在群里面.打包工具.bat1 1.2. 使用方法:放在项目主目录下,执行即可1 1.3. 打包工具的原理以及要打包的项目列表1 1. ...

  6. 利用PowerShell复制SQLServer账户的所有权限

    问题 对于DBA或者其他运维人员来说授权一个账户的相同权限给另一个账户是一个很普通的任务.但是随着服务器.数据库.应用.使用人员地增加就变得很枯燥乏味又耗时费力的工作.那么有什么容易的办法来实现这个任 ...

  7. SQL 提示介绍 hash/merge/concat union

    查询提示一直是个很有争议的东西,因为他影响了sql server 自己选择执行计划.很多人在问是否应该使用查询提示的时候一般会被告知慎用或不要使用...但是个人认为善用提示在不修改语句的条件下,是常用 ...

  8. 使用四元数解决万向节锁(Gimbal Lock)问题

    问题 使用四元数可以解决万向节锁的问题,但是我在实际使用中出现问题:我设计了一个程序,显示一个三维物体,用户可以输入绕zyx三个轴进行旋转的指令,物体进行相应的转动. 由于用户输入的是绕三个轴旋转的角 ...

  9. FineReport:关于扩展行列求各种条件下的函数运用

    最简单的扩展列,扩展行的求"最大,最小,平均"值的例子 设计图 效果图 相关函数 =MAX(B2:E2) =MIN(B2:E2) =AVERAGE(B2:E2) 这个是(满足条件) ...

  10. 技术笔记:Indy的TIdSMTP改造,解决发送Html和主题截断问题

    使用Indy来发邮件坑不少啊,只不过有比没有好吧,使用delphi6这种老工具没办法,只能使用了新一点的Indy版本9,公司限制... 1.邮件包含TIdText和TIdAttachment时会出现T ...