3 目录浏览

3.1 功能描述

对设备端的SD卡或者U盘,进行浏览

3.2 示例代码

[[[JL_RunSDK sharedMe] mBleEntityM].mCmdManager cmdBrowseMonitorResult:^(NSArray * _Nullable array, JL_BrowseReason reason) {
switch (reason) {
    case JL_BrowseReasonReading:{
        [wself updateData:array];

        NSLogEx(@"正在读取:%lu",(unsigned long)array.count);
    }break;
    case JL_BrowseReasonCommandEnd:{
        [wself updateData:array];
        self->reqNum-=10;
        if (self->reqNum>0) {
            [wself request:self->reqModel];
        }
        NSLogEx(@"读取命令结束:%lu delegate:%@",(unsigned long)array.count,self->_delegate);
    }break;
    case JL_BrowseReasonFolderEnd:{
        [wself updateData:array];

        if (array.count == 0 && self->reqNum != -1) {
            if ([self->_delegate respondsToSelector:@selector(dmHandleWithItemModelArray:)]) {
                [self->_delegate dmHandleWithItemModelArray:array];
            }
        }
        self->reqNum = -1;
        NSLogEx(@"目录读取结束:%lu delegate:%@",(unsigned long)array.count,self->_delegate);
    }break;
    case JL_BrowseReasonBusy:{
        NSLogEx(@"设备在忙");
    }break;
    case JL_BrowseReasonDataFail:{
        NSLogEx(@"数据读取失败");
    }break;
    case JL_BrowseReasonPlaySuccess:{
        [wself updatePlay];
        self->playItem = NO;
        NSLogEx(@"播放成功");
    }break;
    case JL_BrowseReasonUnknown:{
        NSLogEx(@"未知错误");
        self->reqNum = -1;
        self->playItem = NO;
        [self requestWith:self->reqModel Number:10];
    }
    default:
        break;
}
}];

3.2.1 相关接口

typedef NS_ENUM(UInt8, JL_FileOperationEnvironmentType) {
    JL_FileOperationEnvironmentTypeBigFileTransmission      = 0x00, //大文件传输
    JL_FileOperationEnvironmentTypeDeleteFile               = 0x01, //删除文件
    JL_FileOperationEnvironmentTypeFormatting               = 0x02, //格式化
    JL_FileOperationEnvironmentTypeFatfsTransmission        = 0x03, //FAT文件传输
};
typedef NS_ENUM(UInt8, JL_DeleteFileLastType) {
    JL_DeleteFileLastTypeIsNotLast                  = 0x00, //不是最后一个文件
    JL_DeleteFileLastTypeIsLast                     = 0x01, //是最后一个文件
};
typedef NS_ENUM(UInt8, JL_BrowseReason) {
    JL_BrowseReasonCommandEnd       = 0,    //读取完当前命令请求的文件
    JL_BrowseReasonFolderEnd        = 1,    //读取完当前目录的文件
    JL_BrowseReasonPlaySuccess      = 2,    //点播成功
    JL_BrowseReasonBusy             = 3,    //设备繁忙
    JL_BrowseReasonDataFail         = 4,    //目录数据发送失败
    JL_BrowseReasonReading          = 0x0f, //正在读取中
    JL_BrowseReasonUnknown,                 //未知原因
};
typedef NS_ENUM(UInt8, JL_FileContentResult) {
    JL_FileContentResultStart       = 0x00, //开始传输
    JL_FileContentResultReading     = 0x01, //正在读取
    JL_FileContentResultEnd         = 0x02, //读取结束
    JL_FileContentResultCancel      = 0x03, //取消读取
    JL_FileContentResultFail        = 0x04, //读取失败
    JL_FileContentResultNull        = 0x05, //文件不存在
    JL_FileContentResultDataError   = 0x06, //数据错误
    JL_FileContentResultCrcFail     = 0x07, //crc校验失败
};
typedef NS_ENUM(UInt8, JL_FileDataType) {
    JL_FileDataTypeDo               = 0,    //开始传输文件数据
    JL_FileDataTypeDone             = 1,    //结束传输文件数据
    JL_FileDataTypeDoing            = 2,    //正在传输文件数据
    JL_FileDataTypeCancel           = 3,    //取消传输文件数据
    JL_FileDataTypeError            = 4,    //传输文件数据出错
    JL_FileDataTypeUnknown,
};
typedef NS_ENUM(UInt8, JL_BigFileTransferCode) {
    JL_BigFileTransferCodeSuccess       = 0x00, //成功
    JL_BigFileTransferCodeFail          = 0x01, //写失败
    JL_BigFileTransferCodeOutOfRange    = 0x02, //数据超出范围
    JL_BigFileTransferCodeCrcFail       = 0x03, //crc校验失败
    JL_BigFileTransferCodeOutOfMemory   = 0x04, //内存不足
};
typedef NS_ENUM(UInt8, JL_BigFileResult) {
    JL_BigFileTransferStart         = 0x00, //开始大文件数据传输
    JL_BigFileTransferDownload      = 0x01, //传输大文件有效数据
    JL_BigFileTransferEnd           = 0x02, //结束大文件数据传输
    JL_BigFileTransferOutOfRange    = 0x03, //大文件传输数据超范围
    JL_BigFileTransferFail          = 0x04, //大文件传输失败
    JL_BigFileCrcError              = 0x05, //大文件校验失败
    JL_BigFileOutOfMemory           = 0x06, //空间不足
    JL_BigFileTransferCancel        = 0x07, //取消传输
    JL_BigFileTransferNoResponse    = 0xF1, //设备没有响应
};

typedef void(^JL_FILE_BK)(NSArray* __nullable array,JL_BrowseReason reason);
typedef void(^JL_FILE_CONTENT_BK)(JL_FileContentResult result, uint32_t size, NSData* __nullable data, float progress);
typedef void(^JL_FILE_DATA_BK)(NSData* __nullable data, NSString* __nullable path, uint16_t size, JL_FileDataType type);
typedef void(^JL_BIGFILE_BK)(NSArray* __nullable array);
typedef void(^JL_BIGFILE_RT)(JL_BigFileResult result, float progress);

@interface JL_FileManager : JL_FunctionBaseManager

- (void)closeTimer;

#pragma mark ---> 监听目录数据
/**
 @param result 状态回复
 */
-(void)cmdBrowseMonitorResult:(JL_FILE_BK __nullable)result;

#pragma mark ---> 浏览目录
/**
 @param model 文件Model
 @param number 读取的数量
 */
-(void)cmdBrowseModel:(JLModel_File*)model
               Number:(uint8_t)number
               Result:(JL_CMD_RESPOND __nullable)result;

#pragma mark ---> 删除文件(必须异步执行)
/**
 @param array 文件Model数组
 */
-(BOOL)cmdDeleteFileModels:(NSArray*)array;
/**
 *  删除单个JLModel_File文件
 *  @param model JLModel_File对象
 *  @param last 是否最后一个文件
 *  @param result 删除成功回调
 */
-(void)cmdDeleteFile:(JLModel_File*)model
             IsLast:(JL_DeleteFileLastType)last
             Result:(JL_CMD_RESPOND __nullable)result;

#pragma mark 设备格式化
//@param model 设备句柄
-(void)cmdDeviceFormat:(NSString*)handle Result:(JL_CMD_RESPOND __nullable)result;

#pragma mark ---> 清除设备音乐缓存记录
/**
 @param type 卡的类型
 */
-(void)cmdCleanCacheType:(JL_CardType)type;

#pragma mark --->【文件传输 固件-->APP】
#pragma mark 1.监听文件数据
-(void)cmdFileDataMonitorResult:(JL_FILE_DATA_BK __nullable)result;

#pragma mark 2.允许传输文件数据
-(void)cmdAllowFileData;

#pragma mark 3.拒绝传输文件数据
-(void)cmdRejectFileData;

#pragma mark 4.停止传输文件数据
-(void)cmdStopFileData;

#pragma mark --->【文件传输 APP-->固件】
#pragma mark 5.请求传输文件给设备
-(void)cmdFileDataSize:(uint8_t)size
              SavePath:(NSString*)path;

#pragma mark 6.推送文件数据给设备
-(void)cmdPushFileData:(NSData*)data;

#pragma mark 【大文件传输 App-->固件】
// @param  path       大文件下载文件路径
// @param  fileName   大文件下载文件名称
// @param  result     大文件下载结果
-(void)cmdBigFileData:(NSString *)path WithFileName:(NSString *)fileName
               Result:(JL_BIGFILE_RT __nullable)result;

#pragma mark 取消文件传输
-(void)cmdStopBigFileData;

#pragma mark 通知固件进行环境准备
// @param  environment 0:大文件传输 1:删除文件 2:格式化
-(void)cmdPreEnvironment:(JL_FileOperationEnvironmentType)environment Result:(JL_CMD_RESPOND __nullable)result;

#pragma mark 通过名字删除文件
-(void)cmdFileDeleteWithName:(NSString*)name Result:(JL_CMD_RESPOND __nullable)result;

#pragma mark 设置文件传输句柄
/**
 *  大文件传输,设置当前传输句柄 for crc16
 */
- (void)setCurrentFileHandleType:(JL_FileHandleType)currentFileHandleType;
- (JL_FileHandleType)getCurrentFileHandleType;

#pragma mark ---> 读取外置卡的文件内容
-(void)cmdFileReadContentWithName:(NSString*)name Result:(JL_FILE_CONTENT_BK __nullable)result;

#pragma mark ---> 簇号方式读取外置卡的文件内容
- (void)cmdFileReadContentWithFileClus:(uint32_t)fileClus Result:(JL_FILE_CONTENT_BK __nullable)result;

#pragma mark ---> 取消读取外置卡的文件内容
-(void)cmdFileReadContentCancel;

@end

3.3 流程说明

3.3.1 初始化

@interface DMusicHandler(){
    /**
    层级结构
        {
        "uuid0": {//设备uuid
            "card": [
            "fm model0",
            "fm model1",
            "fm model2"
            ]
        },
        "uuid1": {
            "card1": "value",
            "usb": [
            "fm model0",
            "fm model1",
            "fm model2"
            ]
        },
        "uuid2": {
            "card1": [
            "fm model0",
            "fm model1",
            "fm model2"
            ],
            "usb": "usbq"
        }
        }
    */
    NSMutableDictionary *saveDict;
    NSString *bleUuid;
    JL_CardType nowType;
    NSInteger reqNum;
    JLModel_File *reqModel;
    BOOL    playItem;
}
@end

@implementation DMusicHandler

static dispatch_once_t onceToken;
static DMusicHandler *_dmh;

+(instancetype)sharedInstance{
    dispatch_once(&onceToken, ^{
        _dmh = [[DMusicHandler alloc] init];
    });
    return _dmh;
}

+(void)deallocDMusic{
    onceToken = 0;
    _dmh = nil;
}

- (instancetype)init
{
    self = [super init];
    if (self) {
        saveDict = [NSMutableDictionary new];
        [self handleFileMonitor];
        [self addNote];
        playItem = NO;
    }
    return self;
}

#pragma mark ///处理返回数组
-(void)handleFileMonitor{
    __weak typeof(self) wself = self;
    [[[JL_RunSDK sharedMe] mBleEntityM].mCmdManager.mFileManager cmdBrowseMonitorResult:^(NSArray * _Nullable array, JL_BrowseReason reason) {
        switch (reason) {
            case JL_BrowseReasonReading:{
                [wself updateData:array];

                NSLogEx(@"正在读取:%lu",(unsigned long)array.count);
            }break;
            case JL_BrowseReasonCommandEnd:{
                [wself updateData:array];
                unsigned long shouldDown = self->reqNum - array.count;
                if (shouldDown>0) {
                    [wself request:self->reqModel];
                }else{
                    self->reqNum = -1;
                }
                NSLogEx(@"读取命令结束:%lu delegate:%@",(unsigned long)array.count,self->_delegate);
            }break;
            case JL_BrowseReasonFolderEnd:{
                [wself updateData:array];
                if (array.count == 0 && self->reqNum != -1) {
                    if ([self->_delegate respondsToSelector:@selector(dmHandleWithItemModelArray:)]) {
                        [self->_delegate dmHandleWithItemModelArray:array];
                    }
                }
                self->reqNum = -1;
                NSLogEx(@"目录读取结束:%lu delegate:%@",(unsigned long)array.count,self->_delegate);
            }break;
            case JL_BrowseReasonBusy:{
                NSLogEx(@"设备在忙");
            }break;
            case JL_BrowseReasonDataFail:{
                NSLogEx(@"数据读取失败");
            }break;
            case JL_BrowseReasonPlaySuccess:{
                [wself updatePlay];
                self->playItem = NO;
                NSLogEx(@"播放成功");
            }break;
            case JL_BrowseReasonUnknown:{
                NSLogEx(@"未知错误");
                self->reqNum = -1;
                self->playItem = NO;
                [self requestWith:self->reqModel Number:10];
            }
            default:
                break;
        }
    }];
}

#pragma mark ///更新数据时的通知处理
-(void)addNote{
    [JL_Tools add:kJL_BLE_M_ENTITY_DISCONNECTED Action:@selector(bleDisconnectAction:) Own:self];
    [JLModel_Device observeModelProperty:@"currentClus" Action:@selector(noteCurrentClus:) Own:self];
    [JLModel_Device observeModelProperty:@"cardArray" Action:@selector(noteCardArray:) Own:self];
}


-(void)bleDisconnectAction:(NSNotification *)note{
    CBPeripheral *pl = [note object];
    NSMutableDictionary *dict = saveDict[pl.identifier.UUIDString];
    [dict removeAllObjects];
}

-(void)noteCardArray:(NSNotification *)note{

    BOOL isOk = [JL_RunSDK isCurrentDeviceCmd:note];
    if (isOk == NO)return;

    NSDictionary *tmpDict = [note object];
    NSString *uuid = tmpDict[kJL_MANAGER_KEY_UUID];
    if (![uuid isEqualToString:[[JL_RunSDK sharedMe] mBleUUID]]) {
        return;
    }
    NSArray *array = tmpDict[kJL_MANAGER_KEY_OBJECT];
    NSArray *tmpA = @[@(JL_CardTypeUSB),@(JL_CardTypeSD_0),@(JL_CardTypeSD_1),@(JL_CardTypeFLASH),@(JL_CardTypeLineIn)];
    for (int i  = 0;i<tmpA.count;i++) {
        if (![array containsObject:tmpA[i]]) {
            JL_CardType type = [tmpA[i] integerValue];
            NSMutableDictionary *dict = saveDict[uuid];
            [dict removeObjectForKey:[self keyByType:type]];
        }
    }
    if ([_delegate respondsToSelector:@selector(dmCardMessageDismiss:)]) {
        [_delegate dmCardMessageDismiss:array];
    }
}

-(void)noteCurrentClus:(NSNotification *)note{
    BOOL isOk = [JL_RunSDK isCurrentDeviceCmd:note];
    if (isOk == NO)return;

    dispatch_async(dispatch_get_main_queue(), ^{
        [self updatePlay];
    });
}

3.3.2 接口声明

typedef enum : NSUInteger {
    DM_ERROR_Max_Fold,
    DM_ERROR_Unknown
} DM_ERROR;
NS_ASSUME_NONNULL_BEGIN
@protocol DMHandlerDelegate <NSObject>

-(void)dmHandleWithTabTitleArray:(NSArray<JLModel_File *> *)modelA;

-(void)dmHandleWithItemModelArray:(NSArray<JLModel_File *> *)modelB;

-(void)dmHandleWithPlayItemOK;

-(void)dmLoadFailed:(DM_ERROR)err;

-(void)dmCardMessageDismiss:(NSArray *)nowArray;

@end

@interface DMusicHandler : NSObject
@property(nonatomic,weak)id<DMHandlerDelegate> delegate;
@property(nonatomic,weak)JL_EntityM *nowEntity;
+(instancetype)sharedInstance;

-(void)loadModeData:(JL_CardType)type;
/// 点击对应的Title时更新目录
/// @param model fileModel
-(void)tabArraySelect:(JLModel_File *)model;

/// 请求目录列表或者点播该文件
/// @param model jlfilemodel
/// @param n 数目
-(void)requestWith:(JLModel_File *)model Number:(NSInteger)n;

/// 在同一个根目录下加载更多的数据
/// @param num 行数
-(void)requestModelBy:(NSInteger)num;

3.3.3 接口实现

#pragma mark ///请求数据

-(void)loadModeData:(JL_CardType)type{
    nowType = type;
    BOOL result = [self checkCaChe:type];
    if (!result) {
        [self firstLoadData];
    }else{
        NSMutableArray *arr = [self checkoutArray:type];
        if ([_delegate respondsToSelector:@selector(dmHandleWithTabTitleArray:)]) {
            [_delegate dmHandleWithTabTitleArray:arr];
        }
        JLModel_File *model = [self lastPathRequest];
        [self request:model];
    }
}
-(void)firstLoadData{
    JL_EntityM *entity = [[JL_RunSDK sharedMe] mBleEntityM];
    JLModel_Device *model = [entity.mCmdManager outputDeviceModel];
    JLModel_File *fileModel = [JLModel_File new];
    fileModel.fileType   = JL_BrowseTypeFolder;
    NSMutableArray *mutbA = [NSMutableArray new];
    NSMutableDictionary *sDict = saveDict[bleUuid];
    switch (nowType) {
        case JL_CardTypeUSB:{
            fileModel.cardType   = JL_CardTypeUSB;
            fileModel.fileName   = @"USB";
            fileModel.folderName = @"USB";
            fileModel.fileHandle = model.handleUSB;
            fileModel.fileClus   = 0;
            [mutbA addObject:fileModel];
            [sDict setValue:mutbA forKey:USB_Card];
        }break;
        case JL_CardTypeSD_0:{
            fileModel.cardType   = JL_CardTypeSD_0;
            fileModel.fileName   = @"SD Card";
            fileModel.folderName = @"SD Card";
            fileModel.fileHandle = model.handleSD_0;
            fileModel.fileClus   = 0;
            [mutbA addObject:fileModel];
            [sDict setValue:mutbA forKey:SD_0_Card];
        }break;
        case JL_CardTypeSD_1:{
            fileModel.cardType   = JL_CardTypeSD_1;
            fileModel.fileName   = @"SD Card 2";
            fileModel.folderName = @"SD Card 2";
            fileModel.fileHandle = model.handleSD_1;
            fileModel.fileClus   = 0;
            [mutbA addObject:fileModel];
            [sDict setValue:mutbA forKey:SD_1_Card];
        }break;
        case JL_CardTypeFLASH:{
            fileModel.cardType   = JL_CardTypeFLASH;
            fileModel.fileName   = @"FLASH";
            fileModel.folderName = @"FLASH";
            fileModel.fileHandle = model.handleFlash;
            fileModel.fileClus   = 0;
            [mutbA addObject:fileModel];
            [sDict setValue:mutbA forKey:FLASH_Card];
        }break;
        default:
            break;
    }
    [self requestWith:fileModel Number:20];
}


-(void)requestWith:(JLModel_File *)model Number:(NSInteger)n{
    reqModel = model;
    if (model.fileType == JL_BrowseTypeFile) {
        if (playItem == YES) {
            return;
        }
        playItem = YES;
        JL_EntityM *entity = [[JL_RunSDK sharedMe] mBleEntityM];
        [entity.mCmdManager.mFileManager cmdBrowseModel:model Number:1 Result:nil];
    }else{
        if (reqNum>0) {
            NSLogEx(@"取消请求%@",model.fileName);
            return;
        }
        reqNum = n;
        [self addTabArray:model];
        [self request:model];
        NSLogEx(@"%@",model.fileName);
    }
}

-(void)request:(JLModel_File *)model{
    JL_EntityM *entity = [[JL_RunSDK sharedMe] mBleEntityM];
    [entity.mCmdManager.mFileManager cmdBrowseModel:model Number:10 Result:nil];
}

-(void)requestModelBy:(NSInteger)num{
    JLModel_File *fm = [self lastPathRequest];
    [self requestWith:fm Number:num];
}
/// 点击对应的Title时更新目录
/// @param model fileModel
-(void)tabArraySelect:(JLModel_File *)model{
    if (reqNum>0) {
        NSLogEx(@"取消请求%@",model.fileName);
        return;
    }
    if (playItem == YES) {
        NSLogEx(@"需要等待播放成功之后才可以继续操控");
        return;
    }
    NSMutableArray *array = [self checkoutArray:nowType];
    NSMutableArray *newArray = [NSMutableArray new];
    for (int i = 0; i<array.count; i++) {
        JLModel_File *fm = array[i];
        [newArray addObject:array[i]];
        if ([fm.pathData isEqualToData:model.pathData]) {
            break;
        }
        if (model.pathData == nil) {
            break;
        }
    }
    NSMutableDictionary *dict = saveDict[bleUuid];
    NSString *key = [self keyByType:nowType];
    [dict setValue:newArray forKey:key];
    if ([_delegate respondsToSelector:@selector(dmHandleWithTabTitleArray:)]) {
        [_delegate dmHandleWithTabTitleArray:newArray];
    }
    JLModel_File *backModel = [self lastPathRequest];
    reqNum = -1;
    [self requestWith:backModel Number:20];
}

-(BOOL)shouldBeUpdate:(JLModel_File *)model{
    NSMutableArray *arr = [self checkoutArray:nowType];
    JLModel_File *item = [arr lastObject];
    if (item.pathData == nil) {
        return YES;
    }
    if ([item.pathData isEqualToData:model.pathData]) {
        return YES;
    }else{
        return NO;
    }
}

备注

使用时注意添加对应的协议 DMHandlerDelegate