# EZOpenSDK iOS 基础集成

## 一、CocoaPods 安装（推荐）

### Podfile 配置
```ruby
source 'https://github.com/CocoaPods/Specs.git'

platform :ios, '13.0'
target 'YourProjectTarget' do
  pod 'EZOpenSDK', '~> 5.28.4'
end
```

### 执行安装
```bash
pod install
```

安装成功后使用 `.xcworkspace` 文件打开工程。

### 头文件导入
```objc
#import <EZOpenSDKFramework/EZOpenSDKFramework.h>
```

## 二、手动导入静态库

1. 下载 SDK 并解压，将 `EZOpenSDK` 文件夹拖入工程
2. 导入系统依赖库：libsqlite3.0、CoreMedia、AudioToolbox、VideoToolbox、GLKit、OpenAL、MobileCoreServices、AVFoundation、CoreTelephony、SystemConfiguration、libc++、libiconv、libbz2、libz
3. 添加 Other Linker Flags：`-ObjC`（注意大小写）
4. 关闭 Bitcode

## 三、权限配置（Info.plist）

### 相册权限（截图/录制保存）
```xml
<key>NSPhotoLibraryUsageDescription</key>
<string>$(PRODUCT_NAME)需要使用手机相册</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>$(PRODUCT_NAME)需要使用手机相册</string>
```

### 麦克风权限（对讲）
```xml
<key>NSMicrophoneUsageDescription</key>
<string>$(PRODUCT_NAME)需要使用手机麦克风</string>
```

### 摄像头权限（扫码）
```xml
<key>NSCameraUsageDescription</key>
<string>$(PRODUCT_NAME)需要使用手机照相机用于扫码</string>
```

### 本地网络权限 & 定位权限（配网）
```xml
<key>NSLocalNetworkUsageDescription</key>
<string>$(PRODUCT_NAME)需要使用本地网络权限用于wifi配网</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>$(PRODUCT_NAME)需要使用定位权限用于wifi配网</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>$(PRODUCT_NAME)需要使用定位权限用于wifi配网</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>$(PRODUCT_NAME)需要使用定位权限用于wifi配网</string>
```

### Signing & Capabilities 配置（配网需要）
- Access WiFi Information（获取手机连接的 WiFi 名）
- Hotspot Configuration（连接指定 WiFi）
- Multicast（局域网设备搜索，需证书支持）

## 四、SDK 初始化

### 国内客户
```objc
// 基线平台（不需要设置域名）
[EZOpenSDK initLibWithAppKey:@"Your AppKey"];

// 私有云平台（额外设置域名）
[EZOpenSDK initLibWithAppKey:@"Your AppKey" url:@"Your apiUrl" authUrl:nil];
```

### 海外客户
```objc
[EZGlobalSDK initLibWithAppKey:@"Your AppKey" url:@"Your apiUrl" authUrl:nil];
```

## 五、SDK 授权

### 模式一：AccessToken 授权（推荐）
```objc
[EZOpenSDK setAccessToken:@"Your accessToken"];
```

验证 accessToken 有效性：
```objc
[EZOpenSDK getDeviceList:0 pageSize:1 completion:^(NSArray *deviceList, NSInteger totalCount, NSError *error) {
    if (error) {
        // accessToken 无效
    } else {
        // accessToken 有效
    }
}];
```

### 模式二：小权限 TKToken 授权
此模式默认关闭，需在 initLib 之前开启：
```objc
[EZOpenSDK enableSDKWithTKToken:YES];
[EZOpenSDK initLibWithAppKey:@"Your AppKey"];
```

授权流程：
```objc
[EZOpenSDK setHttpToken:@"Your httpTKToken"];
```

TKToken 类型说明：
| token 类型 | 说明 | 使用场景 |
|-----------|------|----------|
| httpToken | 非设备类小权限 token | 入参不含设备序列号的 API |
| deviceToken | 设备级小权限 token | 入参含设备序列号、不含通道号的 API |
| deviceGlobalToken | 通道级 global token | SDK 内部封装接口，取流必须 |
| deviceVideoToken | 通道级 video token | 入参含设备序列号和通道号的 API |
| previewStreamToken | 预览 token | startRealPlay 之前设置 |
| playbackStreamToken | 回放 token | startPlaybackFromDevice 之前设置 |
| talkStreamToken | 对讲 token | startVoiceTalk 之前设置 |

设置方法：
```objc
[EZOpenSDK setHttpToken:httpTKToken];
[EZOpenSDK setDeviceTokenForDeviceSerial:deviceSerial deviceToken:deviceToken];
[EZOpenSDK setDeviceTokenForDeviceSerial:deviceSerial cameraNo:cameraNo deviceGlobalToken:globalToken deviceVideoToken:videoToken];
[player setStreamToken:streamToken];
```

注意事项：
- IPC 设备对讲使用 0 通道，对讲 streamToken 生成请使用 0 通道
- 双目设备取流使用交织流 0 通道，取流 streamToken 生成请使用 0 通道，deviceGlobalToken 和 deviceVideoToken 保持使用 1 通道

## 六、启动时序与就绪判断

### 核心约束

**所有依赖 SDK 的业务网络请求（getDeviceList、startRealPlay 等）必须在 `initLibWithAppKey` 完成 且 `setAccessToken` 完成之后再发起。**

`initLibWithAppKey` 本身是同步调用，但在实际 App 启动场景中，以下并发情况可能导致时序颠倒：
- `initLibWithAppKey` 被 `dispatch_async(主队列)` 包裹 → 推迟到下一个 runloop 执行
- Scene 连接后立即创建设备列表页 → `viewDidLoad` 同步触发 `getDeviceList`
- 结果：`getDeviceList` 先于 `initLibWithAppKey` 执行 → SDK 未初始化/无 Token → 返回空数据或错误

### 推荐模式：就绪状态 + 通知

在 SDK 管理单例中维护一个「就绪」标志，当 initLib 完成且 Token 已设置后广播就绪通知：

```objc
// EZSDKManager.h
extern NSString * const EZSDKManagerDidBecomeReadyNotification;
@property (nonatomic, assign, readonly) BOOL isReady;
```

```objc
// EZSDKManager.m — 在 initLib 成功 + setAccessToken 之后调用
- (void)markReadyAndNotify {
    if (self.isReady) return;
    self.isReady = YES;
    dispatch_async(dispatch_get_main_queue(), ^{
        [[NSNotificationCenter defaultCenter] postNotificationName:EZSDKManagerDidBecomeReadyNotification
                                                            object:nil];
    });
}
```

业务页面（如设备列表）在 `viewDidLoad` 中做判断：

```objc
EZSDKManager *sdk = [EZSDKManager sharedInstance];
if (sdk.isReady) {
    [self.listModule loadFirstPage]; // SDK 已就绪，直接加载
} else {
    // 等待就绪通知
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(handleSDKDidBecomeReady:)
                                                 name:EZSDKManagerDidBecomeReadyNotification
                                               object:nil];
}
```

### 常见错误

| 错误写法 | 后果 |
|----------|------|
| `viewDidLoad` 中无条件调用 `getDeviceList` | SDK 未初始化时返回空列表或错误 |
| 把 `initLibWithAppKey` 包进 `dispatch_async(主队列)` 又没有通知机制 | 与 UI 加载形成竞态 |
| 在 `application:didFinishLaunchingWithOptions:` 之前使用 SDK 接口 | 崩溃或未定义行为 |

### 正确的启动顺序（完整）

```
AppDelegate didFinishLaunchingWithOptions
  ├── setDebugLogEnable / setDebugStreamEnable   （可选，必须在 initLib 之前）
  ├── initLibWithAppKey                          （同步，立即返回）
  ├── setAccessToken                             （SDK 初始化后设置）
  └── 广播就绪通知 → 业务页面监听后发起网络请求
```

## 七、SDK 账号切换
```objc
// 第一步：退出前一个账号
[EZOpenSDK logout:^(NSError *error) {}];
// 第二步：重新初始化
[EZOpenSDK initLibWithAppKey:@"Your new AppKey"];
// 第三步：重新授权
[EZOpenSDK setAccessToken:@"Your new accessToken"];
```
必须按顺序完成，否则会有异常。

## 八、SDK 调试模式

应用发布时务必关闭，否则沙盒中会产生大量码流文件。

```objc
// 日志开关（建议 debug 打开，release 关闭），在初始化之前调用
[EZOpenSDK setDebugLogEnable:YES]; // debug
[EZOpenSDK setDebugLogEnable:NO];  // release

// 码流缓存开关（release 必须关闭），在初始化之前调用
[EZOpenSDK setDebugStreamEnable:YES]; // debug
[EZOpenSDK setDebugStreamEnable:NO];  // release
```

码流文件路径：
- 视频码流：`应用沙盒/Documents/ezopensdk/EZSavedStreamData`
- 对讲码流：`应用沙盒/Documents/ezopensdk/EZSavedIntercomData`

## 九、登录页面与中间页

SDK 提供了内置的登录页面和功能页面：
```objc
// 打开登录页面
[EZOpenSDK openLoginPage:^(EZAccessToken *accessToken) {}];

// 打开云存储页面
[EZOpenSDK openCloudPage:deviceSerial channelNo:cameraNo];

// 打开修改密码页面
[EZOpenSDK openChangePasswordPage:^(NSError *error) {}];
```

## 十、获取 SDK 版本号
```objc
NSString *version = [EZOpenSDK getVersion];
```

## 十一、各大区域 ApiUrl

| 区域 | ApiUrl |
|------|--------|
| 国内 | https://open.ys7.com |
| 海外 | https://open.ezvizlife.com |
| 俄罗斯 | https://irusopen.ezvizru.com |
| 亚洲 | https://isgpopen.ezvizlife.com |
| 北美洲 | https://iusopen.ezvizlife.com |
| 南美洲 | https://isaopen.ezvizlife.com |
| 欧洲 | https://ieuopen.ezvizlife.com |
