用于自行部署或者本地导入
支持真机arm64、模拟器x86架构和模拟器arm64架构
内含真机arm64对应的符号表dSYMs,用于解析崩溃栈
内含Privacyinfo.xcprivacy用于苹果合规要求
SDK依赖的所有资源,包括UI组件依赖的显示资源和UI组件依赖的素材资源
BuiltInResource目录下存放的是UI组件依赖的显示资源,包括icon、文案等
EffectResource目录下存放的是UI组件依赖的素材资源,详情参考EffectOne iOS 功能详解中资源导入与配置部分的介绍
License目录下存放的是离线的授权证书
基于swift语言的示例代码
EffectOneModule.swift 演示了SDK各个功能接口的调用
EOCustomRequestBuilderImpl.swift 演示了从服务端获取资源接口的定义
EOExportUI 是视频导出功能的实现代码
CutSame 是剪同款UI跟高光成片UI实现代码
EOCustomRequestBuilderImpl.swift 源码
import UIKit import EffectOneKit class EOCustomRequestBuilderImpl: NSObject, EOResourceRequestBuilderProtocol { // {zh} 获取config {en} Get config func buildConfigURLRequest(byPanelKey panelKey: String?, resourceInfo resource: [AnyHashable : Any]?) -> URLRequest? { let url = "https://cvsdk.tos-cn-beijing.volces.com/resource/eo/1.3.0/EffectResource/Panel_configs/\(panelKey ?? "").json" var request = URLRequest(url: URL(string: url)!) request.httpMethod = "GET" return request } // {zh} 获取icon {en} Get icon func iconUrlString(_ iconName: String?, panelKey: String?) -> String? { return panelKey == EOPanelKeyCutSame ? "https://cvsdk.tos-cn-beijing.volces.com/resource/eo/1.3.0/EffectResource/Resource_icons/\(iconName ?? "")" : "" } // {zh} 获取视频 {en} Get video func videoUrlString(_ videoName: String?, panelKey: String?) -> String? { return "https://cvsdk.tos-cn-beijing.volces.com/resource/eo/1.3.0/EffectResource/Resource_videos/\(videoName ?? "")" } // {zh} 获取模板 {en} Get template func buildResourceDownloadRequest(byRelativePath relativePath: String?, isModel: Bool, panelKey: String?, resourceInfo resource: [AnyHashable : Any]?) -> URLRequest? { let url = "https://cvsdk.tos-cn-beijing.volces.com/resource/eo/1.3.0/RemoteResource\(relativePath ?? "")" return URLRequest(url: URL(string: url)!) } }
EffectOneModule.swift 源码
import Foundation import UIKit import Toast // {zh} 需要引入EffectOneKit {en} Need to introduce EffectOneKit import EffectOneKit // {zh} 需要引入导出组件 {en} Export component needs to be introduced import EOExportUI // {zh} 需要引入剪同款组件 {en} You need to introduce and cut the same components. import CutSameUIIF class EffectOneModule : NSObject { let authFileName : String = "com.volcengine.effectone.inhouse.licbag" // {zh} 存储鉴权状态 {en} store authentication state var isAuthSucceeded: Bool = false // {zh} 存储被使用的VC,用于弹出界面 {en} Store the used VC for the pop-up interface var parentVC: UIViewController // {zh} 暂存拍摄组件的应用,用于退出编辑组件时,隐藏相册选图页面 {en} The application that temporarily stores the shooting component is used to hide the album selection page when exiting the editing component private weak var recorderVC: UIViewController? init(parentVC: UIViewController) { self.parentVC = parentVC } // {zh} 启动鉴权 {en} enable authentication public func makeAuth(){ let config = EOAuthorizationConfig { [self] initializer in // {zh} 鉴权方式是离线鉴权 {en} The authentication method is offline authentication. initializer.isOnline = false // {zh} 设置离线证书所在的路径 {en} Set the path where the offline certificate is located initializer.licensePathForOffline = self.localBundle().path(forResource: self.authFileName, ofType: nil, inDirectory: "License")! } // {zh} 开始鉴权 {en} Start authentication EOAuthorization.sharedInstance().makeAuth(with: config) {isSuccess, errMsg in self.isAuthSucceeded = isSuccess if !self.isAuthSucceeded { // {zh} 鉴权失败打印错误码 {en} Authentication failed to print error code print(errMsg) } else { // {zh} 鉴权成功,初始化EOSDK及资源配置 {en} Authentication successful, initialize EOSDK and resource allocation EOSDK.initSDK { [weak self] in // {zh} 设置资源根路径 {en} Set the resource root path EOSDK.setResourceBaseDir(EOSDK.getEODocumentRootDir() + "/download") // {zh} 设置配置文件根路径 {en} Set the configuration file root path EOSDK.setResourceDefaultBuiltInConfig(EOSDK.defaultPanelConfigDir(self!.localBundle().bundlePath)) // {zh} 设置内置资源路径 {en} Set built-in resource path EOSDK.setBuiltInResourceDir(EOSDK.defaultResourceDir(self!.localBundle().bundlePath)) // {zh} 注册剪同款获取资源协议 {en} Register to cut the same item to get the resource agreement EOInjectContainer.shared().resourceRequestBuilderImpl = EOCustomRequestBuilderImpl() // {zh} 设置从服务端获取 {en} Set fetch from server level EOSDK.useRemoteConfig(true, useRemoteResource: true) } } } } // {zh} 进入基础编辑组件的源 {en} Access to the source of the basic editing component enum EditorParams { case normal(EORecordInfo) case draft(EOSDKDraftModel) } // {zh} 获取资源根目录 {en} Get resource root directory private func localBundle() -> Bundle { Bundle(path: Bundle.main.path(forResource: "EOLocalResources", ofType: "bundle") ?? "") ?? Bundle.main } private func showErrorAlert(_ err: NSError, presenter: UIViewController) { let msg = "\(NSLocalizedString("eo_home_error_alert_title", comment: ""))\(err.domain):\(err.code)" let alert = UIAlertController(title: nil, message: msg, preferredStyle: .alert) alert.addAction(UIAlertAction(title: NSLocalizedString("eo_home_error_alert_ok", comment: ""), style: .cancel)) presenter.present(alert, animated: true) } public func showToast(_ text: String) { guard let window = UIApplication.shared.keyWindow else { return } window.makeToast(text, duration: 1.5, position:CSToastPositionCenter) } private func topMostViewController() -> UIViewController? { var topViewController: UIViewController? let window = UIApplication.shared.keyWindow let rootViewController = window?.rootViewController if let tabBar = rootViewController as? UITabBarController { topViewController = tabBar.selectedViewController } else { topViewController = rootViewController; } if let nav = topViewController as? UINavigationController { topViewController = nav.topViewController } while topViewController?.presentedViewController != nil { topViewController = topViewController?.presentedViewController if let nav = topViewController as? UINavigationController { topViewController = nav.topViewController } } return topViewController } func showAlert(withTitle title: String, message: String, cancelTitle: String?, confirmTitle: String?, cancelHandler: (() -> Void)?, confirmHandler: (() -> Void)?) { let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) if let cancelTitle = cancelTitle { let cancelAction = UIAlertAction(title: cancelTitle, style: .cancel) { _ in cancelHandler?() } alert.addAction(cancelAction) } if let confirmTitle = confirmTitle { let confirmAction = UIAlertAction(title: confirmTitle, style: .default) { _ in confirmHandler?() } alert.addAction(confirmAction) } self.parentVC.present(alert, animated: true, completion: nil) } } extension EffectOneModule { // {zh} 启动基础编辑组件 {en} Start the basic editing component public func showRecorderViewController() { if !self.isAuthSucceeded { self.showToast("authority is failed!,please check it.") return } // {zh} 构造拍摄组件默认配置 {en} Construct the default configuration of the shooting component let config = EORecorderConfig { initializer in // config recorder if needed initializer.configRecorderViewController { recorderVCInitializer in // config recoderViewController if needed } } // {zh} 显示拍摄页面 {en} Show the shooting page EORecorderViewController.startRecorder(with: config, presenter: self.parentVC, delegate: self) { [weak self] error in if let err = error as? NSError, let presenter = self?.parentVC { self?.showErrorAlert(err, presenter: presenter) } } } // {zh} 视频合拍 {en} Video co-production public func showDuetViewController() { if !self.isAuthSucceeded { self.showToast("authority is failed!,please check it.") return } // {zh} 检测相册权限 {en} Detect album permissions EOAlbumHelper.eoCheckAlbumAuthorized { [weak self] in // {zh} 使用EOSDK中相册组件来实现选图功能 {en} Use the album component in EOSDK to realize the picture selection function guard let resourcePicker = EOInjectContainer.shared().resourcePickerSharedImpl else { return } // {zh} 选择相册素材结束 {en} Select album material to end resourcePicker.pickVideoForDuet(completion: { [weak self] resources, error, cancel in if let resorce = resources.first { // {zh} 进入合拍页面 {en} Enter the match page self?.gotoDuetViewController(withVideoURL: resorce.url!) } }) } restrictedOrDenied: { [weak self] in self?.showAlert(withTitle: NSLocalizedString("eo_home_camera_album_unauth_alert_title", comment: ""), message: NSLocalizedString("eo_home_camera_album_unauth_alert_message", comment: ""), cancelTitle: NSLocalizedString("eo_home_camera_unauth_alert_cancel", comment: ""), confirmTitle: NSLocalizedString("eo_home_camera_unauth_alert_confirm", comment: ""), cancelHandler: nil, confirmHandler: { if #available(iOS 10.0, *) { UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:]) { success in // Completion handler } } else { UIApplication.shared.openURL(URL(string: UIApplication.openSettingsURLString)!) } }) } } // {zh} 进入合拍页面 {en} Enter the match page public func gotoDuetViewController(withVideoURL videoURL:URL) { if !self.isAuthSucceeded { self.showToast("authority is failed!,please check it.") return } let config = EORecorderConfig { initializer in // config recorder if needed initializer.configRecorderViewController { recorderVCInitializer in // config recoderViewController if needed recorderVCInitializer.musicBarHidden = true let sideConfig = EORecorderSideBarConfig() sideConfig.itemKeys = [EORecordSideBarItemKey.barItemRotateCameraKey, EORecordSideBarItemKey.barItemFlashKey, EORecordSideBarItemKey.barItemMicKey, EORecordSideBarItemKey.barItemDuetLayoutKey, EORecordSideBarItemKey.barItemTimerKey, EORecordSideBarItemKey.barItemFiltersKey, EORecordSideBarItemKey.barItemBeautyKey, EORecordSideBarItemKey.barItemSpeedKey,] recorderVCInitializer.sideBarConfig = sideConfig } } EODuetViewController.startDuet(with: config, presenter: self.parentVC,duetVideoURL:videoURL, delegate: self, completion: { [weak self] error in if let err = error as? NSError, let presenter = self?.parentVC { self?.showErrorAlert(err, presenter: presenter) } }) } // {zh} 进入剪同款页面 {en} Enter CutSame page func showCutSameController() { CutSameRouter.toCutSameCategoryVC() } } extension EffectOneModule: EORecorderViewControllerDelegate { // {zh} 显示相册页面 {en} Show album page func recorderViewControllerDidTapAlbum(_ recorderViewController: EORecorderViewController) { // {zh} 暂存拍摄组件的应用,用于退出编辑组件时,隐藏相册选图页面 {en} The application that temporarily stores the shooting component is used to hide the album selection page when exiting the editing component self.recorderVC = recorderViewController // {zh} 使用EOSDK中相册组件来实现选图功能 {en} Use the album component in EOSDK to realize the picture selection function guard let resourcePicker = EOInjectContainer.shared().resourcePickerSharedImpl else { return } recorderViewController.pausePreview() resourcePicker.pickResourcesFromRecorder { [weak self] resources, error, cancel in guard !resources.isEmpty else { return; } let info = EORecordInfo() info.mediaAssets = resources info.source = .album // {zh} 完成选图后显示编辑页面 {en} Show the edit page after completing the selection if let presenter = self?.topMostViewController() { self?.showEditorViewController(.normal(info), presenter: presenter) } } } // {zh} 完成拍摄后,跳转进入编辑页面 {en} After finishing shooting, jump to the editing page func recorderViewController(_ recorderViewController: EORecorderViewController, didFinishRecordingMediaWith info: EORecordInfo) { // {zh} 暂存拍摄组件的应用,用于退出编辑组件时,隐藏相册选图页面 {en} The application that temporarily stores the shooting component is used to hide the album selection page when exiting the editing component self.recorderVC = recorderViewController recorderViewController.pausePreview() // {zh} 进入基础编辑 {en} Enter basic editing self.showEditorViewController(.normal(info), presenter: recorderViewController) } } extension EffectOneModule { // {zh} 拍摄完成、从相册或者草稿进入基础编辑 {en} The shooting is completed or enter the basic editing from the album. func showEditorViewController(_ params: EditorParams, presenter: UIViewController) { if !self.isAuthSucceeded { self.showToast("authority is failed!,please check it.") return } // {zh} 构造编辑组件默认配置 {en} Construct editing component default configuration let config = EOEditorConfig { initializer in} // {zh} 设置编辑组件需要的输入参数 {en} Set the input parameters required by the editing component let sceneConfig = EOEditorSceneConfig() switch params { case .normal(let info):// {zh} 从拍摄或相册进入基础编辑 {en} Go from shooting or album to basic editing sceneConfig.resources = info.mediaAssets sceneConfig.backgroundMusic = info.backgroundMusic sceneConfig.coverImage = info.coverImage sceneConfig.previewContentMode = info.source == .camera ? .aspectFill : .aspectFit sceneConfig.fromType = info.source == .camera ? "camera" : "album" // {zh} 判断是否合拍 {en} Determine whether it is in tune if info.isDuet == true { sceneConfig.editorPreviewForType = .duetEditor sceneConfig.duetMusic = info.duetUrl } case .draft(let model):// {zh} 从草稿进入基础编辑 {en} From draft to basic editing sceneConfig.draftModel = model } // {zh} 显示编辑页面 {en} Show edit page EOVideoEditorViewController.startEditor(with: config, sceneConfig: sceneConfig, presenter: presenter, delegate: self) { [weak self, weak presenter] error in if let err = error as? NSError, let vc = presenter { self?.showErrorAlert(err, presenter: vc) } }; } // {zh} 启动草稿组件方法 {en} Start draft component method public func showDraftViewController() { if !self.isAuthSucceeded { self.showToast("authority is failed!,please check it.") return } // {zh} 显示草稿组件 {en} Show draft component EODraftBoxController.presentDraftVCDelegate(self) } //{zh} 调用高光成片 {en} Call highlight film public func showHighLightViewController() { if !self.isAuthSucceeded { self.showToast("authority is failed!,please check it.") return } _showHighLightViewController() } } // MARK: highLightFirm extension EffectOneModule { public func _showHighLightViewController() { if !self.isAuthSucceeded { self.showToast("authority is failed!,please check it.") return } EOAlbumHelper.eoCheckAlbumAuthorized {// {zh} 相册权限已打开 {en} Album permissions are turned on // {zh} 启动高光成片 {en} Start highlight film EOHighLightManager.toHighLight() } restrictedOrDenied: { [weak self] in self?.showAlert(withTitle: NSLocalizedString("eo_home_camera_album_unauth_alert_title", comment: ""), message: NSLocalizedString("eo_home_camera_album_unauth_alert_message", comment: ""), cancelTitle: NSLocalizedString("eo_home_camera_unauth_alert_cancel", comment: ""), confirmTitle: NSLocalizedString("eo_home_camera_unauth_alert_confirm", comment: ""), cancelHandler: nil, confirmHandler: { if #available(iOS 10.0, *) { UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:]) { success in // Completion handler } } else { UIApplication.shared.openURL(URL(string: UIApplication.openSettingsURLString)!) } }) } } } extension EffectOneModule: EOVideoEditorViewControllerDelegate { func videoEditorViewControllerDidCancel(_ videoEditorViewController: EOVideoEditorViewController) { if let recorder = recorderVC { // {zh} 隐藏相册选图页面 {en} Hide album selection page recorder.dismiss(animated: true, completion: nil) } else { // {zh} 当从草稿进入编辑页面再退出编辑时,隐藏编辑页面 {en} Hide the edit page when entering the edit page from the draft and then exiting the edit videoEditorViewController.dismiss(animated: true, completion: nil) } } // {zh} 点击下一步按钮,启动导出页面 {en} Click the Next button to launch the export page func videoEditorViewControllerTapNext(_ exportModel: EOExportModel, presentVC viewController: UIViewController) { EOExportViewController.startExport(with: exportModel, presentVC: viewController) } } extension EffectOneModule: EODraftBoxControllerDelegate { // {zh} 从草稿进入基础编辑组件 {en} Moving from draft to basic editing components func draftBoxController(_ draftBoxController: EODraftBoxController, didSelectDraft draftModel: EOSDKDraftModel) { self.showEditorViewController(.draft(draftModel), presenter: draftBoxController) } }