需要添加直播接口

This commit is contained in:
cbb
2026-01-12 17:52:15 +08:00
parent 83fec2617c
commit 13af9eb303
281 changed files with 313157 additions and 104 deletions

View File

@@ -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?
}

View 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)
}
}
}
}

View File

@@ -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),
])
}
}
}

View 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
}
}

View File

@@ -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()
}

View File

@@ -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)
}
}

View File

@@ -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"
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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"
}
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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"
}
}
}

View File

@@ -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"
}
}

View File

@@ -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)
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}