智能小视频Smartsmallvideo

    功能说明

    视频流

    代码目录

    image.png

    文件 说明
    BDSSVListViewController 视频流控制器
    BDSSVListCollectionViewCell 视频流数据单元
    BDSSVCollectionWaterfallLayout 视频流瀑布流布局

    获取视频媒资

    下拉刷新

    摘录BDSSVListViewController.m文件

    self.collectionView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
            if (self.isRefresh) {
                return;
            }
            index = 0;
            self.isRefresh = YES;
            [self.videos removeAllObjects];
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.01f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                NSMutableDictionary *dic = [NSMutableDictionary dictionary];
                if (self.model.author.user_id) {
                    dic[@"url"] = [NSString stringWithFormat:@"%@%@",kHost,@"/v1/video/getSpecificVideoList"];
                    dic[@"user_id"] = self.model.author.user_id;
                } else {
                    dic[@"url"] = [NSString stringWithFormat:@"%@%@",kHost,@"/v1/video/getVideoList"];
                }
                dic[@"index"] = [NSNumber numberWithInteger:index];
                @weakify(self);
                [self.listModel refreshNewListWithParam:dic success:^(NSArray * _Nonnull list) {
                    @strongify(self);
                    if (self.delegate && [self.delegate respondsToSelector:@selector(sourceDataSuccess:)]) {
                        [self.delegate sourceDataSuccess:list];
                    }
                    [self.collectionView.mj_header endRefreshing];
                    if (list.count > 0) {
                        [self.videos addObjectsFromArray:list];
                        [self.collectionView reloadData];
                        [self.collectionView.mj_footer resetNoMoreData];
                    } else {
                        [self.collectionView.mj_footer endRefreshingWithNoMoreData];
                    }
                    self.collectionView.mj_footer.hidden = NO;
                    self.isRefresh = NO;
                } failure:^(NSError * _Nonnull error) {
                    @strongify(self);
                    if (self.delegate && [self.delegate respondsToSelector:@selector(sourceDataFailure:)]) {
                        [self.delegate sourceDataFailure:error];
                    }
                    [self.collectionView.mj_header endRefreshing];
                    self.isRefresh = NO;
                    [self.collectionView reloadData];
                    [self.collectionView.mj_footer endRefreshingWithNoMoreData];
                }];
            });
        }];

    加载更多

    摘录BDSSVListViewController.m文件

    self.collectionView.mj_footer = [MJRefreshAutoNormalFooter footerWithRefreshingBlock:^{
            // 进入刷新状态后会自动调用这个block
            if (self.isRefresh) {
                return;
            }
            index++;
            NSLog(@"downrefresh %ld",(long)index);
            self.isRefresh = YES;
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.01f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                NSMutableDictionary *dic = [NSMutableDictionary dictionary];
                if (self.model.author.user_id) {
                    dic[@"url"] = [NSString stringWithFormat:@"%@%@",kHost,@"/v1/video/getSpecificVideoList"];
                    dic[@"user_id"] = self.model.author.user_id;
                } else {
                    dic[@"url"] = [NSString stringWithFormat:@"%@%@",kHost,@"/v1/video/getVideoList"];
                }
                dic[@"index"] = [NSNumber numberWithInteger:index];
                @weakify(self);
                [self.listModel refreshNewListWithParam:dic success:^(NSArray * _Nonnull list) {
                    @strongify(self);
                    if (self.delegate && [self.delegate respondsToSelector:@selector(sourceDataSuccess:)]) {
                        [self.delegate sourceDataSuccess:list];
                    }
                    [self.collectionView.mj_footer endRefreshing];
                    if (list.count > 0) {
                        [self.videos addObjectsFromArray:list];
                        [self.collectionView reloadData];
                    }else {
                        index--;
                        [self.collectionView reloadData];
                        [self.collectionView.mj_footer endRefreshingWithNoMoreData];
                    }
                    self.isRefresh = NO;
                } failure:^(NSError * _Nonnull error) {
                    @strongify(self);
                    index--;
                    if (self.delegate && [self.delegate respondsToSelector:@selector(sourceDataFailure:)]) {
                        [self.delegate sourceDataFailure:error];
                    }
                    [self.collectionView.mj_footer endRefreshing];
                    self.isRefresh = NO;
                    [self.collectionView reloadData];
                    [self.collectionView.mj_footer endRefreshingWithNoMoreData];
                }];
            });
        }];

    视频采集

    代码目录

    image.png

    文件 说明
    DARFiltersController 拍摄贴纸控制器
    VideoCapture 视频拍摄storyBoard
    CaptureViewController 视频拍摄主控制器
    LiveModel 视频拍摄模型,负责开启、结束、开始预览、结束预览及手电筒、切换摄像头

    开启摄像头采集

    摘录CaptureViewController.swift文件,会自动向用户获取摄像头及麦克风系统权限

        override func viewWillAppear(_ animated: Bool) {
            super.viewWillAppear(true)
            navigationController?.setNavigationBarHidden(true, animated: false)
            navigationController?.interactivePopGestureRecognizer?.isEnabled = false
            // 还原到拍摄器初始美颜滤镜
            if (model?.session.imageFilterSetting.colorFilter != nil) {
                model?.session.imageFilterSetting.removeFilter(with: .colorAdjust)
            }
            model?.startPreview()
        }

    自动开启美颜

    开启视频采集,回调中添加美颜能力,摘录CaptureViewController.swift文件

    extension CaptureViewController : BDCloudAVRecordSessionDelegate {
        func previewStarted() {
            model?.filterS.imageSetting.setBeautyLevel(0.7);
            model?.filterS.imageSetting.setSkinLevel(0.8);
        }
    }

    数据流图

    shipinzhencaijichuliliucheng.png

    视频录制

    代码目录

    与视频采集相同

    开启摄像头录制

    摘录CaptureViewController.swift文件,写入录制文件时,会提供开始写入和结束写入回调给开发者,便于业务二次开发

    extension CaptureViewController : BDCloudAVRecordSessionDelegate {
        func writeStarted(at time: CMTime) {
            isCapturing = true
            timeCounter = NSDate.init()
            DispatchQueue.main.async {
                self.timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(self.onTimerCount(timer:)), userInfo: nil, repeats: true)
            }
        }
        
        func writeStopped(at time: CMTime, error: Error!) {
            if self.timer != nil {
                if (self.timer?.isValid)! {
                    timer?.invalidate()
                    timer = nil
                    timeCounter = nil
                }
            }
            
            DispatchQueue.main.async {
                self.isCapturing = false
                self.circleProgressBar.clearProgress()
                if (!self.isCancelled) {
                    self.enterEdit()
                }
                self.isCancelled = false;
            }
        }
    }

    数据流图

    shipinluzhiliucheng.png

    视频预览

    代码目录

    image.png

    文件 说明
    EditSubtitleViewController 字幕控制器
    EditViewController.storyboard 视频预览storyBoard
    EditSettingViewController 视频处理控制器,包括颜色调整、滤镜、美颜和添加音乐
    EditViewController 视频预览控制器,包括预览设置及视频处理设置
    PreviewModel 视频预览模型,包括预览开始、结束控制及设置预览文件地址、速度、正反放、区间
    • 颜色调整,参见ImageFilterView.swift文件
    • 滤镜切换,参见StyleFilterView.swift文件
    • 美颜调整,参加BeautyFilterView.swift文件
    • 背景音乐,参加MusicFilterView.swift文件

    初始化视频预览文件

    摘录PreviewModel.m文件

    - (instancetype)initWithSrcFile:(NSString*)file {
        if (self = [super init]) {
            _sourceS = [PartialSourceSetting new];
            _sourceS.file = file;
            _filterS = [FilterSetting new];
            _destS = nil;
            _speed = 1.0;
            BDCloudAVPreviewSession *session = [[BDCloudAVPreviewSession alloc] initWithURL:[NSURL fileURLWithPath:_sourceS.file]];
            session.fillMode = BDCloudAVImagePlayerFillModePreserveAspectRatio;
            _session = session;
            [_filterS setEffectSession:_session];
        }
        return self;
    }
    • 开始预览视频文件 摘录PreviewModel.m文件,BDCloudAVPreviewSession类实现隐藏在BDCloudAVKit.framework中
    - (BDCloudAVPreviewSession*)previewSession {
        return (BDCloudAVPreviewSession*)self.session;
    }
    
    - (void)start {
        [self.previewSession start];
    }

    数据流图

    shipinyulanliucheng.png

    视频特效

    支持特效的类型

    • 基础美颜(美白、磨皮)
    • 高级美颜(大眼、瘦脸)
    • 风格滤镜

      • 颜色调整(对比度、亮度、曝光度、色温、饱和度、锐度)
      • 智能调整(回忆、少女、红润、都市、微光、红唇)
    • 人脸贴纸,只有少数贴纸使用内置,其他需要使用网络下载

      • 2D贴纸
      • 3D贴纸
      • 手势贴纸

    使用方式,可参见

    • 颜色调整,参见ImageFilterView.swift文件
    • 滤镜切换,参见StyleFilterView.swift文件
    • 美颜调整,参加BeautyFilterView.swift文件

    视频合成&&上传

    代码目录

    image.png

    文件 说明
    SynSuccessViewController 视频上传结果控制器
    UploadViewController 视频上传控制器
    ProcessViewController 视频合成控制器
    Process 视频合成storyboard
    ProcessModel 视频合成模型

    视频合成创建、开始及结束

      - (instancetype)initWithSource:(NSString *)src
                       destination:(NSString *)dest
                     videoSettings:(BDCloudAVVideoOutputSettings *)videoSettings
                     audioSettings:(BDCloudAVAudioOutputSettings *)audioSettings {
        if (self = [super init]) {
            _session = [[BDCloudAVProcessSession alloc] initWithSource:[NSURL fileURLWithPath:src]
                                                           destination:[NSURL fileURLWithPath:dest]
                                                         videoSettings:videoSettings
                                                         audioSettings:audioSettings];
        }
        return self;
    }
    
    - (void)start {
        [self.session start];
    }
    
    - (void)stop {
        [self.session stop];
    }

    视频上传

    摘录UploadVideoActionModel.m文件,使用百度云BOS SDK上传

    - (void)start {
        
        __weak typeof(self) wself = self;
        
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            __strong typeof(wself) sself = wself;
            
            sself.stage = ActionStageOngoing;
            if (sself.actionStartEvent) {
                sself.actionStartEvent();
            }
            
            __block NSError* error;
            
            // 从后台获取上传临时token
            __block NSMutableDictionary *dic = [NSMutableDictionary new];
            BDSSVLoginParam *loginParam = [BDSSVLoginParam shareInstance];
            
            dic[@"user_id"] = loginParam.userid;
            dic[@"file_type"] = @"0";//视频
            dic[@"file_id"] = self.file.lastPathComponent;
            dic[@"location"] = @"nolocation";
            
            sself.uploadConf = [[ClientModel sharedInstance] fetchClientConf:dic];
            
            dic[@"video_id"] = sself.uploadConf[@"video_id"];
            
            if (sself.actionProgressEvent) {
                sself.actionProgressEvent(0.1);
            }
            
            BOSObjectContent* content = [BOSObjectContent new];
            content.objectData.file = sself.file;
            
            BOSPutObjectRequest* putReq = [BOSPutObjectRequest new];
            putReq.bucket = self.uploadConf[@"bucket"];
            putReq.key = self.uploadConf[@"bos_key"];//sself.response.sourceKey;
            putReq.objectContent = content;
            sself.bosClient = [ClientModel sharedInstance].bosClient;
            BCETask* task = [sself.bosClient putObject:putReq];
            task.then(^(BCEOutput* output) {
                if (output.progress) {
                    if (sself.actionProgressEvent) {
                        sself.actionProgressEvent(0.1 + output.progress.floatValue * 0.008);
                    }
                }
                
                if (output.response) {
                    if (sself.actionProgressEvent) {
                        sself.actionProgressEvent(0.9);
                        dic[@"status"] = @"11"; //success
                    }
                }
                
                if (output.error) {
                    error = output.error;
                    dic[@"status"] = @"12"; //failure
                }
            });
            
            if ([sself monitorTaskCancel:task]) {
                return;
            }
            
            if ([sself checkError:error]) {
                return;
            }
            
            [[ClientModel sharedInstance] uploadClientFinish:dic];
            
            if (sself.actionProgressEvent) {
                sself.actionProgressEvent(1);
            }
            
            sself.stage = ActionStageAfter;
            
            self.leftButtonTitle = @"返回首页";
            self.message = @"上传成功!";
            if (sself.actionFinishEvent) {
                sself.actionFinishEvent(nil);
            }
        });
    }
    上一篇
    整体流程
    下一篇
    后端服务集成说明