需要添加直播接口
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
import DCloudUTSFoundation
|
||||
import RTCRoomEngine
|
||||
|
||||
class ExperimentalApiInvoker {
|
||||
public static let shared = ExperimentalApiInvoker()
|
||||
private let jsonDecoder = JSONDecoder()
|
||||
// const data = { "api": "setTestEnvironment", "params": { "enableRoomTestEnv": true } } // 设置 IM 测试环境
|
||||
// const data = { "api": "setLocalVideoMuteImage", "params": { "image": "filePath" } } // 设置垫片
|
||||
// const giftData = { "api": "setCurrentLanguage", "params": { "language" : "en"} } // 礼物功能设置语言
|
||||
public func callExperimentalAPI(
|
||||
_ jsonString: String, callback: @escaping TUIExperimentalAPIResponseBlock
|
||||
) {
|
||||
guard let data = jsonString.data(using: .utf8) else {
|
||||
callback("Invalid JSON string")
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
let requestData = try jsonDecoder.decode(RequestData.self, from: data)
|
||||
if requestData.api == "setLocalVideoMuteImage" {
|
||||
setLocalVideoMuteImage(data: requestData, callback: callback)
|
||||
return
|
||||
}
|
||||
TUIRoomEngine.sharedInstance().callExperimentalAPI(
|
||||
jsonStr: jsonString, callback: callback)
|
||||
} catch {
|
||||
callback("JSON parsing error")
|
||||
}
|
||||
}
|
||||
|
||||
private func setLocalVideoMuteImage(
|
||||
data: RequestData, callback: @escaping TUIExperimentalAPIResponseBlock
|
||||
) {
|
||||
guard let filePath = data.params?.image, !filePath.isEmpty else {
|
||||
callback("setLocalVideoMuteImage: filePath is empty")
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
let image = UIImage(contentsOfFile: filePath)
|
||||
TUIRoomEngine.sharedInstance().setLocalVideoMuteImage(image: image)
|
||||
} catch {
|
||||
callback("setLocalVideoMuteImage: unexpected error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct RequestData: Codable {
|
||||
let api: String
|
||||
let params: Params?
|
||||
}
|
||||
|
||||
// 不要修改数据,每个数据对应一个关键字
|
||||
struct Params: Codable {
|
||||
let image: String?
|
||||
}
|
||||
117
uni_modules/tuikit-atomic-x/utssdk/app-ios/swift/JsonUtil.swift
Normal file
117
uni_modules/tuikit-atomic-x/utssdk/app-ios/swift/JsonUtil.swift
Normal file
@@ -0,0 +1,117 @@
|
||||
import AtomicXCore
|
||||
import Foundation
|
||||
|
||||
public class JsonUtil {
|
||||
public static func toJson(_ object: Any) -> String? {
|
||||
guard let jsonData = try? JSONSerialization.data(withJSONObject: object, options: .fragmentsAllowed),
|
||||
let jsonString = String(data: jsonData, encoding: .utf8)
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
return jsonString
|
||||
}
|
||||
|
||||
public static func toCompletionClosure(
|
||||
success: (() -> Void)?,
|
||||
failure: ((_ code: Int, _ message: String) -> Void)?
|
||||
) -> CompletionClosure {
|
||||
return { result in
|
||||
switch result {
|
||||
case .success:
|
||||
success?()
|
||||
case .failure(let errorInfo):
|
||||
failure?(errorInfo.code, errorInfo.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static func toLiveInfoCompletionClosure(
|
||||
success: ((_ liveInfo: String) -> Void)?,
|
||||
failure: ((_ code: Int, _ message: String) -> Void)?
|
||||
) -> LiveInfoCompletionClosure {
|
||||
return { result in
|
||||
switch result {
|
||||
case .success(let liveInfo):
|
||||
let dict = TypeConvert.convertLiveInfoToDic(liveInfo: liveInfo)
|
||||
if let json = JsonUtil.toJson(dict) {
|
||||
success?(json)
|
||||
} else {
|
||||
success?("")
|
||||
}
|
||||
case .failure(let errorInfo):
|
||||
failure?(errorInfo.code, errorInfo.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static func toStopLiveCompletionClosure(
|
||||
success: ((_ tuiLiveStatisticsData: String) -> Void)?,
|
||||
failure: ((_ code: Int, _ message: String) -> Void)?
|
||||
) -> StopLiveCompletionClosure {
|
||||
return { result in
|
||||
switch result {
|
||||
case .success(let statisticsData):
|
||||
var dict: [String: Any] = [
|
||||
"totalViewers": statisticsData.totalViewers,
|
||||
"totalGiftsSent": statisticsData.totalGiftsSent,
|
||||
"totalGiftCoins": statisticsData.totalGiftCoins,
|
||||
"totalUniqueGiftSenders": statisticsData.totalUniqueGiftSenders,
|
||||
"totalLikesReceived": statisticsData.totalLikesReceived,
|
||||
// "totalMessageCount": statisticsData.totalMessageCount,
|
||||
// "liveDuration": statisticsData.liveDuration,
|
||||
]
|
||||
if let json = JsonUtil.toJson(dict) {
|
||||
success?(json)
|
||||
} else {
|
||||
success?("")
|
||||
}
|
||||
case .failure(let errorInfo):
|
||||
failure?(errorInfo.code, errorInfo.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static func toMetaDataCompletionClosure(
|
||||
success: ((_ metaData: String) -> Void)?,
|
||||
failure: ((_ code: Int, _ message: String) -> Void)?
|
||||
) -> MetaDataCompletionClosure {
|
||||
return { result in
|
||||
switch result {
|
||||
case .success(let data):
|
||||
success?(JsonUtil.toJson(data) ?? "")
|
||||
case .failure(let errorInfo):
|
||||
failure?(errorInfo.code, errorInfo.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static func toBattleRequestClosure(
|
||||
success: ((_ battleInfo: String, _ resultMap: String) -> Void)?,
|
||||
failure: ((_ code: Int, _ message: String) -> Void)?
|
||||
) -> BattleRequestClosure {
|
||||
return { result in
|
||||
switch result {
|
||||
case .success(let data):
|
||||
let (battleInfo, resultMap) = data
|
||||
|
||||
var battleInfoDict: [String: Any] = [
|
||||
"battleID": battleInfo.battleID,
|
||||
"config": [
|
||||
"duration": battleInfo.config.duration,
|
||||
"needResponse": battleInfo.config.needResponse,
|
||||
"extensionInfo": battleInfo.config.extensionInfo,
|
||||
],
|
||||
"startTime": battleInfo.startTime,
|
||||
"endTime": battleInfo.endTime,
|
||||
]
|
||||
|
||||
let battleInfoJson = JsonUtil.toJson(battleInfoDict) ?? ""
|
||||
let resultMapJson = JsonUtil.toJson(resultMap) ?? ""
|
||||
|
||||
success?(battleInfoJson, resultMapJson)
|
||||
case .failure(let errorInfo):
|
||||
failure?(errorInfo.code, errorInfo.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import AtomicXCore
|
||||
import DCloudUTSFoundation
|
||||
import RTCRoomEngine
|
||||
import UIKit
|
||||
|
||||
public class LiveRenderView: UIView {
|
||||
private let cornerRadius: CGFloat = 18 // 圆角半径
|
||||
private var nativeViewType = CoreViewType.playView
|
||||
|
||||
// MARK: - 初始化
|
||||
override init(frame: CGRect = .zero) {
|
||||
super.init(frame: frame)
|
||||
commonInit()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
commonInit()
|
||||
}
|
||||
|
||||
private func commonInit() {
|
||||
self.layer.cornerRadius = cornerRadius
|
||||
self.layer.masksToBounds = true
|
||||
self.layer.shouldRasterize = true
|
||||
self.layer.rasterizationScale = UIScreen.main.scale
|
||||
}
|
||||
|
||||
// 保证圆角在布局变化时始终生效
|
||||
public override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
self.layer.cornerRadius = cornerRadius
|
||||
self.layer.masksToBounds = true
|
||||
}
|
||||
|
||||
public func updateViewType(_ viewType: Any) {
|
||||
guard viewType != nil else {
|
||||
return
|
||||
}
|
||||
guard viewType is String else {
|
||||
return
|
||||
}
|
||||
if let viewTypeStr = viewType as? String {
|
||||
if viewTypeStr == "PUSH_VIEW" {
|
||||
nativeViewType = CoreViewType.pushView
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 渲染视图更新
|
||||
public func updateRenderView(_ liveID: Any) {
|
||||
console.log("iOS-LiveRenderView, updateRenderView, liveID: ", liveID)
|
||||
guard liveID != nil else {
|
||||
console.log("iOS-LiveRenderView, updateRenderView: liveID is empty")
|
||||
return
|
||||
}
|
||||
guard liveID is String else {
|
||||
console.log("iOS-LiveRenderView, updateRenderView: liveID is not String")
|
||||
return
|
||||
}
|
||||
|
||||
subviews.forEach { $0.removeFromSuperview() }
|
||||
|
||||
console.log("iOS-LiveRenderView, updateRenderView, viewType: ", self.nativeViewType)
|
||||
if let liveIDStr = liveID as? String , !liveIDStr.isEmpty {
|
||||
let renderView = LiveCoreView(viewType: self.nativeViewType, frame : .zero)
|
||||
renderView.setLiveID(liveIDStr)
|
||||
renderView.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(renderView)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
renderView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
renderView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||
renderView.topAnchor.constraint(equalTo: topAnchor),
|
||||
renderView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
119
uni_modules/tuikit-atomic-x/utssdk/app-ios/swift/LogUpload.swift
Normal file
119
uni_modules/tuikit-atomic-x/utssdk/app-ios/swift/LogUpload.swift
Normal file
@@ -0,0 +1,119 @@
|
||||
//
|
||||
// LogUpload.swift
|
||||
// Created by reneechai on 2025/9/17.
|
||||
//
|
||||
import UIKit
|
||||
import Foundation
|
||||
import DCloudUTSFoundation
|
||||
|
||||
public class FileModel {
|
||||
var fileName: String
|
||||
var filePath: String
|
||||
|
||||
init(fileName: String, filePath: String) {
|
||||
self.fileName = fileName
|
||||
self.filePath = filePath
|
||||
}
|
||||
}
|
||||
|
||||
public class LogUpload {
|
||||
public static let shared = LogUpload()
|
||||
private var fileModelArray: [FileModel] = []
|
||||
|
||||
public func shareLog(_ row: Int){
|
||||
if row < self.fileModelArray.count {
|
||||
let fileModel = self.fileModelArray[row]
|
||||
let logPath = fileModel.filePath
|
||||
let shareObj = URL(fileURLWithPath: logPath)
|
||||
/// file:///var/mobile/Containers/Data/Application/3173F3A2-91A4-44B3-AC13-DBBD02C2DDBE/Documents/log/LiteAV_C_20250820-19243.clog
|
||||
// print("LogUpload: shareObj: \(shareObj)")
|
||||
console.log("LogUpload, shareObj:", shareObj)
|
||||
|
||||
let activityView = UIActivityViewController(activityItems: [shareObj], applicationActivities: nil)
|
||||
guard let curVC = getCurrentWindowViewController() else { return }
|
||||
curVC.present(activityView, animated: true) {}
|
||||
}
|
||||
}
|
||||
|
||||
public func fetchLogfileList() -> [FileModel] {
|
||||
var fileArray: [FileModel] = []
|
||||
guard let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first
|
||||
else { return []}
|
||||
|
||||
let logPath = (documentsPath as NSString).appendingPathComponent("log")
|
||||
guard let libraryPath = NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true).first
|
||||
else { return []}
|
||||
|
||||
let cachePath = (libraryPath as NSString).appendingPathComponent("Caches/com_tencent_imsdk_log")
|
||||
let liteAVSDKClogFiles = getFilesFromDirectory(atPath: logPath, withExtension: ".clog")
|
||||
fileArray += liteAVSDKClogFiles
|
||||
let liteAVSDKXlogFiles = getFilesFromDirectory(atPath: logPath, withExtension: ".xlog")
|
||||
fileArray += liteAVSDKXlogFiles
|
||||
let imXlogFiles = getFilesFromDirectory(atPath: cachePath, withExtension: ".xlog")
|
||||
fileArray += imXlogFiles
|
||||
|
||||
fileModelArray = fileArray
|
||||
console.log("LogUpload, fileModelArray count:", fileModelArray.count)
|
||||
// 打印详细日志
|
||||
// printLogFile()
|
||||
return fileModelArray
|
||||
}
|
||||
|
||||
private func printLogFile(){
|
||||
if fileModelArray.isEmpty {
|
||||
print("LogUpload: fileModelArray 为空,未找到任何日志文件")
|
||||
} else {
|
||||
print("LogUpload: fileModelArray 共 \(fileModelArray.count) 个日志文件:")
|
||||
fileModelArray.enumerated().forEach { index, file in
|
||||
print("LogUpload: :\(file.fileName)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 解析日志文件夹
|
||||
private func getFilesFromDirectory(atPath path: String, withExtension fileExtension: String) -> [FileModel] {
|
||||
let fileManager = FileManager.default
|
||||
var files: [FileModel] = []
|
||||
|
||||
do {
|
||||
let contents = try fileManager.contentsOfDirectory(atPath: path)
|
||||
for fileName in contents {
|
||||
if fileName.hasSuffix(fileExtension) {
|
||||
let filePath = (path as NSString).appendingPathComponent(fileName)
|
||||
let file = FileModel(fileName: fileName, filePath: filePath)
|
||||
files.append(file)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
print("Error: \(error.localizedDescription)")
|
||||
}
|
||||
|
||||
return files
|
||||
}
|
||||
|
||||
/// 获取 iOS 分享面板
|
||||
private func getCurrentWindowViewController() -> UIViewController? {
|
||||
var keyWindow: UIWindow?
|
||||
for window in UIApplication.shared.windows {
|
||||
if window.isMember(of: UIWindow.self), window.isKeyWindow {
|
||||
keyWindow = window
|
||||
break
|
||||
}
|
||||
}
|
||||
guard let rootController = keyWindow?.rootViewController else {
|
||||
return nil
|
||||
}
|
||||
func findCurrentController(from vc: UIViewController?) -> UIViewController? {
|
||||
if let nav = vc as? UINavigationController {
|
||||
return findCurrentController(from: nav.topViewController)
|
||||
} else if let tabBar = vc as? UITabBarController {
|
||||
return findCurrentController(from: tabBar.selectedViewController)
|
||||
} else if let presented = vc?.presentedViewController {
|
||||
return findCurrentController(from: presented)
|
||||
}
|
||||
return vc
|
||||
}
|
||||
let viewController = findCurrentController(from: rootController)
|
||||
return viewController
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
import UIKit
|
||||
import DCloudUTSFoundation
|
||||
import SVGAPlayer
|
||||
|
||||
public class SVGAAnimationView: UIView {
|
||||
private var playerView: SVGAPlayer?
|
||||
private var svgaDelegate : SVGAAnimationViewDelegate?
|
||||
|
||||
private func cleanupOldPlayer() {
|
||||
playerView?.removeFromSuperview()
|
||||
playerView?.delegate = nil
|
||||
playerView = nil
|
||||
}
|
||||
|
||||
public func setDelegate(_ delegate : SVGAAnimationViewDelegate){
|
||||
self.svgaDelegate = delegate
|
||||
}
|
||||
|
||||
public func startAnimation(_ playUrl: String) {
|
||||
console.log("======startAnimation, playUrl: ", playUrl)
|
||||
guard isSVGAFile(url: playUrl) else {
|
||||
console.error("======startAnimation error, playUrl is not svga")
|
||||
self.svgaDelegate?.onFinished()
|
||||
return
|
||||
}
|
||||
|
||||
cleanupOldPlayer()
|
||||
|
||||
let player = SVGAPlayer(frame: bounds)
|
||||
player.contentMode = .scaleAspectFill
|
||||
player.delegate = self
|
||||
player.loops = 1
|
||||
addSubview(player)
|
||||
|
||||
player.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
player.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
player.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||
player.topAnchor.constraint(equalTo: topAnchor),
|
||||
player.bottomAnchor.constraint(equalTo: bottomAnchor)
|
||||
])
|
||||
|
||||
self.playerView = player // 保存实例
|
||||
|
||||
// 异步加载并播放动画
|
||||
DispatchQueue.global().async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
let url: URL?
|
||||
|
||||
if playUrl.hasPrefix("http://") || playUrl.hasPrefix("https://") {
|
||||
url = URL(string: playUrl)
|
||||
} else {
|
||||
url = URL(fileURLWithPath: playUrl)
|
||||
}
|
||||
guard let validUrl = url,
|
||||
let animationData = try? Data(contentsOf: validUrl) else {
|
||||
DispatchQueue.main.async {
|
||||
self.cleanupOldPlayer()
|
||||
console.error("======startAnimation error, url parse error")
|
||||
self.svgaDelegate?.onFinished()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
let parser = SVGAParser()
|
||||
parser.parse(with: animationData, cacheKey: validUrl.lastPathComponent) { [weak self] videoItem in
|
||||
DispatchQueue.main.async {
|
||||
console.error("======startAnimation begin")
|
||||
guard let self = self else { return }
|
||||
self.playerView?.videoItem = videoItem
|
||||
self.playerView?.startAnimation()
|
||||
}
|
||||
} failureBlock: { [weak self] error in
|
||||
DispatchQueue.main.async {
|
||||
console.error("======startAnimation failed")
|
||||
self?.cleanupOldPlayer()
|
||||
self?.svgaDelegate?.onFinished()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func stopAnimation() {
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
self.playerView?.stopAnimation()
|
||||
|
||||
// 移除视图并清理代理
|
||||
self.playerView?.removeFromSuperview()
|
||||
self.playerView?.delegate = nil
|
||||
|
||||
// 清空实例并通知中断
|
||||
self.playerView = nil
|
||||
}
|
||||
}
|
||||
|
||||
private func isSVGAFile(url: String) -> Bool {
|
||||
guard let urlObj = URL(string: url) else { return false }
|
||||
let svgaExtension = "svga"
|
||||
return urlObj.pathExtension.lowercased() == svgaExtension
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - SVGAPlayerDelegate
|
||||
extension SVGAAnimationView: SVGAPlayerDelegate {
|
||||
public func svgaPlayerDidFinishedAnimation(_ player: SVGAPlayer) {
|
||||
UIView.animate(withDuration: 0.2, animations: {
|
||||
player.alpha = 0
|
||||
}) { _ in
|
||||
player.removeFromSuperview()
|
||||
self.cleanupOldPlayer()
|
||||
console.error("======startAnimation, onFinished")
|
||||
self.svgaDelegate?.onFinished()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public protocol SVGAAnimationViewDelegate: AnyObject {
|
||||
func onFinished()
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import AtomicXCore
|
||||
import Combine
|
||||
import DCloudUTSFoundation
|
||||
import RTCRoomEngine
|
||||
|
||||
public class AudioEffectStoreObserver {
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
public static let shared = AudioEffectStoreObserver()
|
||||
|
||||
public func audioEffectStoreChanged(
|
||||
_ callback: @escaping (_ name: String, _ data: String) -> Void
|
||||
) {
|
||||
cancellables.removeAll()
|
||||
|
||||
AudioEffectStore.shared
|
||||
.state.subscribe(StatePublisherSelector(keyPath: \AudioEffectState.audioChangerType))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { value in
|
||||
callback("audioChangerType", String(value.rawValue))
|
||||
}).store(in: &cancellables)
|
||||
|
||||
AudioEffectStore.shared
|
||||
.state.subscribe(StatePublisherSelector(keyPath: \AudioEffectState.audioReverbType))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { value in
|
||||
callback("audioReverbType", String(value.rawValue))
|
||||
}).store(in: &cancellables)
|
||||
|
||||
AudioEffectStore.shared
|
||||
.state.subscribe(StatePublisherSelector(keyPath: \AudioEffectState.isEarMonitorOpened))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { value in
|
||||
callback("isEarMonitorOpened", String(value))
|
||||
}).store(in: &cancellables)
|
||||
|
||||
AudioEffectStore.shared
|
||||
.state.subscribe(StatePublisherSelector(keyPath: \AudioEffectState.earMonitorVolume))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { value in
|
||||
callback("earMonitorVolume", String(value))
|
||||
}).store(in: &cancellables)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import AtomicXCore
|
||||
import Combine
|
||||
import DCloudUTSFoundation
|
||||
import Foundation
|
||||
import RTCRoomEngine
|
||||
|
||||
public class BarrageStoreObserver {
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
public static let shared = BarrageStoreObserver()
|
||||
|
||||
public func barrageStoreChanged(
|
||||
_ liveID: String, _ callback: @escaping (_ name: String, _ data: String) -> Void
|
||||
) {
|
||||
cancellables.removeAll()
|
||||
BarrageStore.create(liveID: liveID)
|
||||
.state.subscribe(StatePublisherSelector(keyPath: \BarrageState.messageList))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { [weak self] messageList in
|
||||
guard let self = self else { return }
|
||||
let dict = messageList.map { self.convertBarrageToDic(barrage: $0) }
|
||||
if let jsonList = JsonUtil.toJson(dict) {
|
||||
callback("messageList", jsonList)
|
||||
}
|
||||
}).store(in: &cancellables)
|
||||
// TODO: 底层未实现,暂时隐藏
|
||||
// BarrageStore.create(liveID: liveID)
|
||||
// .state.subscribe(StatePublisherSelector(keyPath: \BarrageState.allowSendMessage))
|
||||
// .receive(on: DispatchQueue.main)
|
||||
// .sink(receiveValue: { message in
|
||||
// callback("allowSendMessage", String(message))
|
||||
// }).store(in: &cancellables)
|
||||
}
|
||||
|
||||
private func convertBarrageToDic(barrage: Barrage) -> [String: Any] {
|
||||
var dict: [String: Any] = [
|
||||
"liveID": barrage.liveID,
|
||||
"sender": TypeConvert.convertLiveUserInfoToDic(liveUserInfo: barrage.sender),
|
||||
"sequence": barrage.sequence,
|
||||
"timestampInSecond": barrage.timestampInSecond,
|
||||
"messageType": convertMessageType(barrage.messageType),
|
||||
"textContent": barrage.textContent,
|
||||
"extensionInfo": barrage.extensionInfo,
|
||||
"businessID": barrage.businessID,
|
||||
"data": barrage.data,
|
||||
]
|
||||
return dict
|
||||
}
|
||||
|
||||
private func convertMessageType(_ type: BarrageType) -> String {
|
||||
if type == BarrageType.custom {
|
||||
return "CUSTOM"
|
||||
}
|
||||
return "TEXT"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import AtomicXCore
|
||||
import Combine
|
||||
import DCloudUTSFoundation
|
||||
import RTCRoomEngine
|
||||
|
||||
public class BaseBeautyStoreObserver {
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
public static let shared = BaseBeautyStoreObserver()
|
||||
|
||||
public func beautyStoreChanged(_ callback: @escaping (_ name: String, _ data: String) -> Void) {
|
||||
cancellables.removeAll()
|
||||
|
||||
BaseBeautyStore.shared
|
||||
.state.subscribe(StatePublisherSelector(keyPath: \BaseBeautyState.smoothLevel))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { value in
|
||||
callback("smoothLevel", String(value))
|
||||
}).store(in: &cancellables)
|
||||
|
||||
BaseBeautyStore.shared
|
||||
.state.subscribe(StatePublisherSelector(keyPath: \BaseBeautyState.whitenessLevel))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { value in
|
||||
callback("whitenessLevel", String(value))
|
||||
}).store(in: &cancellables)
|
||||
|
||||
BaseBeautyStore.shared
|
||||
.state.subscribe(StatePublisherSelector(keyPath: \BaseBeautyState.ruddyLevel))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { value in
|
||||
callback("ruddyLevel", String(value))
|
||||
}).store(in: &cancellables)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
import AtomicXCore
|
||||
import Combine
|
||||
import DCloudUTSFoundation
|
||||
import RTCRoomEngine
|
||||
|
||||
public class BattleStoreObserver {
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
private var battleEventCancellables = Set<AnyCancellable>()
|
||||
public static let shared = BattleStoreObserver()
|
||||
|
||||
public func battleStoreChanged(
|
||||
_ liveID: String, _ callback: @escaping (_ name: String, _ data: String) -> Void
|
||||
) {
|
||||
cancellables.removeAll()
|
||||
|
||||
BattleStore.create(liveID: liveID)
|
||||
.state.subscribe(StatePublisherSelector(keyPath: \BattleState.currentBattleInfo))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { [weak self] currentBattleInfo in
|
||||
guard let self = self else { return }
|
||||
if let battleInfo = currentBattleInfo {
|
||||
if let json = JsonUtil.toJson(
|
||||
self.convertBattleInfoToDic(battleInfo: battleInfo))
|
||||
{
|
||||
callback("currentBattleInfo", json)
|
||||
}
|
||||
}
|
||||
}).store(in: &cancellables)
|
||||
|
||||
BattleStore.create(liveID: liveID)
|
||||
.state.subscribe(StatePublisherSelector(keyPath: \BattleState.battleUsers))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { [weak self] battleUsers in
|
||||
guard let self = self else { return }
|
||||
let userList = battleUsers.map {
|
||||
TypeConvert.convertSeatUserInfoToDic(seatUserInfo: $0)
|
||||
}
|
||||
if let json = JsonUtil.toJson(userList) {
|
||||
callback("battleUsers", json)
|
||||
}
|
||||
}).store(in: &cancellables)
|
||||
|
||||
BattleStore.create(liveID: liveID)
|
||||
.state.subscribe(StatePublisherSelector(keyPath: \BattleState.battleScore))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { [weak self] battleScore in
|
||||
guard let self = self else { return }
|
||||
if let json = JsonUtil.toJson(battleScore) {
|
||||
callback("battleScore", json)
|
||||
}
|
||||
}).store(in: &cancellables)
|
||||
}
|
||||
|
||||
private func convertBattleInfoToDic(battleInfo: BattleInfo) -> [String: Any] {
|
||||
return [
|
||||
"battleID": battleInfo.battleID,
|
||||
"config": convertBattleConfigToDic(config: battleInfo.config),
|
||||
"startTime": battleInfo.startTime,
|
||||
"endTime": battleInfo.endTime,
|
||||
]
|
||||
}
|
||||
|
||||
private func convertBattleConfigToDic(config: BattleConfig) -> [String: Any] {
|
||||
return [
|
||||
"duration": config.duration,
|
||||
"needResponse": config.needResponse,
|
||||
"extensionInfo": config.extensionInfo,
|
||||
]
|
||||
}
|
||||
|
||||
public func setupBattleEvent(
|
||||
_ liveID: String, _ callback: @escaping (_ name: String, _ data: String) -> Void
|
||||
) {
|
||||
battleEventCancellables.removeAll()
|
||||
BattleStore.create(liveID: liveID).battleEventPublisher
|
||||
.receive(on: RunLoop.main)
|
||||
.sink { [weak self] event in
|
||||
guard let self = self else { return }
|
||||
switch event {
|
||||
case .onBattleStarted(let battleInfo, let inviter, let invitees):
|
||||
let dict: [String: Any] = [
|
||||
"battleInfo": self.convertBattleInfoToDic(battleInfo: battleInfo),
|
||||
"inviter": TypeConvert.convertSeatUserInfoToDic(seatUserInfo: inviter),
|
||||
"invitees": invitees.map {
|
||||
TypeConvert.convertSeatUserInfoToDic(seatUserInfo: $0)
|
||||
},
|
||||
]
|
||||
if let json = JsonUtil.toJson(dict) {
|
||||
callback("onBattleStarted", json)
|
||||
}
|
||||
case .onBattleEnded(let battleInfo, let reason):
|
||||
let reasonStr = reason == .allMemberExit ? "ALL_MEMBER_EXIT" : "TIME_OVER"
|
||||
let dict: [String: Any] = [
|
||||
"battleInfo": self.convertBattleInfoToDic(battleInfo: battleInfo),
|
||||
"reason": reasonStr,
|
||||
]
|
||||
if let json = JsonUtil.toJson(dict) {
|
||||
callback("onBattleEnded", json)
|
||||
}
|
||||
case .onUserJoinBattle(let battleID, let battleUser):
|
||||
let dict: [String: Any] = [
|
||||
"battleID": battleID,
|
||||
"battleUser": TypeConvert.convertSeatUserInfoToDic(
|
||||
seatUserInfo: battleUser),
|
||||
]
|
||||
if let json = JsonUtil.toJson(dict) {
|
||||
callback("onUserJoinBattle", json)
|
||||
}
|
||||
case .onUserExitBattle(let battleID, let battleUser):
|
||||
let dict: [String: Any] = [
|
||||
"battleID": battleID,
|
||||
"battleUser": TypeConvert.convertSeatUserInfoToDic(
|
||||
seatUserInfo: battleUser),
|
||||
]
|
||||
if let json = JsonUtil.toJson(dict) {
|
||||
callback("onUserExitBattle", json)
|
||||
}
|
||||
case .onBattleRequestReceived(let battleID, let inviter, let invitee):
|
||||
let dict: [String: Any] = [
|
||||
"battleID": battleID,
|
||||
"inviter": TypeConvert.convertSeatUserInfoToDic(seatUserInfo: inviter),
|
||||
"invitee": TypeConvert.convertSeatUserInfoToDic(seatUserInfo: invitee),
|
||||
]
|
||||
if let json = JsonUtil.toJson(dict) {
|
||||
callback("onBattleRequestReceived", json)
|
||||
}
|
||||
case .onBattleRequestCancelled(let battleID, let inviter, let invitee):
|
||||
let dict: [String: Any] = [
|
||||
"battleID": battleID,
|
||||
"inviter": TypeConvert.convertSeatUserInfoToDic(seatUserInfo: inviter),
|
||||
"invitee": TypeConvert.convertSeatUserInfoToDic(seatUserInfo: invitee),
|
||||
]
|
||||
if let json = JsonUtil.toJson(dict) {
|
||||
callback("onBattleRequestCancelled", json)
|
||||
}
|
||||
case .onBattleRequestTimeout(let battleID, let inviter, let invitee):
|
||||
let dict: [String: Any] = [
|
||||
"battleID": battleID,
|
||||
"inviter": TypeConvert.convertSeatUserInfoToDic(seatUserInfo: inviter),
|
||||
"invitee": TypeConvert.convertSeatUserInfoToDic(seatUserInfo: invitee),
|
||||
]
|
||||
if let json = JsonUtil.toJson(dict) {
|
||||
callback("onBattleRequestTimeout", json)
|
||||
}
|
||||
case .onBattleRequestAccept(let battleID, let inviter, let invitee):
|
||||
let dict: [String: Any] = [
|
||||
"battleID": battleID,
|
||||
"inviter": TypeConvert.convertSeatUserInfoToDic(seatUserInfo: inviter),
|
||||
"invitee": TypeConvert.convertSeatUserInfoToDic(seatUserInfo: invitee),
|
||||
]
|
||||
if let json = JsonUtil.toJson(dict) {
|
||||
callback("onBattleRequestAccept", json)
|
||||
}
|
||||
case .onBattleRequestReject(let battleID, let inviter, let invitee):
|
||||
let dict: [String: Any] = [
|
||||
"battleID": battleID,
|
||||
"inviter": TypeConvert.convertSeatUserInfoToDic(seatUserInfo: inviter),
|
||||
"invitee": TypeConvert.convertSeatUserInfoToDic(seatUserInfo: invitee),
|
||||
]
|
||||
if let json = JsonUtil.toJson(dict) {
|
||||
callback("onBattleRequestReject", json)
|
||||
}
|
||||
}
|
||||
}.store(in: &battleEventCancellables)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
import AtomicXCore
|
||||
import Combine
|
||||
import DCloudUTSFoundation
|
||||
import RTCRoomEngine
|
||||
|
||||
public class CoGuestStoreObserver {
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
private var hostEventCancellables = Set<AnyCancellable>()
|
||||
private var guestEventCancellables = Set<AnyCancellable>()
|
||||
public static let shared = CoGuestStoreObserver()
|
||||
|
||||
public func coGuestStoreChanged(
|
||||
_ liveID: String, _ callback: @escaping (_ name: String, _ data: String) -> Void
|
||||
) {
|
||||
cancellables.removeAll()
|
||||
CoGuestStore.create(liveID: liveID)
|
||||
.state.subscribe(StatePublisherSelector(keyPath: \CoGuestState.connected))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { arr in
|
||||
let dict = arr.map { TypeConvert.convertSeatUserInfoToDic(seatUserInfo: $0) }
|
||||
if let json = JsonUtil.toJson(dict) {
|
||||
callback("connected", json)
|
||||
}
|
||||
}).store(in: &cancellables)
|
||||
|
||||
let arrayKeys: [(String, KeyPath<CoGuestState, [LiveUserInfo]>)] = [
|
||||
("invitees", \CoGuestState.invitees),
|
||||
("applicants", \CoGuestState.applicants),
|
||||
("candidates", \CoGuestState.candidates),
|
||||
]
|
||||
for (key, kp) in arrayKeys {
|
||||
CoGuestStore.create(liveID: liveID)
|
||||
.state.subscribe(StatePublisherSelector(keyPath: kp))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { arr in
|
||||
let dict = arr.map { TypeConvert.convertLiveUserInfoToDic(liveUserInfo: $0) }
|
||||
if let json = JsonUtil.toJson(dict) {
|
||||
callback(key, json)
|
||||
}
|
||||
}).store(in: &cancellables)
|
||||
}
|
||||
}
|
||||
|
||||
public func setupHostEvent(
|
||||
_ liveID: String, _ callback: @escaping (_ name: String, _ data: String) -> Void
|
||||
) {
|
||||
hostEventCancellables.removeAll()
|
||||
CoGuestStore.create(liveID: liveID).hostEventPublisher
|
||||
.receive(on: RunLoop.main)
|
||||
.sink { [weak self] event in
|
||||
guard let self = self else { return }
|
||||
switch event {
|
||||
case .onGuestApplicationReceived(let guestUser):
|
||||
let dict: [String: Any] = [
|
||||
"guestUser" : TypeConvert.convertLiveUserInfoToDic(liveUserInfo: guestUser)
|
||||
]
|
||||
if let json = JsonUtil.toJson(dict) {
|
||||
callback("onGuestApplicationReceived", json)
|
||||
}
|
||||
case .onGuestApplicationCancelled(let guestUser):
|
||||
let dict: [String: Any] = [
|
||||
"guestUser" : TypeConvert.convertLiveUserInfoToDic(liveUserInfo: guestUser)
|
||||
]
|
||||
if let json = JsonUtil.toJson(dict) {
|
||||
callback("onGuestApplicationCancelled", json)
|
||||
}
|
||||
case .onGuestApplicationProcessedByOtherHost(let guestUser, let hostUser):
|
||||
var dict: [String: Any] = [:]
|
||||
dict["guestUser"] = TypeConvert.convertLiveUserInfoToDic(liveUserInfo: guestUser)
|
||||
dict["hostUser"] = TypeConvert.convertLiveUserInfoToDic(liveUserInfo: hostUser)
|
||||
if let json = JsonUtil.toJson(dict) {
|
||||
callback("onGuestApplicationProcessedByOtherHost", json)
|
||||
}
|
||||
case .onHostInvitationResponded(let isAccept, let guestUser):
|
||||
var dict: [String: Any] = [:]
|
||||
dict["isAccept"] = isAccept
|
||||
dict["guestUser"] = TypeConvert.convertLiveUserInfoToDic(liveUserInfo: guestUser)
|
||||
if let json = JsonUtil.toJson(dict) {
|
||||
callback("onHostInvitationResponded", json)
|
||||
}
|
||||
case .onHostInvitationNoResponse(let guestUser, let reason):
|
||||
var dict: [String: Any] = [:]
|
||||
dict["guestUser"] = TypeConvert.convertLiveUserInfoToDic(liveUserInfo: guestUser)
|
||||
dict["reason"] = convertNoResponseReason(reason)
|
||||
if let json = JsonUtil.toJson(dict) {
|
||||
callback("onHostInvitationNoResponse", json)
|
||||
}
|
||||
}
|
||||
}.store(in: &hostEventCancellables)
|
||||
}
|
||||
|
||||
private func convertNoResponseReason(_ reason: NoResponseReason) -> String {
|
||||
if reason == NoResponseReason.alreadySeated {
|
||||
return "ALREADY_SEATED"
|
||||
}
|
||||
return "TIMEOUT"
|
||||
}
|
||||
|
||||
public func setupGuestEvent(
|
||||
_ liveID: String, _ callback: @escaping (_ name: String, _ data: String) -> Void
|
||||
) {
|
||||
guestEventCancellables.removeAll()
|
||||
CoGuestStore.create(liveID: liveID).guestEventPublisher
|
||||
.receive(on: RunLoop.main)
|
||||
.sink { [weak self] event in
|
||||
guard let self = self else { return }
|
||||
switch event {
|
||||
case .onHostInvitationReceived(let hostUser):
|
||||
let dict: [String: Any] = [
|
||||
"hostUser" : TypeConvert.convertLiveUserInfoToDic(liveUserInfo: hostUser)
|
||||
]
|
||||
if let json = JsonUtil.toJson(dict) {
|
||||
callback("onHostInvitationReceived", json)
|
||||
}
|
||||
case .onHostInvitationCancelled(let hostUser):
|
||||
let dict: [String: Any] = [
|
||||
"hostUser" : TypeConvert.convertLiveUserInfoToDic(liveUserInfo: hostUser)
|
||||
]
|
||||
if let json = JsonUtil.toJson(dict) {
|
||||
callback("onHostInvitationCancelled", json)
|
||||
}
|
||||
case .onGuestApplicationResponded(let isAccept, let hostUser):
|
||||
var dict: [String: Any] = [:]
|
||||
dict["isAccept"] = isAccept
|
||||
dict["hostUser"] = TypeConvert.convertLiveUserInfoToDic(liveUserInfo: hostUser)
|
||||
if let json = JsonUtil.toJson(dict) {
|
||||
callback("onGuestApplicationResponded", json)
|
||||
}
|
||||
case .onGuestApplicationNoResponse(let reason):
|
||||
var dict: [String: Any] = [:]
|
||||
dict["reason"] = convertNoResponseReason(reason)
|
||||
if let json = JsonUtil.toJson(dict) {
|
||||
callback("onGuestApplicationNoResponse", json)
|
||||
}
|
||||
case .onKickedOffSeat(let seatIndex, let hostUser):
|
||||
var dict: [String: Any] = [:]
|
||||
dict["seatIndex"] = seatIndex
|
||||
dict["hostUser"] = TypeConvert.convertLiveUserInfoToDic(liveUserInfo: hostUser)
|
||||
if let json = JsonUtil.toJson(dict) {
|
||||
callback("onKickedOffSeat", json)
|
||||
}
|
||||
}
|
||||
}.store(in: &guestEventCancellables)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
import AtomicXCore
|
||||
import Combine
|
||||
import DCloudUTSFoundation
|
||||
import RTCRoomEngine
|
||||
|
||||
public class CoHostStoreObserver {
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
private var coHostEventCancellables = Set<AnyCancellable>()
|
||||
public static let shared = CoHostStoreObserver()
|
||||
|
||||
public func coHostStoreChanged(
|
||||
_ liveID: String, _ callback: @escaping (_ name: String, _ data: String) -> Void
|
||||
) {
|
||||
cancellables.removeAll()
|
||||
|
||||
CoHostStore.create(liveID: liveID)
|
||||
.state.subscribe(StatePublisherSelector(keyPath: \CoHostState.coHostStatus))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { value in
|
||||
var coHostStatus = "DISCONNECTED"
|
||||
if value == .connected {
|
||||
coHostStatus = "CONNECTED"
|
||||
}
|
||||
if let json = JsonUtil.toJson(coHostStatus) {
|
||||
callback("coHostStatus", json)
|
||||
}
|
||||
}).store(in: &cancellables)
|
||||
|
||||
CoHostStore.create(liveID: liveID)
|
||||
.state.subscribe(StatePublisherSelector(keyPath: \CoHostState.connected))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { arr in
|
||||
let dict = arr.map { TypeConvert.convertSeatUserInfoToDic(seatUserInfo: $0) }
|
||||
if let json = JsonUtil.toJson(dict) {
|
||||
callback("connected", json)
|
||||
}
|
||||
}).store(in: &cancellables)
|
||||
//TODO: 底层未实现,暂时删除
|
||||
// CoHostStore.create(liveID: liveID)
|
||||
// .state.subscribe(StatePublisherSelector(keyPath: \CoHostState.candidates))
|
||||
// .receive(on: DispatchQueue.main)
|
||||
// .sink(receiveValue: { arr in
|
||||
// let dict = arr.map { TypeConvert.convertSeatUserInfoToDic(seatUserInfo: $0) }
|
||||
// if let json = JsonUtil.toJson(dict) {
|
||||
// callback("candidates", json)
|
||||
// }
|
||||
// }).store(in: &cancellables)
|
||||
|
||||
CoHostStore.create(liveID: liveID)
|
||||
.state.subscribe(StatePublisherSelector(keyPath: \CoHostState.invitees))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { arr in
|
||||
let dict = arr.map { TypeConvert.convertSeatUserInfoToDic(seatUserInfo: $0) }
|
||||
if let json = JsonUtil.toJson(dict) {
|
||||
callback("invitees", json)
|
||||
}
|
||||
}).store(in: &cancellables)
|
||||
|
||||
CoHostStore.create(liveID: liveID)
|
||||
.state.subscribe(StatePublisherSelector(keyPath: \CoHostState.applicant))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { [weak self] value in
|
||||
guard let self = self else { return }
|
||||
guard let value = value else { return }
|
||||
let userInfo = TypeConvert.convertSeatUserInfoToDic(seatUserInfo: value)
|
||||
if let json = JsonUtil.toJson(userInfo) {
|
||||
callback("applicant", json)
|
||||
}
|
||||
}).store(in: &cancellables)
|
||||
}
|
||||
|
||||
public func setupCoHostEvent(
|
||||
_ liveID: String, _ callback: @escaping (_ name: String, _ data: String) -> Void
|
||||
) {
|
||||
coHostEventCancellables.removeAll()
|
||||
CoHostStore.create(liveID: liveID).coHostEventPublisher
|
||||
.receive(on: RunLoop.main)
|
||||
.sink { [weak self] event in
|
||||
guard let self = self else { return }
|
||||
switch event {
|
||||
case .onCoHostRequestReceived(let inviter, let extensionInfo):
|
||||
var dict: [String: Any] = [
|
||||
"inviter": TypeConvert.convertSeatUserInfoToDic(seatUserInfo: inviter),
|
||||
"extensionInfo": extensionInfo,
|
||||
]
|
||||
if let json = JsonUtil.toJson(dict) {
|
||||
callback("onCoHostRequestReceived", json)
|
||||
}
|
||||
case .onCoHostRequestCancelled(let inviter, let invitee):
|
||||
var dict: [String: Any] = [
|
||||
"inviter": TypeConvert.convertSeatUserInfoToDic(seatUserInfo: inviter),
|
||||
]
|
||||
if let invitee = invitee {
|
||||
dict["invitee"] = TypeConvert.convertSeatUserInfoToDic(seatUserInfo: invitee)
|
||||
}
|
||||
if let json = JsonUtil.toJson(dict) {
|
||||
callback("onCoHostRequestCancelled", json)
|
||||
}
|
||||
case .onCoHostRequestAccepted(let invitee):
|
||||
var dict: [String: Any] = [
|
||||
"invitee": TypeConvert.convertSeatUserInfoToDic(seatUserInfo: invitee)
|
||||
]
|
||||
if let json = JsonUtil.toJson(dict) {
|
||||
callback("onCoHostRequestAccepted", json)
|
||||
}
|
||||
case .onCoHostRequestRejected(let invitee):
|
||||
var dict: [String: Any] = [
|
||||
"invitee": TypeConvert.convertSeatUserInfoToDic(seatUserInfo: invitee)
|
||||
]
|
||||
if let json = JsonUtil.toJson(dict) {
|
||||
callback("onCoHostRequestRejected", json)
|
||||
}
|
||||
case .onCoHostRequestTimeout(let inviter, let invitee):
|
||||
var dict: [String: Any] = [
|
||||
"inviter": TypeConvert.convertSeatUserInfoToDic(seatUserInfo: inviter),
|
||||
"invitee": TypeConvert.convertSeatUserInfoToDic(seatUserInfo: invitee),
|
||||
]
|
||||
if let json = JsonUtil.toJson(dict) {
|
||||
callback("onCoHostRequestTimeout", json)
|
||||
}
|
||||
case .onCoHostUserJoined(let userInfo):
|
||||
var dict: [String: Any] = [
|
||||
"userInfo": TypeConvert.convertSeatUserInfoToDic(seatUserInfo: userInfo)
|
||||
]
|
||||
if let json = JsonUtil.toJson(dict) {
|
||||
callback("onCoHostUserJoined", json)
|
||||
}
|
||||
case .onCoHostUserLeft(let userInfo):
|
||||
var dict: [String: Any] = [
|
||||
"userInfo": TypeConvert.convertSeatUserInfoToDic(seatUserInfo: userInfo)
|
||||
]
|
||||
if let json = JsonUtil.toJson(dict) {
|
||||
callback("onCoHostUserLeft", json)
|
||||
}
|
||||
}
|
||||
}.store(in: &coHostEventCancellables)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
import AtomicXCore
|
||||
import Combine
|
||||
import DCloudUTSFoundation
|
||||
import RTCRoomEngine
|
||||
|
||||
public class DeviceStoreObserver {
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
public static let shared = DeviceStoreObserver()
|
||||
|
||||
public func deviceStoreChanged(_ callback: @escaping (_ name: String, _ data: String) -> Void) {
|
||||
cancellables.removeAll()
|
||||
|
||||
DeviceStore.shared
|
||||
.state.subscribe(StatePublisherSelector(keyPath: \DeviceState.microphoneStatus))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { value in
|
||||
callback("microphoneStatus", String(value.rawValue))
|
||||
}).store(in: &cancellables)
|
||||
|
||||
DeviceStore.shared
|
||||
.state.subscribe(StatePublisherSelector(keyPath: \DeviceState.microphoneLastError))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { value in
|
||||
callback("microphoneLastError", String(value.rawValue))
|
||||
}).store(in: &cancellables)
|
||||
|
||||
DeviceStore.shared
|
||||
.state.subscribe(StatePublisherSelector(keyPath: \DeviceState.captureVolume))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { value in
|
||||
callback("captureVolume", String(value))
|
||||
}).store(in: &cancellables)
|
||||
|
||||
DeviceStore.shared
|
||||
.state.subscribe(StatePublisherSelector(keyPath: \DeviceState.currentMicVolume))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { value in
|
||||
callback("currentMicVolume", String(value))
|
||||
}).store(in: &cancellables)
|
||||
|
||||
DeviceStore.shared
|
||||
.state.subscribe(StatePublisherSelector(keyPath: \DeviceState.outputVolume))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { value in
|
||||
callback("outputVolume", String(value))
|
||||
}).store(in: &cancellables)
|
||||
|
||||
DeviceStore.shared
|
||||
.state.subscribe(StatePublisherSelector(keyPath: \DeviceState.cameraStatus))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { value in
|
||||
callback("cameraStatus", String(value.rawValue))
|
||||
}).store(in: &cancellables)
|
||||
|
||||
DeviceStore.shared
|
||||
.state.subscribe(StatePublisherSelector(keyPath: \DeviceState.cameraLastError))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { value in
|
||||
callback("cameraLastError", String(value.rawValue))
|
||||
}).store(in: &cancellables)
|
||||
|
||||
DeviceStore.shared
|
||||
.state.subscribe(StatePublisherSelector(keyPath: \DeviceState.isFrontCamera))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { value in
|
||||
callback("isFrontCamera", String(value))
|
||||
}).store(in: &cancellables)
|
||||
|
||||
DeviceStore.shared
|
||||
.state.subscribe(StatePublisherSelector(keyPath: \DeviceState.localMirrorType))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { [weak self] value in
|
||||
guard let self = self else { return }
|
||||
if let json = JsonUtil.toJson(convertLocalMirrorType(value)) {
|
||||
callback("localMirrorType", json)
|
||||
}
|
||||
}).store(in: &cancellables)
|
||||
|
||||
DeviceStore.shared
|
||||
.state.subscribe(StatePublisherSelector(keyPath: \DeviceState.localVideoQuality))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { [weak self] value in
|
||||
guard let self = self else { return }
|
||||
callback("localVideoQuality", convertVideoQuality(value))
|
||||
}).store(in: &cancellables)
|
||||
|
||||
DeviceStore.shared
|
||||
.state.subscribe(StatePublisherSelector(keyPath: \DeviceState.currentAudioRoute))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { value in
|
||||
callback("currentAudioRoute", String(value.rawValue))
|
||||
}).store(in: &cancellables)
|
||||
|
||||
DeviceStore.shared
|
||||
.state.subscribe(StatePublisherSelector(keyPath: \DeviceState.screenStatus))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { value in
|
||||
callback("screenStatus", String(value.rawValue))
|
||||
}).store(in: &cancellables)
|
||||
|
||||
DeviceStore.shared
|
||||
.state.subscribe(StatePublisherSelector(keyPath: \DeviceState.networkInfo))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { [weak self] value in
|
||||
guard let self = self else { return }
|
||||
if let json = JsonUtil.toJson(convertNetworkInfo(value)) {
|
||||
callback("networkInfo", json)
|
||||
}
|
||||
}).store(in: &cancellables)
|
||||
}
|
||||
|
||||
private func convertNetworkInfo(_ info: NetworkInfo) -> [String: Any] {
|
||||
var map = [String: Any]()
|
||||
map["userID"] = info.userID ?? ""
|
||||
map["quality"] = convertNetworkQuality(info.quality)
|
||||
map["upLoss"] = info.upLoss
|
||||
map["downLoss"] = info.downLoss
|
||||
map["delay"] = info.delay
|
||||
return map
|
||||
}
|
||||
|
||||
private func convertNetworkQuality(_ quality: NetworkQuality?) -> String {
|
||||
guard let quality = quality else {
|
||||
return "UNKNOWN"
|
||||
}
|
||||
switch quality {
|
||||
case .excellent:
|
||||
return "EXCELLENT"
|
||||
case .good:
|
||||
return "GOOD"
|
||||
case .poor:
|
||||
return "POOR"
|
||||
case .veryBad:
|
||||
return "VBAD"
|
||||
case .bad:
|
||||
return "BAD"
|
||||
case .down:
|
||||
return "DOWN"
|
||||
default:
|
||||
return "UNKNOWN"
|
||||
}
|
||||
}
|
||||
|
||||
private func convertVideoQuality(_ quality: VideoQuality) -> String {
|
||||
switch quality {
|
||||
case VideoQuality.quality540P:
|
||||
return "540P"
|
||||
case VideoQuality.quality720P:
|
||||
return "720P"
|
||||
case VideoQuality.quality1080P:
|
||||
return "1080P"
|
||||
default:
|
||||
return "360P"
|
||||
}
|
||||
}
|
||||
private func convertLocalMirrorType(_ type: MirrorType) -> String {
|
||||
switch type {
|
||||
case MirrorType.enable:
|
||||
return "ENABLE"
|
||||
case MirrorType.disable:
|
||||
return "DISABLE"
|
||||
default:
|
||||
return "AUTO"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import AtomicXCore
|
||||
import Combine
|
||||
import DCloudUTSFoundation
|
||||
import RTCRoomEngine
|
||||
|
||||
public class GiftStoreObserver {
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
private var giftEventCancellables = Set<AnyCancellable>()
|
||||
public static let shared = GiftStoreObserver()
|
||||
|
||||
public func giftStoreChanged(
|
||||
_ liveID: String, _ callback: @escaping (_ name: String, _ data: String) -> Void
|
||||
) {
|
||||
cancellables.removeAll()
|
||||
GiftStore.create(liveID: liveID)
|
||||
.state.subscribe(StatePublisherSelector(keyPath: \GiftState.usableGifts))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { [weak self] usableGifts in
|
||||
guard let self = self else { return }
|
||||
let dict = usableGifts.map { self.convertGiftCategoryToDic(giftCategory: $0) }
|
||||
if let jsonList = JsonUtil.toJson(dict) {
|
||||
callback("usableGifts", jsonList)
|
||||
}
|
||||
}).store(in: &cancellables)
|
||||
}
|
||||
|
||||
private func convertGiftCategoryToDic(giftCategory: GiftCategory) -> [String: Any] {
|
||||
var gifts: [[String: Any]] = []
|
||||
for info in giftCategory.giftList {
|
||||
gifts.append(convertGiftToDic(gift: info))
|
||||
}
|
||||
return [
|
||||
"categoryID": giftCategory.categoryID,
|
||||
"name": giftCategory.name,
|
||||
"desc": giftCategory.desc,
|
||||
"extensionInfo": giftCategory.extensionInfo,
|
||||
"giftList": gifts,
|
||||
]
|
||||
}
|
||||
private func convertGiftToDic(gift: Gift) -> [String: Any] {
|
||||
return [
|
||||
"giftID": gift.giftID,
|
||||
"name": gift.name,
|
||||
"desc": gift.desc,
|
||||
"iconURL": gift.iconURL,
|
||||
"resourceURL": gift.resourceURL,
|
||||
"level": gift.level,
|
||||
"coins": gift.coins,
|
||||
"extensionInfo": gift.extensionInfo,
|
||||
]
|
||||
}
|
||||
|
||||
public func setupGiftEvent(
|
||||
_ liveID: String, _ callback: @escaping (_ name: String, _ data: String) -> Void
|
||||
) {
|
||||
giftEventCancellables.removeAll()
|
||||
GiftStore.create(liveID: liveID).giftEventPublisher
|
||||
.receive(on: RunLoop.main)
|
||||
.sink { [weak self] event in
|
||||
guard let self = self else { return }
|
||||
switch event {
|
||||
case .onReceiveGift(let liveID, let gift, let count, let sender):
|
||||
var dict: [String: Any] = [:]
|
||||
dict["liveID"] = liveID
|
||||
dict["gift"] = self.convertGiftToDic(gift: gift)
|
||||
dict["count"] = count
|
||||
dict["sender"] = TypeConvert.convertLiveUserInfoToDic(liveUserInfo: sender)
|
||||
if let json = JsonUtil.toJson(dict) {
|
||||
callback("onReceiveGift", json)
|
||||
}
|
||||
}
|
||||
}.store(in: &giftEventCancellables)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import AtomicXCore
|
||||
import Combine
|
||||
import DCloudUTSFoundation
|
||||
import RTCRoomEngine
|
||||
|
||||
public class LikeStoreObserver {
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
private var likeEventCancellables = Set<AnyCancellable>()
|
||||
public static let shared = LikeStoreObserver()
|
||||
|
||||
public func likeStoreChanged(
|
||||
_ liveID: String, _ callback: @escaping (_ name: String, _ data: String) -> Void
|
||||
) {
|
||||
cancellables.removeAll()
|
||||
LikeStore.create(liveID: liveID)
|
||||
.state.subscribe(StatePublisherSelector(keyPath: \LikeState.totalLikeCount))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { totalLikeCount in
|
||||
callback("totalLikeCount", String(totalLikeCount))
|
||||
}).store(in: &cancellables)
|
||||
}
|
||||
|
||||
public func setupLikeEvent(_ liveID: String, _ callback: @escaping (_ name: String, _ data: String) -> Void) {
|
||||
likeEventCancellables.removeAll()
|
||||
LikeStore.create(liveID: liveID).likeEventPublisher
|
||||
.receive(on: RunLoop.main)
|
||||
.sink { [weak self] event in
|
||||
guard let self = self else { return }
|
||||
switch event {
|
||||
case .onReceiveLikesMessage(liveID: let liveID, totalLikesReceived: let totalLikesReceived, sender: let sender):
|
||||
var dict: [String: Any] = [:]
|
||||
dict["liveID"] = liveID
|
||||
dict["totalLikesReceived"] = totalLikesReceived
|
||||
dict["sender"] = TypeConvert.convertLiveUserInfoToDic(liveUserInfo: sender)
|
||||
if let json = JsonUtil.toJson(dict) {
|
||||
callback("onReceiveLikesMessage", json)
|
||||
}
|
||||
}
|
||||
}.store(in: &likeEventCancellables)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import AtomicXCore
|
||||
import Combine
|
||||
import DCloudUTSFoundation
|
||||
import RTCRoomEngine
|
||||
|
||||
public class LiveAudienceStoreObserver {
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
private var audienceCancellables = Set<AnyCancellable>()
|
||||
public static let shared = LiveAudienceStoreObserver()
|
||||
|
||||
public func liveAudienceStoreChanged(
|
||||
_ liveID: String, _ callback: @escaping (_ name: String, _ data: String) -> Void
|
||||
) {
|
||||
cancellables.removeAll()
|
||||
|
||||
LiveAudienceStore.create(liveID: liveID)
|
||||
.state.subscribe(StatePublisherSelector(keyPath: \LiveAudienceState.audienceList))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { audienceList in
|
||||
let dict = audienceList.map {
|
||||
TypeConvert.convertLiveUserInfoToDic(liveUserInfo: $0)
|
||||
}
|
||||
if let jsonList = JsonUtil.toJson(dict) {
|
||||
callback("audienceList", jsonList)
|
||||
}
|
||||
})
|
||||
.store(in: &cancellables)
|
||||
|
||||
LiveAudienceStore.create(liveID: liveID)
|
||||
.state.subscribe(StatePublisherSelector(keyPath: \LiveAudienceState.audienceCount))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { count in
|
||||
callback("audienceCount", String(count))
|
||||
}).store(in: &cancellables)
|
||||
}
|
||||
|
||||
public func setupAudienceEvent(
|
||||
_ liveID: String, _ callback: @escaping (_ name: String, _ data: String) -> Void
|
||||
) {
|
||||
audienceCancellables.removeAll()
|
||||
LiveAudienceStore.create(liveID: liveID).liveAudienceEventPublisher
|
||||
.receive(on: RunLoop.main)
|
||||
.sink { [weak self] event in
|
||||
guard let self = self else { return }
|
||||
switch event {
|
||||
case .onAudienceJoined(let audience):
|
||||
let dict: [String: Any] = [
|
||||
"audience" : TypeConvert.convertLiveUserInfoToDic(liveUserInfo: audience)
|
||||
]
|
||||
if let json = JsonUtil.toJson(dict) {
|
||||
callback("onAudienceJoined", json)
|
||||
}
|
||||
case .onAudienceMessageDisabled(let audience, let isDisable):
|
||||
var dict: [String: Any] = [:]
|
||||
dict["isDisable"] = isDisable
|
||||
dict["audience"] = TypeConvert.convertLiveUserInfoToDic(liveUserInfo: audience)
|
||||
if let json = JsonUtil.toJson(dict) {
|
||||
callback("onAudienceMessageDisabled", json)
|
||||
}
|
||||
case .onAudienceLeft(let audience):
|
||||
let dict: [String: Any] = [
|
||||
"audience" : TypeConvert.convertLiveUserInfoToDic(liveUserInfo: audience)
|
||||
]
|
||||
if let json = JsonUtil.toJson(dict) {
|
||||
callback("onAudienceLeft", json)
|
||||
}
|
||||
break
|
||||
}
|
||||
}.store(in: &audienceCancellables)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import AtomicXCore
|
||||
import Combine
|
||||
import DCloudUTSFoundation
|
||||
import Foundation
|
||||
import RTCRoomEngine
|
||||
|
||||
public class LiveListStoreObserver {
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
private var liveListEventCancellables = Set<AnyCancellable>()
|
||||
public static let shared = LiveListStoreObserver()
|
||||
|
||||
public func liveStoreChanged(_ callback: @escaping (_ name: String, _ data: String) -> Void) {
|
||||
cancellables.removeAll()
|
||||
|
||||
LiveListStore.shared
|
||||
.state.subscribe(StatePublisherSelector(keyPath: \LiveListState.liveList))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { [weak self] liveList in
|
||||
guard let self = self else { return }
|
||||
let dict = liveList.map { TypeConvert.convertLiveInfoToDic(liveInfo: $0) }
|
||||
if let jsonList = JsonUtil.toJson(dict) {
|
||||
callback("liveList", jsonList)
|
||||
}
|
||||
}).store(in: &cancellables)
|
||||
|
||||
LiveListStore.shared
|
||||
.state.subscribe(StatePublisherSelector(keyPath: \LiveListState.liveListCursor))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { liveListCursor in
|
||||
if let json = JsonUtil.toJson(liveListCursor) {
|
||||
callback("liveListCursor", json)
|
||||
}
|
||||
}).store(in: &cancellables)
|
||||
|
||||
LiveListStore.shared
|
||||
.state.subscribe(StatePublisherSelector(keyPath: \LiveListState.currentLive))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { [weak self] liveInfo in
|
||||
guard let self = self else { return }
|
||||
let dict = TypeConvert.convertLiveInfoToDic(liveInfo: liveInfo)
|
||||
if let json = JsonUtil.toJson(dict) {
|
||||
callback("currentLive", json)
|
||||
}
|
||||
}).store(in: &cancellables)
|
||||
}
|
||||
|
||||
public func setupLiveListEvent(_ callback: @escaping (_ name: String, _ data: String) -> Void) {
|
||||
liveListEventCancellables.removeAll()
|
||||
LiveListStore.shared.liveListEventPublisher
|
||||
.receive(on: RunLoop.main)
|
||||
.sink { [weak self] event in
|
||||
guard let self = self else { return }
|
||||
switch event {
|
||||
case .onLiveEnded(let liveID, let reason, let message):
|
||||
var dict: [String: Any] = [:]
|
||||
dict["liveID"] = liveID
|
||||
dict["reason"] = convertLiveEndedReason(reason)
|
||||
dict["message"] = message
|
||||
if let json = JsonUtil.toJson(dict) {
|
||||
callback("onLiveEnded", json)
|
||||
}
|
||||
case .onKickedOutOfLive(let liveID, let reason, let message):
|
||||
var dict: [String: Any] = [:]
|
||||
dict["liveID"] = liveID
|
||||
dict["reason"] = convertLiveKickedOutReason(reason)
|
||||
dict["message"] = message
|
||||
if let json = JsonUtil.toJson(dict) {
|
||||
callback("onKickedOutOfLive", json)
|
||||
}
|
||||
}
|
||||
}.store(in: &liveListEventCancellables)
|
||||
}
|
||||
|
||||
private func convertLiveEndedReason(_ reason: LiveEndedReason) -> String {
|
||||
if reason == LiveEndedReason.endedByServer {
|
||||
return "ENDED_BY_SERVER"
|
||||
}
|
||||
return "ENDED_BY_HOST"
|
||||
}
|
||||
|
||||
private func convertLiveKickedOutReason(_ reason: LiveKickedOutReason) -> String {
|
||||
switch reason {
|
||||
case .byLoggedOnOtherDevice:
|
||||
return "BY_LOGGED_ON_OTHER_DEVICE"
|
||||
case .byServer:
|
||||
return "BY_SERVER"
|
||||
case .forNetworkDisconnected:
|
||||
return "FOR_NETWORK_DISCONNECTED"
|
||||
case .forJoinRoomStatusInvalidDuringOffline:
|
||||
return "FOR_JOIN_ROOM_STATUS_INVALID_DURING_OFFLINE"
|
||||
case .forCountOfJoinedRoomsExceedLimit:
|
||||
return "FOR_COUNT_OF_JOINED_ROOMS_EXCEED_LIMIT"
|
||||
default:
|
||||
return "BY_ADMIN"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
import AtomicXCore
|
||||
import Combine
|
||||
import DCloudUTSFoundation
|
||||
import Foundation
|
||||
import RTCRoomEngine
|
||||
|
||||
public class LiveSeatStoreObserver {
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
private var liveSeatEventCancellables = Set<AnyCancellable>()
|
||||
public static let shared = LiveSeatStoreObserver()
|
||||
|
||||
public func liveSeatStoreChanged(
|
||||
_ liveID: String, _ callback: @escaping (_ name: String, _ data: String) -> Void
|
||||
) {
|
||||
cancellables.removeAll()
|
||||
|
||||
LiveSeatStore.create(liveID: liveID)
|
||||
.state.subscribe(StatePublisherSelector(keyPath: \LiveSeatState.seatList))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { [weak self] seatList in
|
||||
guard let self = self else { return }
|
||||
let dictArray = seatList.map { self.convertSeatInfoToDic(seatInfo: $0) }
|
||||
if let jsonList = JsonUtil.toJson(dictArray) {
|
||||
callback("seatList", jsonList)
|
||||
}
|
||||
}).store(in: &cancellables)
|
||||
|
||||
LiveSeatStore.create(liveID: liveID)
|
||||
.state.subscribe(StatePublisherSelector(keyPath: \LiveSeatState.canvas))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { [weak self] canvas in
|
||||
guard let self = self else { return }
|
||||
let dict = self.convertCanvasToDic(canvas: canvas)
|
||||
if let json = JsonUtil.toJson(dict) {
|
||||
callback("canvas", json)
|
||||
}
|
||||
}).store(in: &cancellables)
|
||||
|
||||
LiveSeatStore.create(liveID: liveID)
|
||||
.state.subscribe(StatePublisherSelector(keyPath: \LiveSeatState.speakingUsers))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { speakingUsers in
|
||||
if let jsonUsers = JsonUtil.toJson(speakingUsers) {
|
||||
callback("speakingUsers", jsonUsers)
|
||||
}
|
||||
}).store(in: &cancellables)
|
||||
}
|
||||
|
||||
private func convertSeatInfoToDic(seatInfo: SeatInfo) -> [String: Any] {
|
||||
var dict: [String: Any] = [
|
||||
"index": seatInfo.index,
|
||||
"isLocked": seatInfo.isLocked,
|
||||
"userInfo": TypeConvert.convertSeatUserInfoToDic(seatUserInfo: seatInfo.userInfo),
|
||||
"region": convertRegionInfoToDic(region: seatInfo.region),
|
||||
]
|
||||
return dict
|
||||
}
|
||||
|
||||
private func convertRegionInfoToDic(region: RegionInfo) -> [String: Any] {
|
||||
return [
|
||||
"x": region.x,
|
||||
"y": region.y,
|
||||
"w": region.w,
|
||||
"h": region.h,
|
||||
"zorder": region.zorder,
|
||||
]
|
||||
}
|
||||
|
||||
private func convertCanvasToDic(canvas: LiveCanvas) -> [String: Any] {
|
||||
return [
|
||||
"templateID": canvas.templateID,
|
||||
"w": canvas.w,
|
||||
"h": canvas.h,
|
||||
]
|
||||
}
|
||||
|
||||
public func setupLiveSeatEvent(
|
||||
_ liveID: String, _ callback: @escaping (_ name: String, _ data: String) -> Void
|
||||
) {
|
||||
liveSeatEventCancellables.removeAll()
|
||||
LiveSeatStore.create(liveID: liveID).liveSeatEventPublisher
|
||||
.receive(on: RunLoop.main)
|
||||
.sink { [weak self] event in
|
||||
guard let self = self else { return }
|
||||
switch event {
|
||||
case .onLocalCameraOpenedByAdmin(let policy):
|
||||
callback("onLocalCameraOpenedByAdmin", convertDeviceControlPolicy(policy))
|
||||
case .onLocalCameraClosedByAdmin:
|
||||
callback("onLocalCameraClosedByAdmin", "")
|
||||
case .onLocalMicrophoneOpenedByAdmin(let policy):
|
||||
callback("onLocalMicrophoneOpenedByAdmin", convertDeviceControlPolicy(policy))
|
||||
case .onLocalMicrophoneClosedByAdmin:
|
||||
callback("onLocalMicrophoneClosedByAdmin", "")
|
||||
}
|
||||
}.store(in: &liveSeatEventCancellables)
|
||||
}
|
||||
|
||||
private func convertDeviceControlPolicy(_ reason: DeviceControlPolicy) -> String {
|
||||
if reason == DeviceControlPolicy.unlockOnly {
|
||||
return "UNLOCK_ONLY"
|
||||
}
|
||||
return "FORCE_OPEN"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import AtomicXCore
|
||||
import Combine
|
||||
import DCloudUTSFoundation
|
||||
import RTCRoomEngine
|
||||
|
||||
public class LiveSummaryStoreObserver {
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
public static let shared = LiveSummaryStoreObserver()
|
||||
|
||||
public func liveSummaryStoreChanged(
|
||||
_ liveID: String, _ callback: @escaping (_ name: String, _ data: String) -> Void
|
||||
) {
|
||||
cancellables.removeAll()
|
||||
|
||||
LiveSummaryStore.create(liveID: liveID)
|
||||
.state.subscribe(StatePublisherSelector(keyPath: \LiveSummaryState.summaryData))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { data in
|
||||
let dict = TypeConvert.convertLiveSummaryDataToDic(summaryData: data)
|
||||
if let json = JsonUtil.toJson(dict) {
|
||||
callback("summaryData", json)
|
||||
}
|
||||
}).store(in: &cancellables)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import AtomicXCore
|
||||
import Combine
|
||||
import DCloudUTSFoundation
|
||||
import RTCRoomEngine
|
||||
|
||||
public class LoginStoreObserver {
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
public static let shared = LoginStoreObserver()
|
||||
|
||||
public func loginStoreChanged(_ callback: @escaping (_ name: String, _ data: String) -> Void) {
|
||||
cancellables.removeAll()
|
||||
|
||||
LoginStore.shared
|
||||
.state.subscribe(StatePublisherSelector(keyPath: \LoginState.loginUserInfo))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { [weak self] userInfo in
|
||||
guard let self = self else { return }
|
||||
guard let userInfo = userInfo else { return }
|
||||
if let jsonString = JsonUtil.toJson(
|
||||
self.converUserProfileToDic(userProfile: userInfo))
|
||||
{
|
||||
callback("loginUserInfo", jsonString)
|
||||
}
|
||||
}).store(in: &cancellables)
|
||||
|
||||
LoginStore.shared.state.subscribe(StatePublisherSelector(keyPath: \LoginState.loginStatus))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink(receiveValue: { value in
|
||||
var loginStatus = "UNLOGIN"
|
||||
if value == .logined {
|
||||
loginStatus = "LOGINED"
|
||||
}
|
||||
if let json = JsonUtil.toJson(loginStatus) {
|
||||
callback("loginStatus", json)
|
||||
}
|
||||
}).store(in: &cancellables)
|
||||
}
|
||||
|
||||
private func converUserProfileToDic(userProfile: UserProfile) -> [String: Any] {
|
||||
return [
|
||||
"userID": userProfile.userID,
|
||||
"nickname": userProfile.nickname,
|
||||
"avatarURL": userProfile.avatarURL,
|
||||
"selfSignature": userProfile.selfSignature,
|
||||
"gender": convertGender(gender: userProfile.gender),
|
||||
"role": userProfile.role,
|
||||
"level": userProfile.level,
|
||||
"allowType": convertAllowType(type: userProfile.allowType),
|
||||
// "customInfo": userProfile.customInfo, //TODO:暂不支持
|
||||
]
|
||||
}
|
||||
private func convertGender(gender: Gender?) -> String {
|
||||
var genderStr = "UNKNOWN"
|
||||
if( gender == .male) {
|
||||
genderStr = "MALE"
|
||||
} else if( gender == .female) {
|
||||
genderStr = "FEMALE"
|
||||
}
|
||||
return genderStr
|
||||
}
|
||||
private func convertAllowType(type: AllowType?) -> String {
|
||||
var allowType = "ALLOW_ANY"
|
||||
if(type == .needConfirm) {
|
||||
allowType = "NEED_CONFIRM"
|
||||
} else if(type == .denyAny) {
|
||||
allowType = "DENY_ANY"
|
||||
}
|
||||
return allowType
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
import AtomicXCore
|
||||
import RTCRoomEngine
|
||||
|
||||
struct TypeConvert {
|
||||
|
||||
static func convertLiveUserInfoToDic(liveUserInfo: LiveUserInfo) -> [String: String] {
|
||||
var dict: [String: String] = [
|
||||
"userID": liveUserInfo.userID ?? "",
|
||||
"userName": liveUserInfo.userName ?? "",
|
||||
"avatarURL": liveUserInfo.avatarURL ?? "",
|
||||
]
|
||||
return dict
|
||||
}
|
||||
|
||||
static func convertUserRole(_ role: Role?) -> String {
|
||||
switch role {
|
||||
case .admin:
|
||||
return "ADMIN"
|
||||
case .generalUser:
|
||||
return "GENERAL_USER"
|
||||
default:
|
||||
return "OWNER"
|
||||
}
|
||||
}
|
||||
|
||||
static func convertSeatUserInfoToDic(seatUserInfo: SeatUserInfo) -> [String: Any] {
|
||||
var dict: [String: Any] = [
|
||||
"userID": seatUserInfo.userID,
|
||||
"userName": seatUserInfo.userName ?? "",
|
||||
"avatarURL": seatUserInfo.avatarURL ?? "",
|
||||
"role": convertUserRole(seatUserInfo.role),
|
||||
"liveID": seatUserInfo.liveID,
|
||||
"microphoneStatus": convertDeviceStatus(seatUserInfo.microphoneStatus),
|
||||
"allowOpenMicrophone": seatUserInfo.allowOpenMicrophone,
|
||||
"cameraStatus": convertDeviceStatus(seatUserInfo.cameraStatus),
|
||||
"allowOpenCamera": seatUserInfo.allowOpenCamera,
|
||||
]
|
||||
return dict
|
||||
}
|
||||
|
||||
static func convertDeviceStatus(_ status: DeviceStatus) -> String {
|
||||
switch status {
|
||||
case .on:
|
||||
return "ON"
|
||||
case .off:
|
||||
return "OFF"
|
||||
}
|
||||
}
|
||||
|
||||
static func convertLiveInfoToDic(liveInfo: LiveInfo) -> [String: Any] {
|
||||
var dict: [String: Any] = [
|
||||
"liveID": liveInfo.liveID,
|
||||
"liveName": liveInfo.liveName,
|
||||
"notice": liveInfo.notice,
|
||||
"isMessageDisable": liveInfo.isMessageDisable,
|
||||
"isPublicVisible": liveInfo.isPublicVisible,
|
||||
"isSeatEnabled": liveInfo.isSeatEnabled,
|
||||
"keepOwnerOnSeat": liveInfo.keepOwnerOnSeat,
|
||||
"maxSeatCount": liveInfo.maxSeatCount,
|
||||
"seatMode": TypeConvert.convertTakeSeatMode(liveInfo.seatMode),
|
||||
"seatLayoutTemplateID": liveInfo.seatLayoutTemplateID,
|
||||
"coverURL": liveInfo.coverURL,
|
||||
"backgroundURL": liveInfo.backgroundURL,
|
||||
"activityStatus": liveInfo.activityStatus,
|
||||
"liveOwner": TypeConvert.convertLiveUserInfoToDic(liveUserInfo: liveInfo.liveOwner),
|
||||
"createTime" : liveInfo.createTime,
|
||||
"categoryList": liveInfo.categoryList,
|
||||
"totalViewerCount": liveInfo.totalViewerCount,
|
||||
"isGiftEnabled": liveInfo.isGiftEnabled,
|
||||
"metaData": liveInfo.metaData,
|
||||
]
|
||||
return dict
|
||||
}
|
||||
|
||||
static func convertTakeSeatMode(_ mode: TakeSeatMode) -> String {
|
||||
switch mode {
|
||||
case .free:
|
||||
return "FREE"
|
||||
case .apply:
|
||||
return "APPLY"
|
||||
}
|
||||
}
|
||||
|
||||
static func convertLiveSummaryDataToDic(summaryData: LiveSummaryData) -> [String: Any] {
|
||||
var dict: [String: Any] = [
|
||||
"totalDuration": summaryData.totalDuration,
|
||||
"totalViewers": summaryData.totalViewers,
|
||||
"totalGiftsSent": summaryData.totalGiftsSent,
|
||||
"totalGiftUniqueSenders": summaryData.totalGiftUniqueSenders,
|
||||
"totalGiftCoins": summaryData.totalGiftCoins,
|
||||
"totalLikesReceived": summaryData.totalLikesReceived,
|
||||
"totalMessageSent": summaryData.totalMessageSent,
|
||||
]
|
||||
return dict
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user