用于自行部署或者本地导入
支持真机arm64、模拟器x86架构和模拟器arm64架构
内含真机arm64对应的符号表dSYMs,用于解析崩溃栈
内含Privacyinfo.xcprivacy用于苹果合规要求
SDK依赖的所有资源,包括UI组件依赖的显示资源和UI组件依赖的素材资源
BuiltInResource目录下存放的是UI组件依赖的显示资源,包括icon、文案等
EffectResource目录下存放的是UI组件依赖的素材资源,详情参考EffectOne iOS 功能详解 V1.8.1中资源导入与配置部分的介绍
License目录下存放的是离线的授权证书
基于swift语言的示例代码
EffectOneModule.swift 演示了SDK各个功能接口的调用
EOCustomRequestBuilderImpl.swift 演示了从服务端获取资源接口的定义
EOCustomRsourcePickerImpl.swift 演示了如何自定义相册配置
EOExportUI 是视频导出功能的实现代码
CutSame 是剪同款UI跟高光成片UI实现代码
DVEAlbum 是相册的实现代码
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) } }