SeminarViewController.swift 43 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019
  1. //
  2. // SeminarViewController.swift
  3. // DigiXLite
  4. //
  5. // Created by Maronakins on 01/06/23.
  6. //
  7. import UIKit
  8. import nuSDKService
  9. class SeminarViewController: UIViewController {
  10. var data: String = ""
  11. var streamingData: [String: Any] = [:]
  12. var isLive: Bool = false
  13. private var textViewHeightConstraint: NSLayoutConstraint!
  14. private let keyboardLayoutGuide = UILayoutGuide()
  15. private var keyboardTopAnchorConstraint: NSLayoutConstraint!
  16. private let cellIdentifier = "reuseCell"
  17. private var frontCamera = true
  18. private var hasRaiseHand = false
  19. private var mIsSwitch = false
  20. private var currentSpeakingBC = "-666"
  21. private var currentSpeakingVW = "-666"
  22. private var secondViewHeight = 0.0
  23. private var secondViewWidth = 0.0
  24. private var heightTableView: NSLayoutConstraint?
  25. var wbVC : WhiteboardViewController?
  26. private var viewers: [SeminarViewer] = []
  27. private var chats: [SeminarChat] = [] {
  28. didSet {
  29. DispatchQueue.main.async {
  30. self.tableView.reloadData()
  31. if self.tableView.contentSize.height <= 167.0 {
  32. self.heightTableView?.constant = self.tableView.contentSize.height
  33. } else {
  34. self.heightTableView?.constant = 167.0
  35. }
  36. self.tableView.scrollToBottom()
  37. }
  38. }
  39. }
  40. private var isOversized: Bool = false {
  41. didSet {
  42. guard oldValue != isOversized else {
  43. return
  44. }
  45. if isOversized {
  46. textViewHeightConstraint = textView.heightAnchor.constraint(equalToConstant: textView.frame.height)
  47. NSLayoutConstraint.activate([textViewHeightConstraint])
  48. } else {
  49. NSLayoutConstraint.deactivate([textViewHeightConstraint])
  50. }
  51. textView.isScrollEnabled = isOversized
  52. textView.setNeedsUpdateConstraints()
  53. }
  54. }
  55. var statusView = UIView()
  56. var viewCountViewer = UIView()
  57. lazy var status: UILabel = {
  58. let label = UILabel()
  59. label.font = UIFont.systemFont(ofSize: 14)
  60. label.textColor = .mainColor
  61. label.textAlignment = .center
  62. label.text = streamingData["title"] as? String
  63. return label
  64. }()
  65. lazy var tvCameraPreviewB: UIImageView = {
  66. let imageView = UIImageView()
  67. imageView.contentMode = .scaleAspectFit
  68. imageView.backgroundColor = .black
  69. return imageView
  70. }()
  71. lazy var tvCameraPreviewS: UIImageView = {
  72. let imageView = UIImageView()
  73. imageView.contentMode = .scaleAspectFit
  74. imageView.backgroundColor = .black
  75. return imageView
  76. }()
  77. lazy var ivRemoteViewS: UIImageView = {
  78. let imageView = UIImageView()
  79. imageView.contentMode = .scaleAspectFit
  80. imageView.backgroundColor = .black
  81. return imageView
  82. }()
  83. lazy var ivRemoteViewM: UIImageView = {
  84. let imageView = UIImageView()
  85. imageView.contentMode = .scaleAspectFit
  86. imageView.backgroundColor = .black
  87. return imageView
  88. }()
  89. var tvBRotation = 0.0
  90. var tvSRotation = 0.0
  91. var ivSRotation = 0.0
  92. var ivMRotation = 0.0
  93. lazy var tableView: UITableView = {
  94. let tableView = UITableView()
  95. tableView.dataSource = self
  96. tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellIdentifier)
  97. tableView.tableFooterView = UIView()
  98. tableView.separatorStyle = .none
  99. tableView.separatorInset = .zero
  100. tableView.tintColor = .clear
  101. tableView.backgroundColor = .clear
  102. tableView.showsVerticalScrollIndicator = false
  103. return tableView
  104. }()
  105. lazy var textView: UITextView = {
  106. let textView = UITextView()
  107. textView.delegate = self
  108. textView.layer.borderWidth = 1.0
  109. textView.layer.borderColor = UIColor.white.cgColor
  110. textView.layer.backgroundColor = UIColor.clear.cgColor
  111. textView.isScrollEnabled = false
  112. textView.font = UIFont.systemFont(ofSize: 14)
  113. textView.text = "Send Comment".localized()
  114. textView.textColor = UIColor.secondaryColor
  115. return textView
  116. }()
  117. lazy var send: UIButton = {
  118. let button = UIButton()
  119. button.setImage(UIImage(named: "Send-(White)", in: Bundle.resourceBundle(for: DigiX.self), with: nil), for: .normal)
  120. button.imageView?.contentMode = .scaleAspectFit
  121. button.contentVerticalAlignment = .fill
  122. button.contentHorizontalAlignment = .fill
  123. button.backgroundColor = .mainColor
  124. button.addTarget(self, action: #selector(sent(sender:)), for: .touchUpInside)
  125. return button
  126. }()
  127. lazy var raiseHandBtn: UIButton = {
  128. let button = UIButton()
  129. button.frame = CGRect(x:0, y:0, width:40, height:40)
  130. button.setImage(UIImage(named: "pb_raise_hand", in: Bundle.resourceBundle(for: DigiX.self), with: nil), for: .normal)
  131. button.imageView?.contentMode = .scaleAspectFit
  132. button.layer.cornerRadius = button.maxCornerRadius()
  133. button.backgroundColor = .mainColor
  134. button.addTarget(self, action: #selector(raiseHand(sender:)), for: .touchUpInside)
  135. return button
  136. }()
  137. lazy var screenShareBtn: UIButton = {
  138. let button = UIButton()
  139. button.frame = CGRect(x:0, y:0, width:40, height:40)
  140. button.setImage(UIImage(named: "pb_screen_share", in: Bundle.resourceBundle(for: DigiX.self), with: nil), for: .normal)
  141. button.imageView?.contentMode = .scaleAspectFit
  142. button.layer.cornerRadius = button.maxCornerRadius()
  143. button.backgroundColor = .mainColor
  144. button.addTarget(self, action: #selector(screenShare(sender:)), for: .touchUpInside)
  145. return button
  146. }()
  147. lazy var whiteboardBtn: UIButton = {
  148. let button = UIButton()
  149. button.frame = CGRect(x:0, y:0, width:40, height:40)
  150. button.setImage(UIImage(named: "pb_od_draw", in: Bundle.resourceBundle(for: DigiX.self), with: nil), for: .normal)
  151. button.imageView?.contentMode = .scaleAspectFit
  152. button.layer.cornerRadius = button.maxCornerRadius()
  153. button.backgroundColor = .mainColor
  154. button.addTarget(self, action: #selector(whiteboard(sender:)), for: .touchUpInside)
  155. return button
  156. }()
  157. lazy var buttonRotate: UIButton = {
  158. let button = UIButton()
  159. button.frame = CGRect(x:0, y:0, width:40, height:40)
  160. button.setImage(UIImage(named: "pb_ic_camera_rear", in: Bundle.resourceBundle(for: DigiX.self), with: nil), for: .normal)
  161. button.imageView?.contentMode = .scaleAspectFit
  162. button.layer.cornerRadius = button.maxCornerRadius()
  163. button.backgroundColor = .mainColor
  164. button.addTarget(self, action: #selector(camera(sender:)), for: .touchUpInside)
  165. return button
  166. }()
  167. // TODO: badges when raise hand to broadcaster
  168. lazy var handNotif: UIView = {
  169. let view = UIView()
  170. view.frame = CGRect(x:0, y:0, width:10, height:10)
  171. view.backgroundColor = hasRaiseHand ? .systemRed : .white.withAlphaComponent(0)
  172. view.layer.cornerRadius = view.maxCornerRadius()
  173. return view
  174. }()
  175. lazy var stack: UIView = {
  176. let stack = UIView()
  177. return stack
  178. }()
  179. lazy var count: UILabel = {
  180. let count = UILabel()
  181. count.text = "0"
  182. count.font = UIFont.systemFont(ofSize: 14)
  183. count.textColor = .mainColor
  184. return count
  185. }()
  186. lazy var countViewer: UILabel = {
  187. let count = UILabel()
  188. count.text = "0"
  189. count.font = UIFont.systemFont(ofSize: 14)
  190. count.textColor = .mainColor
  191. return count
  192. }()
  193. lazy var toolbarView : UIStackView = {
  194. let stackView = UIStackView()
  195. stackView.axis = .vertical
  196. stackView.alignment = .center
  197. stackView.distribution = .fillEqually
  198. stackView.spacing = 10 // Adjust the spacing between buttons
  199. return stackView
  200. }()
  201. override func viewDidLoad() {
  202. super.viewDidLoad()
  203. navigationController?.changeAppearance(clear: true)
  204. let screenBounds = UIScreen.main.bounds
  205. let screenWidth = screenBounds.width
  206. let screenHeight = screenBounds.height
  207. secondViewWidth = screenWidth / 3.0
  208. secondViewHeight = screenHeight / 3.0
  209. let buttonBack = UIButton()
  210. buttonBack.frame = CGRect(x:0, y:0, width:30, height:30)
  211. buttonBack.setImage(UIImage(systemName: "chevron.backward")?.withTintColor(.mainColor, renderingMode: .alwaysOriginal), for: .normal)
  212. buttonBack.backgroundColor = .white.withAlphaComponent(0.2)
  213. buttonBack.layer.cornerRadius = 15.0
  214. buttonBack.addTarget(self, action: #selector(close(sender:)), for: .touchUpInside)
  215. navigationItem.leftBarButtonItem = UIBarButtonItem(customView: buttonBack)
  216. view.backgroundColor = .clear
  217. if isLive {
  218. view.addSubview(tvCameraPreviewB)
  219. view.addSubview(ivRemoteViewS)
  220. addCountViewerView()
  221. tvCameraPreviewB.anchor(top: view.topAnchor, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor)
  222. }
  223. else {
  224. view.addSubview(tvCameraPreviewS)
  225. view.addSubview(ivRemoteViewM)
  226. view.addSubview(ivRemoteViewS)
  227. ivRemoteViewM.anchor(top: view.topAnchor, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor)
  228. }
  229. view.addSubview(statusView)
  230. statusView.backgroundColor = .white.withAlphaComponent(0.2)
  231. statusView.layer.cornerRadius = 8.0
  232. statusView.layer.masksToBounds = true
  233. statusView.addSubview(status)
  234. status.anchor(left: statusView.leftAnchor, right: statusView.rightAnchor, paddingLeft: 10, paddingRight: 10, centerX: statusView.centerXAnchor, centerY: statusView.centerYAnchor)
  235. statusView.anchor(top: view.topAnchor, left: view.leftAnchor, right: view.rightAnchor, paddingTop: 30, paddingLeft: 80, paddingRight: 80, centerX: view.centerXAnchor, height: 30, dynamicLeft: true, dynamicRight: true)
  236. view.addSubview(stack)
  237. view.addLayoutGuide(keyboardLayoutGuide)
  238. keyboardTopAnchorConstraint = view.layoutMarginsGuide.bottomAnchor.constraint(equalTo: keyboardLayoutGuide.topAnchor, constant: 0)
  239. keyboardTopAnchorConstraint.isActive = true
  240. keyboardLayoutGuide.bottomAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor).isActive = true
  241. stack.anchor(left: view.leftAnchor, bottom: keyboardLayoutGuide.topAnchor,
  242. right: view.rightAnchor, paddingLeft: 20, paddingBottom: 30, paddingRight: 20, height: 200)
  243. addToolbarView()
  244. stack.addSubview(tableView)
  245. stack.addSubview(textView)
  246. stack.addSubview(send)
  247. send.layer.cornerRadius = 16
  248. send.layer.masksToBounds = true
  249. textView.anchor(left: stack.leftAnchor, bottom: stack.bottomAnchor, right: stack.rightAnchor)
  250. send.anchor(bottom: textView.bottomAnchor, right: textView.rightAnchor, paddingBottom: 1, width: 32, height: 32)
  251. tableView.anchor(left: stack.leftAnchor, bottom: textView.topAnchor, right: stack.rightAnchor)
  252. heightTableView = tableView.heightAnchor.constraint(equalToConstant: 44.0)
  253. heightTableView?.isActive = true
  254. textView.layoutIfNeeded()
  255. textView.layer.cornerRadius = textView.frame.height / 2
  256. textView.textContainerInset = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: textView.frame.height + 8)
  257. view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(hideKeyboard)))
  258. NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
  259. NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil)
  260. DigiX.shared.seminarDelegate = self
  261. if isLive {
  262. API.ibca(sTitle: data, nCamIdx: 1, nResIdx: 2, nVQuality: 4, tvCameraPreview: tvCameraPreviewB, ivRemoteS: ivRemoteViewS)
  263. } else {
  264. API.iabc(nCamIdx: 1, nResIdx: 2, nVQuality: 4, tvCameraPreview: tvCameraPreviewS, sBroadcasterID: data, ivRemoteM: ivRemoteViewM, ivRemoteS: ivRemoteViewS)
  265. }
  266. }
  267. func addToolbarView(){
  268. if isLive {
  269. buttonRotate.widthAnchor.constraint(equalToConstant: 40).isActive = true
  270. buttonRotate.heightAnchor.constraint(equalToConstant: 40).isActive = true
  271. toolbarView.insertArrangedSubview(buttonRotate, at: 0)
  272. }
  273. else {
  274. raiseHandBtn.widthAnchor.constraint(equalToConstant: 40).isActive = true
  275. raiseHandBtn.heightAnchor.constraint(equalToConstant: 40).isActive = true
  276. toolbarView.insertArrangedSubview(raiseHandBtn, at: 0)
  277. }
  278. whiteboardBtn.widthAnchor.constraint(equalToConstant: 40).isActive = true
  279. whiteboardBtn.heightAnchor.constraint(equalToConstant: 40).isActive = true
  280. screenShareBtn.widthAnchor.constraint(equalToConstant: 40).isActive = true
  281. screenShareBtn.heightAnchor.constraint(equalToConstant: 40).isActive = true
  282. toolbarView.addArrangedSubview(whiteboardBtn)
  283. toolbarView.addArrangedSubview(screenShareBtn)
  284. // Add the stack view to the view controller's view
  285. view.addSubview(toolbarView)
  286. // Set constraints for the stack view
  287. toolbarView.translatesAutoresizingMaskIntoConstraints = false
  288. NSLayoutConstraint.activate([
  289. toolbarView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 10),
  290. toolbarView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10),
  291. toolbarView.heightAnchor.constraint(equalToConstant: 140),
  292. toolbarView.widthAnchor.constraint(equalToConstant: 45)
  293. ])
  294. }
  295. func switchRaiseHand(raiseHand: Bool){
  296. if hasRaiseHand {
  297. raiseHandBtn.removeFromSuperview()
  298. buttonRotate.widthAnchor.constraint(equalToConstant: 40).isActive = true
  299. buttonRotate.heightAnchor.constraint(equalToConstant: 40).isActive = true
  300. toolbarView.insertArrangedSubview(buttonRotate, at: 0)
  301. }
  302. else {
  303. buttonRotate.removeFromSuperview()
  304. raiseHandBtn.widthAnchor.constraint(equalToConstant: 40).isActive = true
  305. raiseHandBtn.heightAnchor.constraint(equalToConstant: 40).isActive = true
  306. toolbarView.insertArrangedSubview(raiseHandBtn, at: 0)
  307. }
  308. }
  309. func addCountViewerView() {
  310. view.addSubview(viewCountViewer)
  311. viewCountViewer.anchor(top: view.safeAreaLayoutGuide.topAnchor, left: view.leftAnchor, paddingLeft: 20, height: 40)
  312. viewCountViewer.backgroundColor = .white.withAlphaComponent(0.2)
  313. viewCountViewer.layer.cornerRadius = 8.0
  314. viewCountViewer.layer.masksToBounds = true
  315. let imageEye = UIImageView()
  316. viewCountViewer.addSubview(imageEye)
  317. imageEye.anchor(left: viewCountViewer.leftAnchor, paddingLeft: 5.0, centerY: viewCountViewer.centerYAnchor)
  318. imageEye.image = UIImage(systemName: "eye.fill")?.withTintColor(.black, renderingMode: .alwaysOriginal)
  319. viewCountViewer.addSubview(countViewer)
  320. countViewer.anchor(left: imageEye.rightAnchor, right:viewCountViewer.rightAnchor, paddingLeft: 5.0, paddingRight: 5.0, centerY: viewCountViewer.centerYAnchor)
  321. viewCountViewer.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(showListViewer(sender:))))
  322. }
  323. override func viewWillDisappear(_ animated: Bool) {
  324. navigationController?.changeAppearance(clear: false)
  325. }
  326. @objc func close(sender: Any?) {
  327. hideKeyboard()
  328. var alert = LibAlertController(title: "", message: "Are you sure you want to end Seminar?".localized(), preferredStyle: .alert)
  329. if !isLive {
  330. alert = LibAlertController(title: "", message: "Are you sure you want to leave Seminar?".localized(), preferredStyle: .alert)
  331. }
  332. alert.addAction(UIAlertAction(title: "No".localized(), style: UIAlertAction.Style.default, handler: nil))
  333. alert.addAction(UIAlertAction(title: "Yes".localized(), style: UIAlertAction.Style.default, handler: {[weak self] _ in
  334. DispatchQueue.global().async {
  335. API.terminateBC(sBroadcasterID: self?.isLive ?? true ? nil : self?.data)
  336. self?.sendLeft()
  337. }
  338. self?.navigationController?.dismiss(animated: true, completion: nil)
  339. }))
  340. self.present(alert, animated: true, completion: nil)
  341. }
  342. @objc func camera(sender: Any?) {
  343. if frontCamera {
  344. API.changeCameraParam(nCameraIdx: 0, nResolutionIndex: 2, nQuality: 4)
  345. frontCamera = false
  346. } else {
  347. API.changeCameraParam(nCameraIdx: 1, nResolutionIndex: 2, nQuality: 4)
  348. frontCamera = true
  349. }
  350. }
  351. @objc func sent(sender: Any?) {
  352. if textView.textColor == UIColor.secondaryColor {
  353. return
  354. }
  355. guard let text = textView.text, !text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
  356. return
  357. }
  358. guard let me = User.getDataCanNil(pin: UserDefaults.standard.string(forKey: "me")) else {
  359. return
  360. }
  361. chats.append(SeminarChat(name: "You".localized(), thumb: me.thumb, messageText: text.trimmingCharacters(in: .whitespacesAndNewlines)))
  362. if textViewHeightConstraint != nil {
  363. NSLayoutConstraint.deactivate([textViewHeightConstraint])
  364. }
  365. textView.isScrollEnabled = false
  366. textView.setNeedsUpdateConstraints()
  367. DispatchQueue.global().async {
  368. self.sendChat(text: text.trimmingCharacters(in: .whitespacesAndNewlines))
  369. }
  370. textView.text = ""
  371. }
  372. @objc func screenShare(sender: Any?) {
  373. // TODO: implement screen sharing
  374. }
  375. @objc func whiteboard(sender: Any?) {
  376. // TODO: implement whiteboard
  377. _ = DigiX.write(message: CoreMessage_TMessageBank.getSeminarDraw(broadcaster: data, flag: "1"))
  378. }
  379. @objc func hideKeyboard() {
  380. view.endEditing(true)
  381. }
  382. var isShow: Bool = false
  383. @objc func keyboardWillShow(notification: Notification) {
  384. if !isShow {
  385. isShow = true
  386. keyboard(notification: notification, show: true)
  387. }
  388. }
  389. @objc func keyboardWillHide(notification: Notification) {
  390. isShow = false
  391. keyboard(notification: notification, show: false)
  392. }
  393. private func keyboard(notification: Notification, show: Bool) {
  394. let keyboardFrameEnd = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue
  395. let animationDuration = (notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue
  396. let rawAnimationCurve = (notification.userInfo?[UIResponder.keyboardAnimationCurveUserInfoKey] as? NSNumber)?.uint32Value
  397. guard let animDuration = animationDuration,
  398. let keyboardFrame = keyboardFrameEnd,
  399. let rawAnimCurve = rawAnimationCurve else {
  400. return
  401. }
  402. keyboardTopAnchorConstraint.constant = show ? keyboardFrame.cgRectValue.height : 0
  403. view.setNeedsLayout()
  404. let rawAnimCurveAdjusted = UInt(rawAnimCurve << 16)
  405. let animationCurve = UIView.AnimationOptions(rawValue: rawAnimCurveAdjusted)
  406. UIView.animate(withDuration: animDuration, delay: 0.0, options: [.beginFromCurrentState, animationCurve], animations: {
  407. self.view.layoutIfNeeded()
  408. }, completion: nil)
  409. }
  410. deinit {
  411. NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil)
  412. NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil)
  413. }
  414. private func sendLive() {
  415. guard let title = streamingData["title"] as? String else {
  416. return
  417. }
  418. guard let type = streamingData["type"] as? String else {
  419. return
  420. }
  421. guard let blog = streamingData["blog"] as? String else {
  422. return
  423. }
  424. _ = DigiX.write(message: CoreMessage_TMessageBank.getStartSeminarInvited(title: "1~\(title)", type: type, typeValue: "", category: "3", blog_id: blog))
  425. }
  426. private func sendJoin() {
  427. let id = Date().currentTimeMillis().toHex()
  428. _ = DigiX.write(message: CoreMessage_TMessageBank.joinSeminar(broadcast_id: data, request_id: id))
  429. }
  430. public func sendLeft() {
  431. let id = Date().currentTimeMillis().toHex()
  432. _ = DigiX.write(message: CoreMessage_TMessageBank.leftSeminar(broadcast_id: data, request_id: id))
  433. }
  434. private func sendChat(text: String) {
  435. _ = DigiX.write(message: CoreMessage_TMessageBank.getSendSeminarChat(l_pin: data, message_text: text))
  436. }
  437. @objc func raiseHand(sender: Any?) {
  438. guard let me = User.getDataCanNil(pin: UserDefaults.standard.string(forKey: "me")) else {
  439. return
  440. }
  441. let status = hasRaiseHand ? "0" : "1"
  442. hasRaiseHand = !hasRaiseHand
  443. _ = DigiX.write(message: CoreMessage_TMessageBank.getSeminarRaiseHand(p_pin: me.pin, l_pin: data, status: status))
  444. }
  445. @objc func showListViewer(sender: Any?){
  446. let controller = AppStoryBoard.Palio.instance.instantiateViewController(identifier: "seminarListNav") as! UINavigationController
  447. if let vc = controller.viewControllers.first as? SeminarListViewController {
  448. vc.data = viewers
  449. vc.makeSpeaker = { viewer in
  450. API.sabc(sAudienceID: viewer.f_pin)
  451. self.viewers.first(where: {$0.f_pin == viewer.f_pin})?.isRaise = false
  452. self.viewers.first(where: {$0.f_pin == viewer.f_pin})?.isSpeak = true
  453. }
  454. vc.removeSpeaker = { viewer in
  455. API.eabc(sAudienceID: viewer.f_pin)
  456. self.viewers.first(where: {$0.f_pin == viewer.f_pin})?.isSpeak = false
  457. }
  458. }
  459. self.navigationController?.present(controller, animated: true, completion: nil)
  460. }
  461. func switchBroadcaster() {
  462. if isLive {
  463. if(currentSpeakingBC == "-666") {
  464. // toast return
  465. return
  466. }
  467. mIsSwitch = !mIsSwitch
  468. if mIsSwitch {
  469. adjust(viewOnFront: tvCameraPreviewB, viewOnBack: ivRemoteViewS)
  470. } else {
  471. adjust(viewOnFront: ivRemoteViewS, viewOnBack: tvCameraPreviewB)
  472. }
  473. } else {
  474. if(currentSpeakingVW == "-666") {
  475. // toast return
  476. return
  477. }
  478. mIsSwitch = !mIsSwitch
  479. let me = UserDefaults.standard.string(forKey: "me")
  480. if currentSpeakingVW == me {
  481. if mIsSwitch {
  482. adjust(viewOnFront: ivRemoteViewM, viewOnBack: tvCameraPreviewS)
  483. }
  484. else {
  485. adjust(viewOnFront: tvCameraPreviewS, viewOnBack: ivRemoteViewM)
  486. }
  487. }
  488. else {
  489. if mIsSwitch {
  490. adjust(viewOnFront: ivRemoteViewM, viewOnBack: ivRemoteViewS)
  491. }
  492. else {
  493. adjust(viewOnFront: ivRemoteViewS, viewOnBack: ivRemoteViewM)
  494. }
  495. }
  496. }
  497. }
  498. func forceRevertSwitch() {
  499. if isLive {
  500. adjust(viewOnFront: ivRemoteViewS, viewOnBack: tvCameraPreviewB)
  501. mIsSwitch = false
  502. } else {
  503. let me = UserDefaults.standard.string(forKey: "me")
  504. if currentSpeakingVW == me {
  505. adjust(viewOnFront: tvCameraPreviewS, viewOnBack: ivRemoteViewS)
  506. } else {
  507. adjust(viewOnFront: ivRemoteViewS, viewOnBack: ivRemoteViewM)
  508. }
  509. mIsSwitch = false
  510. }
  511. }
  512. func adjust(viewOnFront: UIImageView, viewOnBack: UIImageView) {
  513. viewOnFront.removeConstraints(viewOnFront.constraints)
  514. viewOnBack.removeConstraints(viewOnBack.constraints)
  515. viewOnFront.anchor(bottom: view.bottomAnchor, right: view.rightAnchor, paddingBottom: 70.0, width: secondViewWidth, height: secondViewHeight)
  516. viewOnBack.anchor(top: view.topAnchor, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor)
  517. btf()
  518. view.bringSubviewToFront(viewOnFront)
  519. }
  520. func btf() {
  521. view.bringSubviewToFront(stack)
  522. view.bringSubviewToFront(statusView)
  523. view.bringSubviewToFront(toolbarView)
  524. if isLive {
  525. view.bringSubviewToFront(viewCountViewer)
  526. }
  527. }
  528. }
  529. extension SeminarViewController: UITextViewDelegate {
  530. func textViewDidChange(_ textView: UITextView) {
  531. isOversized = textView.contentSize.height >= 100
  532. }
  533. func textViewDidBeginEditing(_ textView: UITextView) {
  534. if textView.textColor == UIColor.secondaryColor {
  535. textView.text = nil
  536. textView.textColor = .white
  537. }
  538. }
  539. func textViewDidEndEditing(_ textView: UITextView) {
  540. if textView.text.isEmpty {
  541. textView.text = "Send Comment".localized()
  542. textView.textColor = UIColor.secondaryColor
  543. }
  544. }
  545. }
  546. extension SeminarViewController: UITableViewDataSource {
  547. func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  548. return chats.count
  549. }
  550. func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  551. let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)
  552. cell.tintColor = .clear
  553. cell.backgroundColor = .clear
  554. cell.selectionStyle = .none
  555. let chat = chats[indexPath.row]
  556. let content = cell.contentView
  557. content.subviews.forEach({ $0.removeFromSuperview() })
  558. let viewContent = UIView()
  559. content.addSubview(viewContent)
  560. viewContent.anchor(top: content.topAnchor, left: content.leftAnchor, bottom: content.bottomAnchor, right: content.rightAnchor)
  561. viewContent.backgroundColor = .clear
  562. if !chat.isInfo {
  563. let image = UIImageView()
  564. viewContent.addSubview(image)
  565. image.anchor(top: viewContent.topAnchor, left: viewContent.leftAnchor, width: 30, height: 30)
  566. if !chat.thumb.isEmpty {
  567. getImage(name: chat.thumb, placeholderImage: UIImage(systemName: "person.circle.fill")!, isCircle: true, tableView: tableView, indexPath: indexPath, completion: { result, isDownloaded, imagePerson in
  568. image.image = imagePerson
  569. })
  570. } else {
  571. image.image = UIImage(systemName: "person.circle.fill")!
  572. image.tintColor = .lightGray
  573. }
  574. let name = UILabel()
  575. viewContent.addSubview(name)
  576. name.anchor(top: viewContent.topAnchor, left: image.rightAnchor, paddingLeft: 3.0)
  577. name.numberOfLines = 1
  578. name.text = chat.name
  579. name.font = UIFont.boldSystemFont(ofSize: 14)
  580. name.textColor = .secondaryColor
  581. let message = UILabel()
  582. viewContent.addSubview(message)
  583. message.anchor(top: name.bottomAnchor, left: image.rightAnchor, bottom: content.bottomAnchor, right:viewContent.rightAnchor, paddingLeft: 3.0, paddingBottom: 5.0)
  584. message.numberOfLines = 0
  585. message.text = chat.messageText
  586. message.font = UIFont.systemFont(ofSize: 14)
  587. message.textColor = .white
  588. } else {
  589. let image = UIImageView()
  590. viewContent.addSubview(image)
  591. image.anchor(left: viewContent.leftAnchor, centerY: viewContent.centerYAnchor, width: 30, height: 30)
  592. if !chat.thumb.isEmpty {
  593. getImage(name: chat.thumb, placeholderImage: UIImage(systemName: "person.circle.fill")!, isCircle: true, tableView: tableView, indexPath: indexPath, completion: { result, isDownloaded, imagePerson in
  594. image.image = imagePerson
  595. })
  596. } else {
  597. image.image = UIImage(systemName: "person.circle.fill")!
  598. image.tintColor = .lightGray
  599. }
  600. let name = UILabel()
  601. viewContent.addSubview(name)
  602. name.anchor(left: image.rightAnchor, paddingLeft: 3.0, centerY: viewContent.centerYAnchor)
  603. name.numberOfLines = 1
  604. name.text = chat.name
  605. name.font = UIFont.italicSystemFont(ofSize: 14)
  606. name.textColor = .secondaryColor
  607. let message = UILabel()
  608. viewContent.addSubview(message)
  609. message.anchor(left: name.rightAnchor, paddingLeft: 3.0, centerY: viewContent.centerYAnchor)
  610. message.numberOfLines = 0
  611. message.text = chat.messageText
  612. message.font = UIFont.italicSystemFont(ofSize: 14)
  613. message.textColor = .white
  614. }
  615. return cell
  616. }
  617. }
  618. extension SeminarViewController: SeminarDelegate {
  619. func onStartSeminar(state: Int, message: String) {
  620. if state == DigiX.OUTGOING_CALL, message.contains("Initiating") {
  621. DispatchQueue.main.async {
  622. self.tvCameraPreviewB.transform = CGAffineTransform.init(scaleX: 1.6, y: 1.6)
  623. }
  624. } else if state == 12 {
  625. sendLive()
  626. }
  627. }
  628. func onJoinSeminar(state: Int, message: String) {
  629. if state == DigiX.AUDIO_CALL_OFFHOOK {
  630. let m = message.split(separator: ",")
  631. let _ = String(m[0])
  632. let _ = String(m[1])
  633. let camera = Int(m[2])
  634. let platform = Int(m[3])
  635. if platform == 1 { // Android
  636. DispatchQueue.main.async {
  637. self.ivRemoteViewM.transform = CGAffineTransform.init(scaleX: 1.9, y: 1.9).rotated(by: camera == 1 ? (CGFloat.pi * 3)/2 : (CGFloat.pi)/2)
  638. }
  639. } else {
  640. DispatchQueue.main.async {
  641. self.ivRemoteViewM.transform = CGAffineTransform.init(scaleX: 1.9, y: 1.9).rotated(by: camera == 1 ? (CGFloat.pi * 5)/2 : (CGFloat.pi)/2)
  642. }
  643. }
  644. sendJoin()
  645. } else if state == DigiX.AUDIO_CALL_RINGING {
  646. let m = message.split(separator: ",")
  647. let _ = String(m[0])
  648. let _ = String(m[1])
  649. let camera = Int(m[2])
  650. let platform = Int(m[3])
  651. if platform == 1 { // Android
  652. DispatchQueue.main.async {
  653. self.ivRemoteViewM.transform = CGAffineTransform.init(scaleX: 1.9, y: 1.9).rotated(by: camera == 1 ? (CGFloat.pi * 3)/2 : (CGFloat.pi)/2)
  654. }
  655. } else {
  656. DispatchQueue.main.async {
  657. self.ivRemoteViewM.transform = CGAffineTransform.init(scaleX: 1.9, y: 1.9).rotated(by: camera == 1 ? (CGFloat.pi * 5)/2 : (CGFloat.pi)/2)
  658. }
  659. }
  660. } else if state == DigiX.VIDEO_CALL_OFFHOOK { // initBCA (3* is from Broadcaster PoV)
  661. DispatchQueue.main.async {
  662. self.tvCameraPreviewB.removeConstraints(self.tvCameraPreviewB.constraints)
  663. self.tvCameraPreviewB.anchor(top: self.view.topAnchor, left: self.view.leftAnchor, bottom: self.view.bottomAnchor, right: self.view.rightAnchor)
  664. self.view.bringSubviewToFront(self.tvCameraPreviewB)
  665. self.btf()
  666. }
  667. } else if state == DigiX.VIDEO_CALL_RINGING { // startAudience (3* is from Broadcaster PoV)
  668. let m = message.split(separator: ",")
  669. let f_pin = m[0]
  670. let camera = Int(m[1])
  671. DispatchQueue.main.async {
  672. let rotation = camera == 1 ? CGFloat.pi * 2.5 : CGFloat.pi * 0.5
  673. self.ivRemoteViewS.transform = CGAffineTransform.init(scaleX: 1.9, y: 1.9).rotated(by: rotation)
  674. self.ivSRotation = rotation
  675. }
  676. if currentSpeakingBC != "-666" {
  677. viewers.first(where: {$0.f_pin == currentSpeakingBC})?.isRaise = false
  678. viewers.first(where: {$0.f_pin == currentSpeakingBC})?.isSpeak = false
  679. }
  680. if currentSpeakingBC != f_pin {
  681. DispatchQueue.main.async {
  682. self.forceRevertSwitch()
  683. }
  684. }
  685. viewers.first(where: {$0.f_pin == currentSpeakingBC})?.isRaise = false
  686. viewers.first(where: {$0.f_pin == currentSpeakingBC})?.isSpeak = true
  687. // TODO: remove badge if no raise hand
  688. } else if state == DigiX.VIDEO_CAMERA_PARAMS_CHANED { // endAudience (3* is from Broadcaster PoV)
  689. let m = message.split(separator: ",")
  690. let f_pin = m[0]
  691. if currentSpeakingBC == f_pin {
  692. DispatchQueue.main.async {
  693. self.forceRevertSwitch()
  694. }
  695. currentSpeakingBC = "-666"
  696. }
  697. DispatchQueue.main.async {
  698. let rotation = -(self.ivSRotation)
  699. self.ivRemoteViewS.transform = CGAffineTransform.init(scaleX: 1.9, y: 1.9).rotated(by: rotation)
  700. self.ivSRotation = 0
  701. }
  702. viewers.first(where: {$0.f_pin == f_pin})?.isRaise = false
  703. viewers.first(where: {$0.f_pin == f_pin})?.isSpeak = false
  704. } else if state == DigiX.VIDEO_CALL_MUTE_UNMUTE || state == 46 { // audience change camera (3* is from Broadcaster PoV)
  705. let m = message.split(separator: ",")
  706. let camera = Int(m[2])
  707. let rotation = camera == 1 ? CGFloat.pi * 2.5 : CGFloat.pi * 0.5
  708. let scaleY = camera == 1 ? -1.0 : 1.0
  709. DispatchQueue.main.async {
  710. self.ivRemoteViewS.transform = CGAffineTransform.init(scaleX: 1.9, y: 1.9 * scaleY).rotated(by: rotation - self.ivSRotation)
  711. self.ivSRotation = rotation
  712. }
  713. } else if state == 42 { // joinBC (4* is from Audience PoV)
  714. let m = message.split(separator: ",")
  715. let camera = Int(m[2])
  716. var x = "NONE"
  717. if m.indices.contains(5){
  718. x = String(m[5])
  719. }
  720. let rotation = camera == 1 ? CGFloat.pi * 2.5 : CGFloat.pi * 0.5
  721. let rotation2 = -(self.ivSRotation)
  722. DispatchQueue.main.async {
  723. self.ivRemoteViewM.transform = CGAffineTransform.init(scaleX: 1.9, y: 1.9).rotated(by: rotation - self.ivMRotation)
  724. self.ivMRotation = rotation
  725. self.ivRemoteViewM.removeConstraints(self.ivRemoteViewM.constraints)
  726. self.ivRemoteViewM.anchor(top: self.view.topAnchor, left: self.view.leftAnchor, bottom: self.view.bottomAnchor, right: self.view.rightAnchor)
  727. self.view.bringSubviewToFront(self.ivRemoteViewM)
  728. self.ivRemoteViewS.transform = CGAffineTransform.init(scaleX: 1.9, y: 1.9).rotated(by: rotation2)
  729. self.ivSRotation = 0
  730. if x != "NONE" {
  731. let camera2 = Int(m[6])
  732. let rotation3 = camera2 == 1 ? CGFloat.pi * 2.5 : CGFloat.pi * 0.5
  733. let scaleY = camera2 == 1 ? -1.0 : 1.0
  734. self.ivRemoteViewS.transform = CGAffineTransform.init(scaleX: 1.9, y: 1.9 * scaleY).rotated(by: rotation3 - self.ivSRotation)
  735. self.ivSRotation = rotation3
  736. self.view.bringSubviewToFront(self.ivRemoteViewS)
  737. }
  738. self.btf()
  739. }
  740. sendJoin()
  741. } else if state == 43 { // startAudience (4* is from Audience PoV)
  742. let m = message.split(separator: ",")
  743. let f_pin = String(m[0])
  744. let me = UserDefaults.standard.string(forKey: "me")
  745. DispatchQueue.main.async {
  746. self.tvCameraPreviewS.removeConstraints(self.tvCameraPreviewS.constraints)
  747. self.tvCameraPreviewS.anchor(top: self.view.topAnchor, left: self.view.leftAnchor, bottom: self.view.bottomAnchor, right: self.view.rightAnchor)
  748. self.currentSpeakingVW = f_pin
  749. if self.currentSpeakingVW == me {
  750. self.hasRaiseHand = false
  751. self.switchRaiseHand(raiseHand: false)
  752. }
  753. else {
  754. self.switchRaiseHand(raiseHand: true)
  755. }
  756. // TODO: set whiteboard can draw here
  757. }
  758. } else if state == 44 { // endAudience (4* is from Audience PoV)
  759. let m = message.split(separator: ",")
  760. let f_pin = String(m[0])
  761. let camera = Int(m[1])
  762. let rotation = camera == 1 ? CGFloat.pi * 2.5 : CGFloat.pi * 0.5
  763. let scaleY = camera == 1 ? -1.0 : 1.0
  764. let rotation2 = -(self.ivSRotation)
  765. if f_pin != "NONE" {
  766. DispatchQueue.main.async {
  767. if self.mIsSwitch {
  768. self.forceRevertSwitch()
  769. }
  770. self.currentSpeakingVW = f_pin
  771. self.ivRemoteViewS.transform = CGAffineTransform.init(scaleX: 1.9, y: 1.9 * scaleY).rotated(by: rotation - self.ivSRotation)
  772. self.ivSRotation = rotation
  773. self.view.bringSubviewToFront(self.ivRemoteViewS)
  774. }
  775. } else {
  776. DispatchQueue.main.async {
  777. if self.mIsSwitch {
  778. self.forceRevertSwitch()
  779. }
  780. let me = UserDefaults.standard.string(forKey: "me")
  781. if me == f_pin {
  782. self.currentSpeakingVW = "-666"
  783. }
  784. self.ivRemoteViewS.transform = CGAffineTransform.init(scaleX: 1.9, y: 1.9).rotated(by: rotation2)
  785. self.ivSRotation = 0
  786. self.hasRaiseHand = false
  787. self.switchRaiseHand(raiseHand: true)
  788. // TODO: set whiteboard can draw here
  789. }
  790. }
  791. } else if state == 45 { // CCPb Br.ID Br.Title Br.CameraID Br.OS : (4* is from Audience PoV) broadcaster change camera
  792. let m = message.split(separator: ",")
  793. let camera = Int(m[2])
  794. let rotation = camera == 1 ? CGFloat.pi * 2.5 : CGFloat.pi * 0.5
  795. DispatchQueue.main.async {
  796. self.ivRemoteViewM.transform = CGAffineTransform.init(scaleX: 1.9, y: 1.9).rotated(by: rotation - self.ivMRotation)
  797. self.ivMRotation = rotation
  798. }
  799. } else if state == DigiX.STREAMING_SEMINAR_ENDED {
  800. DispatchQueue.main.async {
  801. self.status.text = "Seminar ended".localized()
  802. }
  803. DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
  804. self.navigationController?.dismiss(animated: true, completion: nil)
  805. })
  806. } else if state == 95 { // chat
  807. let m = message.split(separator: ",", omittingEmptySubsequences: false)
  808. let name = m[3].trimmingCharacters(in: .whitespaces)
  809. let thumb = m[2].trimmingCharacters(in: .whitespaces)
  810. let text = m[4].trimmingCharacters(in: .whitespaces)
  811. chats.append(SeminarChat(name: name, thumb: thumb, messageText: text))
  812. } else if state == 96 { // raise hand
  813. let m = message.split(separator: ",", omittingEmptySubsequences: false)
  814. let f_pin = m[0].trimmingCharacters(in: .whitespaces)
  815. let broadcaster = m[1].trimmingCharacters(in: .whitespaces)
  816. let status = m[2].trimmingCharacters(in: .whitespaces)
  817. guard broadcaster == data else {
  818. return
  819. }
  820. if(status == "1"){
  821. hasRaiseHand = true
  822. viewers.first(where: { $0.f_pin == f_pin })?.isRaise = true
  823. // TODO: edit badges
  824. }
  825. else {
  826. viewers.first(where: { $0.f_pin == f_pin })?.isRaise = false
  827. if viewers.filter({ $0.isRaise }).count == 0 {
  828. hasRaiseHand = false
  829. // TODO: edit badges
  830. }
  831. }
  832. } else if state == 97 { // someone left
  833. let m = message.split(separator: ",", omittingEmptySubsequences: false)
  834. let name = m[3].trimmingCharacters(in: .whitespaces)
  835. let thumb = m[2].trimmingCharacters(in: .whitespaces)
  836. let f_pin = m[1].trimmingCharacters(in: .whitespaces)
  837. let text = "Left".localized()
  838. viewers.removeAll{ $0.f_pin == f_pin }
  839. chats.append(SeminarChat(name: name, thumb: thumb, messageText: text, isInfo: true))
  840. DispatchQueue.main.async {
  841. self.countViewer.text = "\(Int(self.countViewer.text!)! - 1)"
  842. }
  843. } else if state == 98 { // someone join
  844. let m = message.split(separator: ",", omittingEmptySubsequences: false)
  845. let name = m[3].trimmingCharacters(in: .whitespaces)
  846. let thumb = m[2].trimmingCharacters(in: .whitespaces)
  847. let text = "Joined".localized()
  848. let f_pin = m[1].trimmingCharacters(in: .whitespaces)
  849. viewers.append(SeminarViewer(f_pin: f_pin, thumb: thumb, name: name))
  850. chats.append(SeminarChat(name: name, thumb: thumb, messageText: text, isInfo: true))
  851. DispatchQueue.main.async {
  852. self.countViewer.text = "\(Int(self.countViewer.text!)! + 1)"
  853. }
  854. }
  855. }
  856. }
  857. class SeminarChat: Model {
  858. let name: String
  859. let thumb: String
  860. let messageText: String
  861. let isInfo: Bool
  862. init(viewer: SeminarViewer, messageText: String, isInfo: Bool = false) {
  863. self.name = viewer.name
  864. self.thumb = viewer.thumb
  865. self.messageText = messageText
  866. self.isInfo = isInfo
  867. }
  868. init(name: String, thumb: String, messageText: String, isInfo: Bool = false) {
  869. self.name = name
  870. self.thumb = thumb
  871. self.messageText = messageText
  872. self.isInfo = isInfo
  873. }
  874. static func == (lhs: SeminarChat, rhs: SeminarChat) -> Bool {
  875. return false
  876. }
  877. var description: String {
  878. return ""
  879. }
  880. }
  881. class SeminarViewer : Model {
  882. let f_pin: String
  883. let thumb: String
  884. let name: String
  885. var isRaise: Bool
  886. var isSpeak: Bool
  887. init(f_pin: String, thumb: String, name: String, isRaise: Bool = false, isSpeak: Bool = false){
  888. self.f_pin = f_pin
  889. self.thumb = thumb
  890. self.name = name
  891. self.isRaise = isRaise
  892. self.isSpeak = isSpeak
  893. }
  894. var description: String {
  895. return ""
  896. }
  897. static func == (lhs: SeminarViewer, rhs: SeminarViewer) -> Bool {
  898. lhs.f_pin == rhs.f_pin
  899. }
  900. }