Steven 3 年之前
父节点
当前提交
dc705ba860
共有 100 个文件被更改,包括 13779 次插入52 次删除
  1. 360 0
      KulexiuForStudent/KulexiuForStudent.xcodeproj/project.pbxproj
  2. 二进制
      KulexiuForStudent/KulexiuForStudent.xcworkspace/xcuserdata/wangzhi.xcuserdatad/UserInterfaceState.xcuserstate
  3. 16 16
      KulexiuForStudent/KulexiuForStudent.xcworkspace/xcuserdata/wangzhi.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
  4. 1055 1
      KulexiuForStudent/KulexiuForStudent/Common/Base/KSAccompanyWebViewController.m
  5. 12 10
      KulexiuForStudent/KulexiuForStudent/Common/Base/KSBaseWKWebViewController.m
  6. 10 0
      KulexiuForStudent/KulexiuForStudent/Common/Base/KSNetworkingManager.h
  7. 18 0
      KulexiuForStudent/KulexiuForStudent/Common/Base/KSNetworkingManager.m
  8. 40 0
      KulexiuForStudent/KulexiuForStudent/Common/Base/KSVideoRecordManager.h
  9. 434 0
      KulexiuForStudent/KulexiuForStudent/Common/Base/KSVideoRecordManager.m
  10. 1 0
      KulexiuForStudent/KulexiuForStudent/Common/Define/Common.h
  11. 19 0
      KulexiuForStudent/KulexiuForStudent/Common/Tools/NSObject+KeyWindow.h
  12. 24 0
      KulexiuForStudent/KulexiuForStudent/Common/Tools/NSObject+KeyWindow.m
  13. 4 1
      KulexiuForStudent/KulexiuForStudent/Module/Chat/Controller/KSChatListViewController.m
  14. 16 0
      KulexiuForStudent/KulexiuForStudent/Module/Chat/View/KSChatListCell.h
  15. 23 0
      KulexiuForStudent/KulexiuForStudent/Module/Chat/View/KSChatListCell.m
  16. 76 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/BASSMidiPlayer/BassMidiEngine.h
  17. 159 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/BASSMidiPlayer/BassMidiEngine.m
  18. 20 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/Controller/KSCloudViewController.h
  19. 2215 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/Controller/KSCloudViewController.m
  20. 19 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/MidiPlayer/CAudioUnit.h
  21. 12 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/MidiPlayer/CAudioUnit.m
  22. 261 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/MidiPlayer/CoreAudioUtils.c
  23. 15 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/MidiPlayer/CoreAudioUtils.h
  24. 24 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/MidiPlayer/GCDTimer.h
  25. 96 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/MidiPlayer/GCDTimer.m
  26. 150 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/MidiPlayer/MidiPlayerEngine.h
  27. 1006 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/MidiPlayer/MidiPlayerEngine.m
  28. 33 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/Model/CloudSongMessageModel.h
  29. 35 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/Model/CloudSongMessageModel.m
  30. 1206 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/Model/FingerList.plist
  31. 30 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/Model/KSParseMessageModel.h
  32. 13 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/Model/KSParseMessageModel.m
  33. 23 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/Model/KSXMLInfoParse.h
  34. 324 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/Model/KSXMLInfoParse.m
  35. 24 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/Model/KYSourceParseManager.h
  36. 145 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/Model/KYSourceParseManager.m
  37. 76 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/Model/SubjectFinger.plist
  38. 211 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/Model/TBXML.h
  39. 952 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/Model/TBXML.m
  40. 25 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/Channel/TrackChooseView.h
  41. 142 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/Channel/TrackChooseView.m
  42. 113 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/Channel/TrackChooseView.xib
  43. 23 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/Help/CloudFeedbackView.h
  44. 126 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/Help/CloudFeedbackView.m
  45. 325 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/Help/CloudFeedbackView.xib
  46. 23 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/Help/CloudHelpView.h
  47. 254 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/Help/CloudHelpView.m
  48. 131 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/Help/CloudHelpView.xib
  49. 29 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/SettingView/JudgePageView.h
  50. 176 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/SettingView/JudgePageView.m
  51. 157 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/SettingView/JudgePageView.xib
  52. 36 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/SettingView/KSCloudSettingView.h
  53. 139 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/SettingView/KSCloudSettingView.m
  54. 135 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/SettingView/KSCloudSettingView.xib
  55. 36 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/SettingView/SettingPageView.h
  56. 155 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/SettingView/SettingPageView.m
  57. 190 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/SettingView/SettingPageView.xib
  58. 43 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/SoundCheckView/SoundCheckView.h
  59. 153 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/SoundCheckView/SoundCheckView.m
  60. 177 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/SoundCheckView/SoundCheckView.xib
  61. 38 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/SpeedSlider/KSSliderView.h
  62. 154 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/SpeedSlider/KSSliderView.m
  63. 29 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/SpeedSlider/KSTrackingSlider.h
  64. 70 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/SpeedSlider/KSTrackingSlider.m
  65. 25 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/StaffView/CloudControlButton.h
  66. 132 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/StaffView/CloudControlButton.m
  67. 20 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/StaffView/ScoreAnimationView.h
  68. 76 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/StaffView/ScoreAnimationView.m
  69. 51 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/StaffView/StaffImageDisplayView.h
  70. 109 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/StaffView/StaffImageDisplayView.m
  71. 39 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/TipsAlert/EvaluateResultAlert.h
  72. 144 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/TipsAlert/EvaluateResultAlert.m
  73. 391 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/TipsAlert/EvaluateResultAlert.xib
  74. 22 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/TipsAlert/NoWiredTipsAlert.h
  75. 49 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/TipsAlert/NoWiredTipsAlert.m
  76. 121 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/TipsAlert/NoWiredTipsAlert.xib
  77. 34 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/beat/KSCloudBeatView.h
  78. 427 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/beat/KSCloudBeatView.m
  79. 42 0
      KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/beat/KSCloudBeatView.xib
  80. 2 0
      KulexiuForStudent/KulexiuForStudent/Module/Course/Controller/CourseViewController.m
  81. 20 15
      KulexiuForStudent/KulexiuForStudent/Module/Home/Controller/HomeViewController.m
  82. 17 1
      KulexiuForStudent/KulexiuForStudent/Module/Live/Controller/LiveVideoRoomViewController.m
  83. 3 0
      KulexiuForStudent/KulexiuForStudent/Module/Mine/Controller/MineViewController.m
  84. 2 2
      KulexiuForStudent/KulexiuForStudent/Module/Mine/Setting/View/AboutUsBodyView.m
  85. 13 2
      KulexiuForStudent/KulexiuForStudent/Module/Mine/Setting/View/AboutUsBodyView.xib
  86. 二进制
      KulexiuForStudent/KulexiuForStudent/SoundFontFile/synthgms.sf2
  87. 二进制
      KulexiuForStudent/build/Debug-iphonesimulator/AFNetworking/AFNetworking.framework/AFNetworking
  88. 二进制
      KulexiuForStudent/build/Debug-iphonesimulator/AFNetworking/AFNetworking.framework/Info.plist
  89. 1 1
      KulexiuForStudent/build/Debug-iphonesimulator/AFNetworking/AFNetworking.framework/_CodeSignature/CodeResources
  90. 二进制
      KulexiuForStudent/build/Debug-iphonesimulator/CHIPageControl/CHIPageControl.framework/CHIPageControl
  91. 二进制
      KulexiuForStudent/build/Debug-iphonesimulator/CHIPageControl/CHIPageControl.framework/Info.plist
  92. 1 1
      KulexiuForStudent/build/Debug-iphonesimulator/CHIPageControl/CHIPageControl.framework/_CodeSignature/CodeResources
  93. 二进制
      KulexiuForStudent/build/Debug-iphonesimulator/IQKeyboardManager/IQKeyboardManager.framework/IQKeyboardManager
  94. 二进制
      KulexiuForStudent/build/Debug-iphonesimulator/IQKeyboardManager/IQKeyboardManager.framework/Info.plist
  95. 1 1
      KulexiuForStudent/build/Debug-iphonesimulator/IQKeyboardManager/IQKeyboardManager.framework/_CodeSignature/CodeResources
  96. 二进制
      KulexiuForStudent/build/Debug-iphonesimulator/JXCategoryView/JXCategoryView.framework/Info.plist
  97. 二进制
      KulexiuForStudent/build/Debug-iphonesimulator/JXCategoryView/JXCategoryView.framework/JXCategoryView
  98. 1 1
      KulexiuForStudent/build/Debug-iphonesimulator/JXCategoryView/JXCategoryView.framework/_CodeSignature/CodeResources
  99. 二进制
      KulexiuForStudent/build/Debug-iphonesimulator/JXPagingView/JXPagingView.framework/Info.plist
  100. 二进制
      KulexiuForStudent/build/Debug-iphonesimulator/JXPagingView/JXPagingView.framework/JXPagingView

+ 360 - 0
KulexiuForStudent/KulexiuForStudent.xcodeproj/project.pbxproj

@@ -441,11 +441,57 @@
 		BC50171227FC0D5600F8BCBC /* SubjectChooseViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = BC50171127FC0D5600F8BCBC /* SubjectChooseViewController.m */; };
 		BC50171527FC0D8300F8BCBC /* SubjectChooseBodyView.m in Sources */ = {isa = PBXBuildFile; fileRef = BC50171427FC0D8300F8BCBC /* SubjectChooseBodyView.m */; };
 		BC50171727FC0D8E00F8BCBC /* SubjectChooseBodyView.xib in Resources */ = {isa = PBXBuildFile; fileRef = BC50171627FC0D8D00F8BCBC /* SubjectChooseBodyView.xib */; };
+		BC5082B4283345A10031DD0A /* KSChatListCell.m in Sources */ = {isa = PBXBuildFile; fileRef = BC5082B3283345A10031DD0A /* KSChatListCell.m */; };
 		BC76630E2827E48800C91A1D /* NotiferMessageModel.m in Sources */ = {isa = PBXBuildFile; fileRef = BC76630C2827E48800C91A1D /* NotiferMessageModel.m */; };
 		BC7663152827E49900C91A1D /* NotiferHeadView.m in Sources */ = {isa = PBXBuildFile; fileRef = BC76630F2827E49800C91A1D /* NotiferHeadView.m */; };
 		BC7663162827E49900C91A1D /* NotiferHeadView.xib in Resources */ = {isa = PBXBuildFile; fileRef = BC7663102827E49800C91A1D /* NotiferHeadView.xib */; };
 		BC7663172827E49900C91A1D /* NotiferMessageCell.m in Sources */ = {isa = PBXBuildFile; fileRef = BC7663122827E49800C91A1D /* NotiferMessageCell.m */; };
 		BC7663182827E49900C91A1D /* NotiferMessageCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = BC7663132827E49900C91A1D /* NotiferMessageCell.xib */; };
+		BC8A4593283DC33400094BBB /* KSCloudViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = BC8A4548283DC33400094BBB /* KSCloudViewController.m */; };
+		BC8A4594283DC33400094BBB /* MidiPlayerEngine.m in Sources */ = {isa = PBXBuildFile; fileRef = BC8A454A283DC33400094BBB /* MidiPlayerEngine.m */; };
+		BC8A4595283DC33400094BBB /* GCDTimer.m in Sources */ = {isa = PBXBuildFile; fileRef = BC8A454B283DC33400094BBB /* GCDTimer.m */; };
+		BC8A4596283DC33400094BBB /* CoreAudioUtils.c in Sources */ = {isa = PBXBuildFile; fileRef = BC8A454C283DC33400094BBB /* CoreAudioUtils.c */; };
+		BC8A4597283DC33400094BBB /* CAudioUnit.m in Sources */ = {isa = PBXBuildFile; fileRef = BC8A454D283DC33400094BBB /* CAudioUnit.m */; };
+		BC8A4598283DC33400094BBB /* KYSourceParseManager.m in Sources */ = {isa = PBXBuildFile; fileRef = BC8A4554283DC33400094BBB /* KYSourceParseManager.m */; };
+		BC8A4599283DC33400094BBB /* TBXML.m in Sources */ = {isa = PBXBuildFile; fileRef = BC8A4557283DC33400094BBB /* TBXML.m */; };
+		BC8A459A283DC33400094BBB /* SubjectFinger.plist in Resources */ = {isa = PBXBuildFile; fileRef = BC8A4558283DC33400094BBB /* SubjectFinger.plist */; };
+		BC8A459B283DC33400094BBB /* CloudSongMessageModel.m in Sources */ = {isa = PBXBuildFile; fileRef = BC8A4559283DC33400094BBB /* CloudSongMessageModel.m */; };
+		BC8A459C283DC33400094BBB /* KSXMLInfoParse.m in Sources */ = {isa = PBXBuildFile; fileRef = BC8A455A283DC33400094BBB /* KSXMLInfoParse.m */; };
+		BC8A459D283DC33400094BBB /* KSParseMessageModel.m in Sources */ = {isa = PBXBuildFile; fileRef = BC8A455C283DC33400094BBB /* KSParseMessageModel.m */; };
+		BC8A459E283DC33400094BBB /* FingerList.plist in Resources */ = {isa = PBXBuildFile; fileRef = BC8A455E283DC33400094BBB /* FingerList.plist */; };
+		BC8A459F283DC33400094BBB /* SettingPageView.xib in Resources */ = {isa = PBXBuildFile; fileRef = BC8A4563283DC33400094BBB /* SettingPageView.xib */; };
+		BC8A45A0283DC33400094BBB /* JudgePageView.xib in Resources */ = {isa = PBXBuildFile; fileRef = BC8A4564283DC33400094BBB /* JudgePageView.xib */; };
+		BC8A45A1283DC33400094BBB /* KSCloudSettingView.m in Sources */ = {isa = PBXBuildFile; fileRef = BC8A4565283DC33400094BBB /* KSCloudSettingView.m */; };
+		BC8A45A2283DC33400094BBB /* SettingPageView.m in Sources */ = {isa = PBXBuildFile; fileRef = BC8A4566283DC33400094BBB /* SettingPageView.m */; };
+		BC8A45A3283DC33400094BBB /* JudgePageView.m in Sources */ = {isa = PBXBuildFile; fileRef = BC8A4567283DC33400094BBB /* JudgePageView.m */; };
+		BC8A45A4283DC33400094BBB /* KSCloudSettingView.xib in Resources */ = {isa = PBXBuildFile; fileRef = BC8A4568283DC33400094BBB /* KSCloudSettingView.xib */; };
+		BC8A45A5283DC33400094BBB /* StaffImageDisplayView.m in Sources */ = {isa = PBXBuildFile; fileRef = BC8A456E283DC33400094BBB /* StaffImageDisplayView.m */; };
+		BC8A45A6283DC33400094BBB /* ScoreAnimationView.m in Sources */ = {isa = PBXBuildFile; fileRef = BC8A456F283DC33400094BBB /* ScoreAnimationView.m */; };
+		BC8A45A7283DC33400094BBB /* CloudControlButton.m in Sources */ = {isa = PBXBuildFile; fileRef = BC8A4570283DC33400094BBB /* CloudControlButton.m */; };
+		BC8A45A8283DC33400094BBB /* KSCloudBeatView.xib in Resources */ = {isa = PBXBuildFile; fileRef = BC8A4572283DC33400094BBB /* KSCloudBeatView.xib */; };
+		BC8A45A9283DC33400094BBB /* KSCloudBeatView.m in Sources */ = {isa = PBXBuildFile; fileRef = BC8A4574283DC33400094BBB /* KSCloudBeatView.m */; };
+		BC8A45AA283DC33400094BBB /* TrackChooseView.m in Sources */ = {isa = PBXBuildFile; fileRef = BC8A4576283DC33400094BBB /* TrackChooseView.m */; };
+		BC8A45AB283DC33400094BBB /* TrackChooseView.xib in Resources */ = {isa = PBXBuildFile; fileRef = BC8A4577283DC33400094BBB /* TrackChooseView.xib */; };
+		BC8A45AC283DC33400094BBB /* KSSliderView.m in Sources */ = {isa = PBXBuildFile; fileRef = BC8A457B283DC33400094BBB /* KSSliderView.m */; };
+		BC8A45AD283DC33400094BBB /* KSTrackingSlider.m in Sources */ = {isa = PBXBuildFile; fileRef = BC8A457C283DC33400094BBB /* KSTrackingSlider.m */; };
+		BC8A45AE283DC33400094BBB /* SoundCheckView.m in Sources */ = {isa = PBXBuildFile; fileRef = BC8A457F283DC33400094BBB /* SoundCheckView.m */; };
+		BC8A45AF283DC33400094BBB /* SoundCheckView.xib in Resources */ = {isa = PBXBuildFile; fileRef = BC8A4580283DC33400094BBB /* SoundCheckView.xib */; };
+		BC8A45B0283DC33400094BBB /* CloudHelpView.m in Sources */ = {isa = PBXBuildFile; fileRef = BC8A4584283DC33400094BBB /* CloudHelpView.m */; };
+		BC8A45B1283DC33400094BBB /* CloudHelpView.xib in Resources */ = {isa = PBXBuildFile; fileRef = BC8A4585283DC33400094BBB /* CloudHelpView.xib */; };
+		BC8A45B2283DC33400094BBB /* CloudFeedbackView.m in Sources */ = {isa = PBXBuildFile; fileRef = BC8A4586283DC33400094BBB /* CloudFeedbackView.m */; };
+		BC8A45B3283DC33400094BBB /* CloudFeedbackView.xib in Resources */ = {isa = PBXBuildFile; fileRef = BC8A4588283DC33400094BBB /* CloudFeedbackView.xib */; };
+		BC8A45B4283DC33400094BBB /* EvaluateResultAlert.xib in Resources */ = {isa = PBXBuildFile; fileRef = BC8A458A283DC33400094BBB /* EvaluateResultAlert.xib */; };
+		BC8A45B5283DC33400094BBB /* EvaluateResultAlert.m in Sources */ = {isa = PBXBuildFile; fileRef = BC8A458D283DC33400094BBB /* EvaluateResultAlert.m */; };
+		BC8A45B6283DC33400094BBB /* NoWiredTipsAlert.xib in Resources */ = {isa = PBXBuildFile; fileRef = BC8A458E283DC33400094BBB /* NoWiredTipsAlert.xib */; };
+		BC8A45B7283DC33500094BBB /* NoWiredTipsAlert.m in Sources */ = {isa = PBXBuildFile; fileRef = BC8A458F283DC33400094BBB /* NoWiredTipsAlert.m */; };
+		BC8A45BB283DC3F500094BBB /* KSVideoRecordManager.m in Sources */ = {isa = PBXBuildFile; fileRef = BC8A45BA283DC3F500094BBB /* KSVideoRecordManager.m */; };
+		BC8A45BE283DCADE00094BBB /* NSObject+KeyWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = BC8A45BD283DCADE00094BBB /* NSObject+KeyWindow.m */; };
+		BC8A45C1283DDD7100094BBB /* synthgms.sf2 in Resources */ = {isa = PBXBuildFile; fileRef = BC8A45C0283DDD7100094BBB /* synthgms.sf2 */; };
+		BC8A45C3283DDE5C00094BBB /* CoreMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BC8A45C2283DDE5C00094BBB /* CoreMIDI.framework */; };
+		BC8A45C5283DDE6D00094BBB /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BC8A45C4283DDE6D00094BBB /* AudioToolbox.framework */; };
+		BC8A45C7283DDE8E00094BBB /* VideoToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BC8A45C6283DDE8E00094BBB /* VideoToolbox.framework */; };
+		BC8A45C9283DDE9A00094BBB /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BC8A45C8283DDE9900094BBB /* CoreMedia.framework */; };
+		BC8A45CB283DDEA100094BBB /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BC8A45CA283DDEA100094BBB /* AVFoundation.framework */; };
 		BC8C2C572823F57100FBA5D5 /* AddressDetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = BC8C2C452823F57100FBA5D5 /* AddressDetailViewController.m */; };
 		BC8C2C582823F57100FBA5D5 /* AddressListViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = BC8C2C462823F57100FBA5D5 /* AddressListViewController.m */; };
 		BC8C2C592823F57100FBA5D5 /* AddressListModel.m in Sources */ = {isa = PBXBuildFile; fileRef = BC8C2C482823F57100FBA5D5 /* AddressListModel.m */; };
@@ -1430,6 +1476,8 @@
 		BC50171327FC0D8300F8BCBC /* SubjectChooseBodyView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SubjectChooseBodyView.h; sourceTree = "<group>"; };
 		BC50171427FC0D8300F8BCBC /* SubjectChooseBodyView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SubjectChooseBodyView.m; sourceTree = "<group>"; };
 		BC50171627FC0D8D00F8BCBC /* SubjectChooseBodyView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SubjectChooseBodyView.xib; sourceTree = "<group>"; };
+		BC5082B2283345A10031DD0A /* KSChatListCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KSChatListCell.h; sourceTree = "<group>"; };
+		BC5082B3283345A10031DD0A /* KSChatListCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KSChatListCell.m; sourceTree = "<group>"; };
 		BC76630C2827E48800C91A1D /* NotiferMessageModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NotiferMessageModel.m; sourceTree = "<group>"; };
 		BC76630D2827E48800C91A1D /* NotiferMessageModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NotiferMessageModel.h; sourceTree = "<group>"; };
 		BC76630F2827E49800C91A1D /* NotiferHeadView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NotiferHeadView.m; sourceTree = "<group>"; };
@@ -1438,6 +1486,78 @@
 		BC7663122827E49800C91A1D /* NotiferMessageCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NotiferMessageCell.m; sourceTree = "<group>"; };
 		BC7663132827E49900C91A1D /* NotiferMessageCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NotiferMessageCell.xib; sourceTree = "<group>"; };
 		BC7663142827E49900C91A1D /* NotiferHeadView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NotiferHeadView.h; sourceTree = "<group>"; };
+		BC8A4547283DC33400094BBB /* KSCloudViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KSCloudViewController.h; sourceTree = "<group>"; };
+		BC8A4548283DC33400094BBB /* KSCloudViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KSCloudViewController.m; sourceTree = "<group>"; };
+		BC8A454A283DC33400094BBB /* MidiPlayerEngine.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MidiPlayerEngine.m; sourceTree = "<group>"; };
+		BC8A454B283DC33400094BBB /* GCDTimer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDTimer.m; sourceTree = "<group>"; };
+		BC8A454C283DC33400094BBB /* CoreAudioUtils.c */ = {isa = PBXFileReference; explicitFileType = sourcecode.c.objc; fileEncoding = 4; path = CoreAudioUtils.c; sourceTree = "<group>"; };
+		BC8A454D283DC33400094BBB /* CAudioUnit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CAudioUnit.m; sourceTree = "<group>"; };
+		BC8A454E283DC33400094BBB /* MidiPlayerEngine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MidiPlayerEngine.h; sourceTree = "<group>"; };
+		BC8A454F283DC33400094BBB /* CoreAudioUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CoreAudioUtils.h; sourceTree = "<group>"; };
+		BC8A4550283DC33400094BBB /* GCDTimer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDTimer.h; sourceTree = "<group>"; };
+		BC8A4551283DC33400094BBB /* CAudioUnit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CAudioUnit.h; sourceTree = "<group>"; };
+		BC8A4553283DC33400094BBB /* KSXMLInfoParse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KSXMLInfoParse.h; sourceTree = "<group>"; };
+		BC8A4554283DC33400094BBB /* KYSourceParseManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KYSourceParseManager.m; sourceTree = "<group>"; };
+		BC8A4555283DC33400094BBB /* CloudSongMessageModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CloudSongMessageModel.h; sourceTree = "<group>"; };
+		BC8A4556283DC33400094BBB /* KSParseMessageModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KSParseMessageModel.h; sourceTree = "<group>"; };
+		BC8A4557283DC33400094BBB /* TBXML.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TBXML.m; sourceTree = "<group>"; };
+		BC8A4558283DC33400094BBB /* SubjectFinger.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = SubjectFinger.plist; sourceTree = "<group>"; };
+		BC8A4559283DC33400094BBB /* CloudSongMessageModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CloudSongMessageModel.m; sourceTree = "<group>"; };
+		BC8A455A283DC33400094BBB /* KSXMLInfoParse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KSXMLInfoParse.m; sourceTree = "<group>"; };
+		BC8A455B283DC33400094BBB /* KYSourceParseManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KYSourceParseManager.h; sourceTree = "<group>"; };
+		BC8A455C283DC33400094BBB /* KSParseMessageModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KSParseMessageModel.m; sourceTree = "<group>"; };
+		BC8A455D283DC33400094BBB /* TBXML.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TBXML.h; sourceTree = "<group>"; };
+		BC8A455E283DC33400094BBB /* FingerList.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = FingerList.plist; sourceTree = "<group>"; };
+		BC8A4561283DC33400094BBB /* SettingPageView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SettingPageView.h; sourceTree = "<group>"; };
+		BC8A4562283DC33400094BBB /* JudgePageView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JudgePageView.h; sourceTree = "<group>"; };
+		BC8A4563283DC33400094BBB /* SettingPageView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SettingPageView.xib; sourceTree = "<group>"; };
+		BC8A4564283DC33400094BBB /* JudgePageView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = JudgePageView.xib; sourceTree = "<group>"; };
+		BC8A4565283DC33400094BBB /* KSCloudSettingView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KSCloudSettingView.m; sourceTree = "<group>"; };
+		BC8A4566283DC33400094BBB /* SettingPageView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SettingPageView.m; sourceTree = "<group>"; };
+		BC8A4567283DC33400094BBB /* JudgePageView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JudgePageView.m; sourceTree = "<group>"; };
+		BC8A4568283DC33400094BBB /* KSCloudSettingView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = KSCloudSettingView.xib; sourceTree = "<group>"; };
+		BC8A4569283DC33400094BBB /* KSCloudSettingView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KSCloudSettingView.h; sourceTree = "<group>"; };
+		BC8A456B283DC33400094BBB /* ScoreAnimationView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ScoreAnimationView.h; sourceTree = "<group>"; };
+		BC8A456C283DC33400094BBB /* StaffImageDisplayView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StaffImageDisplayView.h; sourceTree = "<group>"; };
+		BC8A456D283DC33400094BBB /* CloudControlButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CloudControlButton.h; sourceTree = "<group>"; };
+		BC8A456E283DC33400094BBB /* StaffImageDisplayView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StaffImageDisplayView.m; sourceTree = "<group>"; };
+		BC8A456F283DC33400094BBB /* ScoreAnimationView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ScoreAnimationView.m; sourceTree = "<group>"; };
+		BC8A4570283DC33400094BBB /* CloudControlButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CloudControlButton.m; sourceTree = "<group>"; };
+		BC8A4572283DC33400094BBB /* KSCloudBeatView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = KSCloudBeatView.xib; sourceTree = "<group>"; };
+		BC8A4573283DC33400094BBB /* KSCloudBeatView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KSCloudBeatView.h; sourceTree = "<group>"; };
+		BC8A4574283DC33400094BBB /* KSCloudBeatView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KSCloudBeatView.m; sourceTree = "<group>"; };
+		BC8A4576283DC33400094BBB /* TrackChooseView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TrackChooseView.m; sourceTree = "<group>"; };
+		BC8A4577283DC33400094BBB /* TrackChooseView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = TrackChooseView.xib; sourceTree = "<group>"; };
+		BC8A4578283DC33400094BBB /* TrackChooseView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TrackChooseView.h; sourceTree = "<group>"; };
+		BC8A457A283DC33400094BBB /* KSTrackingSlider.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KSTrackingSlider.h; sourceTree = "<group>"; };
+		BC8A457B283DC33400094BBB /* KSSliderView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KSSliderView.m; sourceTree = "<group>"; };
+		BC8A457C283DC33400094BBB /* KSTrackingSlider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KSTrackingSlider.m; sourceTree = "<group>"; };
+		BC8A457D283DC33400094BBB /* KSSliderView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KSSliderView.h; sourceTree = "<group>"; };
+		BC8A457F283DC33400094BBB /* SoundCheckView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SoundCheckView.m; sourceTree = "<group>"; };
+		BC8A4580283DC33400094BBB /* SoundCheckView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SoundCheckView.xib; sourceTree = "<group>"; };
+		BC8A4581283DC33400094BBB /* SoundCheckView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SoundCheckView.h; sourceTree = "<group>"; };
+		BC8A4583283DC33400094BBB /* CloudFeedbackView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CloudFeedbackView.h; sourceTree = "<group>"; };
+		BC8A4584283DC33400094BBB /* CloudHelpView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CloudHelpView.m; sourceTree = "<group>"; };
+		BC8A4585283DC33400094BBB /* CloudHelpView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = CloudHelpView.xib; sourceTree = "<group>"; };
+		BC8A4586283DC33400094BBB /* CloudFeedbackView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CloudFeedbackView.m; sourceTree = "<group>"; };
+		BC8A4587283DC33400094BBB /* CloudHelpView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CloudHelpView.h; sourceTree = "<group>"; };
+		BC8A4588283DC33400094BBB /* CloudFeedbackView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = CloudFeedbackView.xib; sourceTree = "<group>"; };
+		BC8A458A283DC33400094BBB /* EvaluateResultAlert.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = EvaluateResultAlert.xib; sourceTree = "<group>"; };
+		BC8A458B283DC33400094BBB /* EvaluateResultAlert.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EvaluateResultAlert.h; sourceTree = "<group>"; };
+		BC8A458C283DC33400094BBB /* NoWiredTipsAlert.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NoWiredTipsAlert.h; sourceTree = "<group>"; };
+		BC8A458D283DC33400094BBB /* EvaluateResultAlert.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EvaluateResultAlert.m; sourceTree = "<group>"; };
+		BC8A458E283DC33400094BBB /* NoWiredTipsAlert.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NoWiredTipsAlert.xib; sourceTree = "<group>"; };
+		BC8A458F283DC33400094BBB /* NoWiredTipsAlert.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NoWiredTipsAlert.m; sourceTree = "<group>"; };
+		BC8A45B9283DC3F400094BBB /* KSVideoRecordManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KSVideoRecordManager.h; sourceTree = "<group>"; };
+		BC8A45BA283DC3F500094BBB /* KSVideoRecordManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KSVideoRecordManager.m; sourceTree = "<group>"; };
+		BC8A45BC283DCADE00094BBB /* NSObject+KeyWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+KeyWindow.h"; sourceTree = "<group>"; };
+		BC8A45BD283DCADE00094BBB /* NSObject+KeyWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+KeyWindow.m"; sourceTree = "<group>"; };
+		BC8A45C0283DDD7100094BBB /* synthgms.sf2 */ = {isa = PBXFileReference; lastKnownFileType = file; path = synthgms.sf2; sourceTree = "<group>"; };
+		BC8A45C2283DDE5C00094BBB /* CoreMIDI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMIDI.framework; path = System/Library/Frameworks/CoreMIDI.framework; sourceTree = SDKROOT; };
+		BC8A45C4283DDE6D00094BBB /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; };
+		BC8A45C6283DDE8E00094BBB /* VideoToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = VideoToolbox.framework; path = System/Library/Frameworks/VideoToolbox.framework; sourceTree = SDKROOT; };
+		BC8A45C8283DDE9900094BBB /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; };
+		BC8A45CA283DDEA100094BBB /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
 		BC8C2C432823F57100FBA5D5 /* AddressDetailViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AddressDetailViewController.h; sourceTree = "<group>"; };
 		BC8C2C442823F57100FBA5D5 /* AddressListViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AddressListViewController.h; sourceTree = "<group>"; };
 		BC8C2C452823F57100FBA5D5 /* AddressDetailViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AddressDetailViewController.m; sourceTree = "<group>"; };
@@ -1743,6 +1863,11 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				BC8A45CB283DDEA100094BBB /* AVFoundation.framework in Frameworks */,
+				BC8A45C9283DDE9A00094BBB /* CoreMedia.framework in Frameworks */,
+				BC8A45C7283DDE8E00094BBB /* VideoToolbox.framework in Frameworks */,
+				BC8A45C5283DDE6D00094BBB /* AudioToolbox.framework in Frameworks */,
+				BC8A45C3283DDE5C00094BBB /* CoreMIDI.framework in Frameworks */,
 				82EE25CEB2BB5A0E1BB8D54B /* Pods_KulexiuForStudent.framework in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
@@ -1977,6 +2102,7 @@
 		275E8AA727E18F8800DD3F6E /* KulexiuForStudent */ = {
 			isa = PBXGroup;
 			children = (
+				BC8A45BF283DDD7100094BBB /* SoundFontFile */,
 				BC48C3AB2829184C00EE65C5 /* KulexiuForStudent.entitlements */,
 				275FA1F027E7356A00CFEA2E /* Module */,
 				2779336127E3249C0010E277 /* Common */,
@@ -2013,6 +2139,7 @@
 		275FA1F027E7356A00CFEA2E /* Module */ = {
 			isa = PBXGroup;
 			children = (
+				BC8A4545283DC33400094BBB /* CloudEngine */,
 				BCB6357327F6D2AB00ACFDCF /* Classroom */,
 				BCB6348727F6D2A200ACFDCF /* SealClass */,
 				BCB6340A27F6D29500ACFDCF /* Live */,
@@ -2234,6 +2361,8 @@
 				2723B5AC27F157A600E0B90B /* KSChatListSearchView.h */,
 				2723B5A927F157A400E0B90B /* KSChatListSearchView.m */,
 				2723B5B427F157AE00E0B90B /* KSChatListSearchView.xib */,
+				BC5082B2283345A10031DD0A /* KSChatListCell.h */,
+				BC5082B3283345A10031DD0A /* KSChatListCell.m */,
 			);
 			path = View;
 			sourceTree = "<group>";
@@ -2407,6 +2536,8 @@
 		2779336227E3249C0010E277 /* Tools */ = {
 			isa = PBXGroup;
 			children = (
+				BC8A45BC283DCADE00094BBB /* NSObject+KeyWindow.h */,
+				BC8A45BD283DCADE00094BBB /* NSObject+KeyWindow.m */,
 				BC119296280FBCB300A716F7 /* UIView+ExtensionForDotLine.h */,
 				BC119297280FBCB400A716F7 /* UIView+ExtensionForDotLine.m */,
 				BC11928E280FB46100A716F7 /* KSVideoHelper.h */,
@@ -3121,6 +3252,8 @@
 				275FA1AF27E7351400CFEA2E /* KSNetworkingManager.m */,
 				BC48C3A72828FC7D00EE65C5 /* KSUploadManager.h */,
 				BC48C3A82828FC7D00EE65C5 /* KSUploadManager.m */,
+				BC8A45B9283DC3F400094BBB /* KSVideoRecordManager.h */,
+				BC8A45BA283DC3F500094BBB /* KSVideoRecordManager.m */,
 			);
 			path = Base;
 			sourceTree = "<group>";
@@ -3243,6 +3376,11 @@
 		9BF683910A95A690331EF86A /* Frameworks */ = {
 			isa = PBXGroup;
 			children = (
+				BC8A45CA283DDEA100094BBB /* AVFoundation.framework */,
+				BC8A45C8283DDE9900094BBB /* CoreMedia.framework */,
+				BC8A45C6283DDE8E00094BBB /* VideoToolbox.framework */,
+				BC8A45C4283DDE6D00094BBB /* AudioToolbox.framework */,
+				BC8A45C2283DDE5C00094BBB /* CoreMIDI.framework */,
 				14CEAEC95E5CF916A3D3F602 /* Pods_KulexiuForStudent.framework */,
 				C9C170A749B6C49F17AC3246 /* Pods_KulexiuForStudent_KulexiuForStudentUITests.framework */,
 				DD4D637EF600D0BAE869423D /* Pods_KulexiuForStudentTests.framework */,
@@ -3483,6 +3621,187 @@
 			path = View;
 			sourceTree = "<group>";
 		};
+		BC8A4545283DC33400094BBB /* CloudEngine */ = {
+			isa = PBXGroup;
+			children = (
+				BC8A4546283DC33400094BBB /* Controller */,
+				BC8A4549283DC33400094BBB /* MidiPlayer */,
+				BC8A4552283DC33400094BBB /* Model */,
+				BC8A455F283DC33400094BBB /* View */,
+				BC8A4590283DC33400094BBB /* BASSMidiPlayer */,
+			);
+			path = CloudEngine;
+			sourceTree = "<group>";
+		};
+		BC8A4546283DC33400094BBB /* Controller */ = {
+			isa = PBXGroup;
+			children = (
+				BC8A4547283DC33400094BBB /* KSCloudViewController.h */,
+				BC8A4548283DC33400094BBB /* KSCloudViewController.m */,
+			);
+			path = Controller;
+			sourceTree = "<group>";
+		};
+		BC8A4549283DC33400094BBB /* MidiPlayer */ = {
+			isa = PBXGroup;
+			children = (
+				BC8A4551283DC33400094BBB /* CAudioUnit.h */,
+				BC8A454D283DC33400094BBB /* CAudioUnit.m */,
+				BC8A454C283DC33400094BBB /* CoreAudioUtils.c */,
+				BC8A454F283DC33400094BBB /* CoreAudioUtils.h */,
+				BC8A4550283DC33400094BBB /* GCDTimer.h */,
+				BC8A454B283DC33400094BBB /* GCDTimer.m */,
+				BC8A454E283DC33400094BBB /* MidiPlayerEngine.h */,
+				BC8A454A283DC33400094BBB /* MidiPlayerEngine.m */,
+			);
+			path = MidiPlayer;
+			sourceTree = "<group>";
+		};
+		BC8A4552283DC33400094BBB /* Model */ = {
+			isa = PBXGroup;
+			children = (
+				BC8A4553283DC33400094BBB /* KSXMLInfoParse.h */,
+				BC8A4554283DC33400094BBB /* KYSourceParseManager.m */,
+				BC8A4555283DC33400094BBB /* CloudSongMessageModel.h */,
+				BC8A4556283DC33400094BBB /* KSParseMessageModel.h */,
+				BC8A4557283DC33400094BBB /* TBXML.m */,
+				BC8A4558283DC33400094BBB /* SubjectFinger.plist */,
+				BC8A4559283DC33400094BBB /* CloudSongMessageModel.m */,
+				BC8A455A283DC33400094BBB /* KSXMLInfoParse.m */,
+				BC8A455B283DC33400094BBB /* KYSourceParseManager.h */,
+				BC8A455C283DC33400094BBB /* KSParseMessageModel.m */,
+				BC8A455D283DC33400094BBB /* TBXML.h */,
+				BC8A455E283DC33400094BBB /* FingerList.plist */,
+			);
+			path = Model;
+			sourceTree = "<group>";
+		};
+		BC8A455F283DC33400094BBB /* View */ = {
+			isa = PBXGroup;
+			children = (
+				BC8A4560283DC33400094BBB /* SettingView */,
+				BC8A456A283DC33400094BBB /* StaffView */,
+				BC8A4571283DC33400094BBB /* beat */,
+				BC8A4575283DC33400094BBB /* Channel */,
+				BC8A4579283DC33400094BBB /* SpeedSlider */,
+				BC8A457E283DC33400094BBB /* SoundCheckView */,
+				BC8A4582283DC33400094BBB /* Help */,
+				BC8A4589283DC33400094BBB /* TipsAlert */,
+			);
+			path = View;
+			sourceTree = "<group>";
+		};
+		BC8A4560283DC33400094BBB /* SettingView */ = {
+			isa = PBXGroup;
+			children = (
+				BC8A4561283DC33400094BBB /* SettingPageView.h */,
+				BC8A4562283DC33400094BBB /* JudgePageView.h */,
+				BC8A4563283DC33400094BBB /* SettingPageView.xib */,
+				BC8A4564283DC33400094BBB /* JudgePageView.xib */,
+				BC8A4565283DC33400094BBB /* KSCloudSettingView.m */,
+				BC8A4566283DC33400094BBB /* SettingPageView.m */,
+				BC8A4567283DC33400094BBB /* JudgePageView.m */,
+				BC8A4568283DC33400094BBB /* KSCloudSettingView.xib */,
+				BC8A4569283DC33400094BBB /* KSCloudSettingView.h */,
+			);
+			path = SettingView;
+			sourceTree = "<group>";
+		};
+		BC8A456A283DC33400094BBB /* StaffView */ = {
+			isa = PBXGroup;
+			children = (
+				BC8A456B283DC33400094BBB /* ScoreAnimationView.h */,
+				BC8A456C283DC33400094BBB /* StaffImageDisplayView.h */,
+				BC8A456D283DC33400094BBB /* CloudControlButton.h */,
+				BC8A456E283DC33400094BBB /* StaffImageDisplayView.m */,
+				BC8A456F283DC33400094BBB /* ScoreAnimationView.m */,
+				BC8A4570283DC33400094BBB /* CloudControlButton.m */,
+			);
+			path = StaffView;
+			sourceTree = "<group>";
+		};
+		BC8A4571283DC33400094BBB /* beat */ = {
+			isa = PBXGroup;
+			children = (
+				BC8A4572283DC33400094BBB /* KSCloudBeatView.xib */,
+				BC8A4573283DC33400094BBB /* KSCloudBeatView.h */,
+				BC8A4574283DC33400094BBB /* KSCloudBeatView.m */,
+			);
+			path = beat;
+			sourceTree = "<group>";
+		};
+		BC8A4575283DC33400094BBB /* Channel */ = {
+			isa = PBXGroup;
+			children = (
+				BC8A4576283DC33400094BBB /* TrackChooseView.m */,
+				BC8A4577283DC33400094BBB /* TrackChooseView.xib */,
+				BC8A4578283DC33400094BBB /* TrackChooseView.h */,
+			);
+			path = Channel;
+			sourceTree = "<group>";
+		};
+		BC8A4579283DC33400094BBB /* SpeedSlider */ = {
+			isa = PBXGroup;
+			children = (
+				BC8A457A283DC33400094BBB /* KSTrackingSlider.h */,
+				BC8A457B283DC33400094BBB /* KSSliderView.m */,
+				BC8A457C283DC33400094BBB /* KSTrackingSlider.m */,
+				BC8A457D283DC33400094BBB /* KSSliderView.h */,
+			);
+			path = SpeedSlider;
+			sourceTree = "<group>";
+		};
+		BC8A457E283DC33400094BBB /* SoundCheckView */ = {
+			isa = PBXGroup;
+			children = (
+				BC8A457F283DC33400094BBB /* SoundCheckView.m */,
+				BC8A4580283DC33400094BBB /* SoundCheckView.xib */,
+				BC8A4581283DC33400094BBB /* SoundCheckView.h */,
+			);
+			path = SoundCheckView;
+			sourceTree = "<group>";
+		};
+		BC8A4582283DC33400094BBB /* Help */ = {
+			isa = PBXGroup;
+			children = (
+				BC8A4583283DC33400094BBB /* CloudFeedbackView.h */,
+				BC8A4584283DC33400094BBB /* CloudHelpView.m */,
+				BC8A4585283DC33400094BBB /* CloudHelpView.xib */,
+				BC8A4586283DC33400094BBB /* CloudFeedbackView.m */,
+				BC8A4587283DC33400094BBB /* CloudHelpView.h */,
+				BC8A4588283DC33400094BBB /* CloudFeedbackView.xib */,
+			);
+			path = Help;
+			sourceTree = "<group>";
+		};
+		BC8A4589283DC33400094BBB /* TipsAlert */ = {
+			isa = PBXGroup;
+			children = (
+				BC8A458A283DC33400094BBB /* EvaluateResultAlert.xib */,
+				BC8A458B283DC33400094BBB /* EvaluateResultAlert.h */,
+				BC8A458C283DC33400094BBB /* NoWiredTipsAlert.h */,
+				BC8A458D283DC33400094BBB /* EvaluateResultAlert.m */,
+				BC8A458E283DC33400094BBB /* NoWiredTipsAlert.xib */,
+				BC8A458F283DC33400094BBB /* NoWiredTipsAlert.m */,
+			);
+			path = TipsAlert;
+			sourceTree = "<group>";
+		};
+		BC8A4590283DC33400094BBB /* BASSMidiPlayer */ = {
+			isa = PBXGroup;
+			children = (
+			);
+			path = BASSMidiPlayer;
+			sourceTree = "<group>";
+		};
+		BC8A45BF283DDD7100094BBB /* SoundFontFile */ = {
+			isa = PBXGroup;
+			children = (
+				BC8A45C0283DDD7100094BBB /* synthgms.sf2 */,
+			);
+			path = SoundFontFile;
+			sourceTree = "<group>";
+		};
 		BC8C2C412823F57100FBA5D5 /* AddressList */ = {
 			isa = PBXGroup;
 			children = (
@@ -4275,11 +4594,13 @@
 				275E8AB527E18F8B00DD3F6E /* Assets.xcassets in Resources */,
 				BC119275280FB01100A716F7 /* AccompanyHomeworkCell.xib in Resources */,
 				BC119280280FB10900A716F7 /* AccompanyRemarkCell.xib in Resources */,
+				BC8A45B6283DC33400094BBB /* NoWiredTipsAlert.xib in Resources */,
 				2723B63527F157D500E0B90B /* GroupApplyChooseCell.xib in Resources */,
 				2723B5C227F157B100E0B90B /* GroupListViewCell.xib in Resources */,
 				BCBFDF432811573D0052AFE5 /* HomeButtonView.xib in Resources */,
 				BC11925A280FA85300A716F7 /* HomeworkSortView.xib in Resources */,
 				2723B62527F157D500E0B90B /* GroupNoticeCell.xib in Resources */,
+				BC8A459E283DC33400094BBB /* FingerList.plist in Resources */,
 				275FA23D27E7356B00CFEA2E /* PasswordBodyView.xib in Resources */,
 				2723B66227F15CFC00E0B90B /* FeedbackBodyView.xib in Resources */,
 				2723B66527F15CFC00E0B90B /* ModifyBodyView.xib in Resources */,
@@ -4287,11 +4608,13 @@
 				BCB635AE27F6E06500ACFDCF /* LiveRoomHeadView.xib in Resources */,
 				275FA1AD27E734C600CFEA2E /* KSImageAlert.xib in Resources */,
 				BCBFDF5228115DA40052AFE5 /* HomeIntroduceView.xib in Resources */,
+				BC8A45AF283DC33400094BBB /* SoundCheckView.xib in Resources */,
 				BC119270280FAF7D00A716F7 /* AccompanyCourseInfoCell.xib in Resources */,
 				BC8C2C5B2823F57100FBA5D5 /* AddressBottomView.xib in Resources */,
 				BC40BA202812552300DEC0D1 /* KSHomeButton.xib in Resources */,
 				BC7663162827E49900C91A1D /* NotiferHeadView.xib in Resources */,
 				2723B5C427F157B100E0B90B /* KSChatListSearchView.xib in Resources */,
+				BC8A459A283DC33400094BBB /* SubjectFinger.plist in Resources */,
 				2779359B27E324A80010E277 /* TZImagePickerController.bundle in Resources */,
 				277935C327E324A90010E277 /* SDQWMaskCustomView.xib in Resources */,
 				BC40B9FA2811768400DEC0D1 /* HotInformationHeadView.xib in Resources */,
@@ -4309,6 +4632,7 @@
 				BC11929D280FD2EF00A716F7 /* HomeworkBottomView.xib in Resources */,
 				BC7663182827E49900C91A1D /* NotiferMessageCell.xib in Resources */,
 				BCB6348127F6D29600ACFDCF /* LiveSeatApplyCell.xib in Resources */,
+				BC8A45A4283DC33400094BBB /* KSCloudSettingView.xib in Resources */,
 				2723B62D27F157D500E0B90B /* GroupApplyMemberCell.xib in Resources */,
 				BCB6347427F6D29600ACFDCF /* BaseEmoji.plist in Resources */,
 				BC40BA252812560100DEC0D1 /* HomeCourseTipsView.xib in Resources */,
@@ -4320,6 +4644,7 @@
 				BC119239280ED98E00A716F7 /* AccompanyCourseCell.xib in Resources */,
 				BC27A076280FF61300F91E27 /* AccompanyDetailBottomView.xib in Resources */,
 				BCBFDF4D28115C7A0052AFE5 /* HomeHotCourseView.xib in Resources */,
+				BC8A45B4283DC33400094BBB /* EvaluateResultAlert.xib in Resources */,
 				BCBFDF3928110C6F0052AFE5 /* HomeNavView.xib in Resources */,
 				27F9033C27E87FE100C08A19 /* MineBodyView.xib in Resources */,
 				27F9032B27E87C2E00C08A19 /* NetworkBodyView.xib in Resources */,
@@ -4329,22 +4654,29 @@
 				275FA23827E7356B00CFEA2E /* FirstSettingBodyView.xib in Resources */,
 				BC27A070280FF56C00F91E27 /* AccompanyStudentEvaCell.xib in Resources */,
 				2723B63927F157D500E0B90B /* GroupMemberListCell.xib in Resources */,
+				BC8A45A0283DC33400094BBB /* JudgePageView.xib in Resources */,
 				BC0212F827FC4A080040569F /* SubjectImageCell.xib in Resources */,
 				BC11921B280ED6A900A716F7 /* NewClassPopCell.xib in Resources */,
 				2723B66E27F15CFC00E0B90B /* PhoneCheckBodyView.xib in Resources */,
 				BC119217280ED6A900A716F7 /* MyLessonSearchView.xib in Resources */,
 				BCBFDF3E2811564C0052AFE5 /* HomeBannerView.xib in Resources */,
+				BC8A459F283DC33400094BBB /* SettingPageView.xib in Resources */,
 				BC8C2C5A2823F57100FBA5D5 /* areainfo.json in Resources */,
 				2723B5C527F157B100E0B90B /* ContractListCell.xib in Resources */,
 				BCBFDF48281159A40052AFE5 /* HomeHotAlbumView.xib in Resources */,
 				27F9033727E87C8B00C08A19 /* MineNavView.xib in Resources */,
+				BC8A45AB283DC33400094BBB /* TrackChooseView.xib in Resources */,
 				BC119215280ED6A900A716F7 /* MyLiveCourseCell.xib in Resources */,
 				BC50171727FC0D8E00F8BCBC /* SubjectChooseBodyView.xib in Resources */,
 				BCB6359D27F6D2AB00ACFDCF /* tock.wav in Resources */,
+				BC8A45C1283DDD7100094BBB /* synthgms.sf2 in Resources */,
 				BC0D1F72281015B000C5D9E5 /* VideoCourseCell.xib in Resources */,
+				BC8A45B1283DC33400094BBB /* CloudHelpView.xib in Resources */,
 				BC11927B280FB07F00A716F7 /* AccompanyArrangeCell.xib in Resources */,
 				BC40B9FF281177BD00DEC0D1 /* HomeInformationCell.xib in Resources */,
+				BC8A45B3283DC33400094BBB /* CloudFeedbackView.xib in Resources */,
 				275E8AB327E18F8800DD3F6E /* Main.storyboard in Resources */,
+				BC8A45A8283DC33400094BBB /* KSCloudBeatView.xib in Resources */,
 				277935B127E324A90010E277 /* mss_browseLoading@2x.png in Resources */,
 				BC11922C280ED8E800A716F7 /* LTSCalendarBottomView.xib in Resources */,
 			);
@@ -4538,6 +4870,7 @@
 				275FA1DC27E7351900CFEA2E /* KSAQRecordManager.m in Sources */,
 				BC40B9F82811767A00DEC0D1 /* HotInformationHeadView.m in Sources */,
 				BCB6356827F6D2A300ACFDCF /* ApplySpeechResultMessage.m in Sources */,
+				BC8A45AD283DC33400094BBB /* KSTrackingSlider.m in Sources */,
 				BC7663152827E49900C91A1D /* NotiferHeadView.m in Sources */,
 				2779351427E324A50010E277 /* NSMutableDictionary+KSSafe.m in Sources */,
 				2779356F27E324A70010E277 /* SkipTextField.m in Sources */,
@@ -4549,6 +4882,7 @@
 				2779351227E324A50010E277 /* KSNetworkAccessibleManager.m in Sources */,
 				BCB6346827F6D29600ACFDCF /* KSLiveChatroomClose.m in Sources */,
 				2779357227E324A70010E277 /* SearchView.m in Sources */,
+				BC8A459C283DC33400094BBB /* KSXMLInfoParse.m in Sources */,
 				2723B64927F15BDC00E0B90B /* KSJXBodyView.m in Sources */,
 				BCB6354E27F6D2A300ACFDCF /* KSWhiteboardView.m in Sources */,
 				2779356527E324A70010E277 /* KSMediaManager.m in Sources */,
@@ -4564,14 +4898,19 @@
 				BC50171227FC0D5600F8BCBC /* SubjectChooseViewController.m in Sources */,
 				2779359727E324A80010E277 /* TZVideoPlayerController.m in Sources */,
 				275FA22E27E7356B00CFEA2E /* MineViewController.m in Sources */,
+				BC8A4597283DC33400094BBB /* CAudioUnit.m in Sources */,
 				2779354627E324A60010E277 /* UIView+Animation.m in Sources */,
 				2723B68327F15D3D00E0B90B /* ModifyViewController.m in Sources */,
 				2779356327E324A70010E277 /* HomeButton.m in Sources */,
 				2779358227E324A80010E277 /* NSDate+KSBaseDatePicker.m in Sources */,
 				BCB6359E27F6D2AB00ACFDCF /* ClassroomMainContainer.m in Sources */,
 				277935CD27E324A90010E277 /* ALCalendarCell.m in Sources */,
+				BC8A45BE283DCADE00094BBB /* NSObject+KeyWindow.m in Sources */,
 				275FA1DA27E7351900CFEA2E /* KSNetworkingManager.m in Sources */,
+				BC8A45B0283DC33400094BBB /* CloudHelpView.m in Sources */,
 				2779358A27E324A80010E277 /* ArchiveTools.m in Sources */,
+				BC8A45AA283DC33400094BBB /* TrackChooseView.m in Sources */,
+				BC8A45B2283DC33400094BBB /* CloudFeedbackView.m in Sources */,
 				BCB6355A27F6D2A300ACFDCF /* KSRemoteUserManager.m in Sources */,
 				BCB6347127F6D29600ACFDCF /* KSChatEmojiBoardView.m in Sources */,
 				2779354527E324A60010E277 /* UIImage+UIImageScale.m in Sources */,
@@ -4592,6 +4931,7 @@
 				2779358D27E324A80010E277 /* DZNSegmentedControl.m in Sources */,
 				2779352027E324A60010E277 /* CALayer+Color.m in Sources */,
 				2723B62C27F157D500E0B90B /* ApplyBottomView.m in Sources */,
+				BC8A4595283DC33400094BBB /* GCDTimer.m in Sources */,
 				BCB6356127F6D2A300ACFDCF /* SongDownloadCallbackMessage.m in Sources */,
 				277935BA27E324A90010E277 /* FSCalendarWeekdayView.m in Sources */,
 				BCB6354427F6D2A300ACFDCF /* RecentSharedView.m in Sources */,
@@ -4601,6 +4941,7 @@
 				BCB6355F27F6D2A300ACFDCF /* TicketExpiredMessage.m in Sources */,
 				277935A827E324A80010E277 /* MSSBrowseModel.m in Sources */,
 				BCB6355327F6D2A300ACFDCF /* InputTextField.m in Sources */,
+				BC8A4596283DC33400094BBB /* CoreAudioUtils.c in Sources */,
 				BC28582B2809036D0024697C /* StudentInfoModel.m in Sources */,
 				277935B927E324A90010E277 /* FSCalendarCollectionViewLayout.m in Sources */,
 				2779359427E324A80010E277 /* TZAssetCell.m in Sources */,
@@ -4610,6 +4951,7 @@
 				BCB6356427F6D2A300ACFDCF /* InviteUpgradeMessage.m in Sources */,
 				2779355327E324A70010E277 /* VoNetworking+RequestManager.m in Sources */,
 				BCB6347327F6D29600ACFDCF /* KSChatEmojiCollectionCell.m in Sources */,
+				BC8A4593283DC33400094BBB /* KSCloudViewController.m in Sources */,
 				2723B62327F157D500E0B90B /* LFPopupMenuDefaultConfig.m in Sources */,
 				2779351627E324A60010E277 /* NSMutableArray+KSSafe.m in Sources */,
 				BC119247280EDA5800A716F7 /* kSJXCollectionView.m in Sources */,
@@ -4663,18 +5005,22 @@
 				BCB6354527F6D2A300ACFDCF /* RecentSharedVideoCell.m in Sources */,
 				BCB6347027F6D29600ACFDCF /* LiveSeatMember.m in Sources */,
 				2779351C27E324A60010E277 /* UIDevice+zhDeviceType.m in Sources */,
+				BC8A45AC283DC33400094BBB /* KSSliderView.m in Sources */,
 				2723B62927F157D500E0B90B /* KSChatComplainController.m in Sources */,
 				2779351527E324A60010E277 /* NSArray+KSSafe.m in Sources */,
 				2723B61A27F157D500E0B90B /* KSSearchResultViewCell.m in Sources */,
 				BC48C3A92828FC7D00EE65C5 /* KSUploadManager.m in Sources */,
+				BC8A4599283DC33400094BBB /* TBXML.m in Sources */,
 				BC11927A280FB07F00A716F7 /* AccompanyArrangeCell.m in Sources */,
 				277935B827E324A90010E277 /* FSCalendar.m in Sources */,
 				BCB6356C27F6D2A300ACFDCF /* Whiteboard.m in Sources */,
 				275FA23527E7356B00CFEA2E /* UserInfoManager.m in Sources */,
 				2779353327E324A60010E277 /* UIScreen+Extend.m in Sources */,
+				BC8A45AE283DC33400094BBB /* SoundCheckView.m in Sources */,
 				2723B67E27F15D3D00E0B90B /* ModifyPhoneCheckController.m in Sources */,
 				2779353A27E324A60010E277 /* UILabel+QWTopLeftLabel.m in Sources */,
 				BC119213280ED6A900A716F7 /* MyLessonBodyView.m in Sources */,
+				BC8A45A3283DC33400094BBB /* JudgePageView.m in Sources */,
 				BCB6354227F6D2A300ACFDCF /* VideoListView.m in Sources */,
 				2779359A27E324A80010E277 /* TZPhotoPreviewController.m in Sources */,
 				2779355E27E324A70010E277 /* KSAudioRecordFileManager.m in Sources */,
@@ -4683,6 +5029,8 @@
 				BCB6346027F6D29600ACFDCF /* KSLiveChatroomKickOut.m in Sources */,
 				2779354127E324A60010E277 /* UIView+ShowProgress.m in Sources */,
 				BCFE53E72812765600AD6786 /* HomeHotAlbumCell.m in Sources */,
+				BC8A459B283DC33400094BBB /* CloudSongMessageModel.m in Sources */,
+				BC8A45B7283DC33500094BBB /* NoWiredTipsAlert.m in Sources */,
 				BCB6346627F6D29600ACFDCF /* KSLiveChatroomUserQuit.m in Sources */,
 				BCB6356D27F6D2A300ACFDCF /* RoomMember.m in Sources */,
 				277935B427E324A90010E277 /* FSCalendarDelegationProxy.m in Sources */,
@@ -4766,6 +5114,7 @@
 				BC0D1F71281015B000C5D9E5 /* VideoCourseCell.m in Sources */,
 				2779352F27E324A60010E277 /* UIView+Hints.m in Sources */,
 				2779351D27E324A60010E277 /* NSString+zh_SafeAccess.m in Sources */,
+				BC8A45A6283DC33400094BBB /* ScoreAnimationView.m in Sources */,
 				BCB6354727F6D2A300ACFDCF /* PersonListView.m in Sources */,
 				2779351A27E324A60010E277 /* NSObject+AssociatedObject.m in Sources */,
 				BCB635A627F6D90600ACFDCF /* KSLiveEmptyView.m in Sources */,
@@ -4775,11 +5124,13 @@
 				2779358627E324A80010E277 /* LLPhoto.m in Sources */,
 				277935C127E324A90010E277 /* FSCalendarAppearance.m in Sources */,
 				275E8ABB27E18F8B00DD3F6E /* main.m in Sources */,
+				BC8A45BB283DC3F500094BBB /* KSVideoRecordManager.m in Sources */,
 				2779359F27E324A80010E277 /* TZPhotoPreviewCell.m in Sources */,
 				BC11928D280FB44300A716F7 /* HomeworkVideoView.m in Sources */,
 				277935A227E324A80010E277 /* TZLocationManager.m in Sources */,
 				2723B62827F157D500E0B90B /* KSSelectConversationViewController.m in Sources */,
 				275FA1E827E7351900CFEA2E /* CustomNavViewController.m in Sources */,
+				BC8A45B5283DC33400094BBB /* EvaluateResultAlert.m in Sources */,
 				BCB6355B27F6D2A300ACFDCF /* ApplySpeechMessage.m in Sources */,
 				275FA24327E73DF600CFEA2E /* InstrumentDescView.m in Sources */,
 				2723B68027F15D3D00E0B90B /* FeedbackViewController.m in Sources */,
@@ -4843,6 +5194,7 @@
 				275FA1E127E7351900CFEA2E /* KSTabBarViewController.m in Sources */,
 				BC11926B280FAF5900A716F7 /* AccompanyAlertView.m in Sources */,
 				277935BB27E324A90010E277 /* FSCalendarSeparatorDecorationView.m in Sources */,
+				BC8A45A9283DC33400094BBB /* KSCloudBeatView.m in Sources */,
 				275FA1DF27E7351900CFEA2E /* RCConnectionManager.m in Sources */,
 				275FA22B27E7356B00CFEA2E /* HomeViewController.m in Sources */,
 				BCFE53F12812898700AD6786 /* HomeVideoCourseCell.m in Sources */,
@@ -4869,6 +5221,7 @@
 				BC119218280ED6A900A716F7 /* MyLessonSearchView.m in Sources */,
 				BCB6354C27F6D2A300ACFDCF /* VideoMaskView.m in Sources */,
 				2723B66D27F15CFC00E0B90B /* ModifyNameBodyView.m in Sources */,
+				BC8A459D283DC33400094BBB /* KSParseMessageModel.m in Sources */,
 				275FA23327E7356B00CFEA2E /* VefiCodeLoginController.m in Sources */,
 				BCB6359C27F6D2AB00ACFDCF /* ClassVideoListCell.m in Sources */,
 				2723B61B27F157D500E0B90B /* KSSearchRCLabel.m in Sources */,
@@ -4918,15 +5271,18 @@
 				BCB6353227F6D2A300ACFDCF /* InputView.m in Sources */,
 				275FA23927E7356B00CFEA2E /* FirstSettingBodyView.m in Sources */,
 				BC7663172827E49900C91A1D /* NotiferMessageCell.m in Sources */,
+				BC8A45A7283DC33400094BBB /* CloudControlButton.m in Sources */,
 				BCB6346527F6D29600ACFDCF /* KSLiveChatroomLike.m in Sources */,
 				BCB6356927F6D2A300ACFDCF /* TurnPageMessage.m in Sources */,
 				277935CA27E324A90010E277 /* TAAnimatedDotView.m in Sources */,
+				BC8A4598283DC33400094BBB /* KYSourceParseManager.m in Sources */,
 				2779353527E324A60010E277 /* NSObject+ReadDocument.m in Sources */,
 				BC40BA0E28117B3B00DEC0D1 /* TYCyclePagerTransformLayout.m in Sources */,
 				277935C227E324A90010E277 /* QWdynamicModel.m in Sources */,
 				2779357E27E324A80010E277 /* KSInputView.m in Sources */,
 				BCB6354927F6D2A300ACFDCF /* PersonListSectionView.m in Sources */,
 				2723B63427F157D500E0B90B /* GroupApplyMemberCell.m in Sources */,
+				BC5082B4283345A10031DD0A /* KSChatListCell.m in Sources */,
 				BCB6345D27F6D29600ACFDCF /* KSLiveChatroomWelcome.m in Sources */,
 				2779352927E324A60010E277 /* zhPopupController.m in Sources */,
 				2779359527E324A80010E277 /* TZVideoEditedPreviewController.m in Sources */,
@@ -4945,10 +5301,12 @@
 				BCB6355127F6D2A300ACFDCF /* LoginHelper.m in Sources */,
 				2723B5C127F157B100E0B90B /* ContractListCell.m in Sources */,
 				275FA1E527E7351900CFEA2E /* KSAccompanyWebViewController.m in Sources */,
+				BC8A45A5283DC33400094BBB /* StaffImageDisplayView.m in Sources */,
 				BC11923A280ED98E00A716F7 /* AccompanyCourseCell.m in Sources */,
 				BC119264280FA90100A716F7 /* HomeworkListModel.m in Sources */,
 				2779354A27E324A60010E277 /* UIImage+Resize.m in Sources */,
 				2723B5A327F1578300E0B90B /* KSChatListViewController.m in Sources */,
+				BC8A45A1283DC33400094BBB /* KSCloudSettingView.m in Sources */,
 				BC119271280FAF7D00A716F7 /* AccompanyCourseInfoCell.m in Sources */,
 				BC8C2C592823F57100FBA5D5 /* AddressListModel.m in Sources */,
 				2779357527E324A70010E277 /* LifeButton.m in Sources */,
@@ -4958,6 +5316,7 @@
 				275FA22D27E7356B00CFEA2E /* ChatViewController.m in Sources */,
 				BCB6354127F6D2A300ACFDCF /* VideoListCell.m in Sources */,
 				2723B5A427F1578300E0B90B /* KSChatConversationViewController.m in Sources */,
+				BC8A45A2283DC33400094BBB /* SettingPageView.m in Sources */,
 				2723B62627F157D500E0B90B /* GroupApplyViewController.m in Sources */,
 				277935C427E324A90010E277 /* SDCycleScrollView.m in Sources */,
 				BC119293280FBC1100A716F7 /* HomeworkAddView.m in Sources */,
@@ -4993,6 +5352,7 @@
 				2779355527E324A70010E277 /* DiskFreeSpaceManager.m in Sources */,
 				2779352E27E324A60010E277 /* NSDictionary+Extension.m in Sources */,
 				2779355127E324A70010E277 /* VoCacheManager.m in Sources */,
+				BC8A4594283DC33400094BBB /* MidiPlayerEngine.m in Sources */,
 				277935B527E324A90010E277 /* FSCalendarDelegationFactory.m in Sources */,
 				BCB6353927F6D2A300ACFDCF /* MessageCell.m in Sources */,
 				BCB6354027F6D2A300ACFDCF /* ClassroomTitleView.m in Sources */,

二进制
KulexiuForStudent/KulexiuForStudent.xcworkspace/xcuserdata/wangzhi.xcuserdatad/UserInterfaceState.xcuserstate


+ 16 - 16
KulexiuForStudent/KulexiuForStudent.xcworkspace/xcuserdata/wangzhi.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist

@@ -62,8 +62,8 @@
             filePath = "KulexiuForStudent/Module/Home/Controller/HomeViewController.m"
             startingColumnNumber = "9223372036854775807"
             endingColumnNumber = "9223372036854775807"
-            startingLineNumber = "999"
-            endingLineNumber = "999"
+            startingLineNumber = "1003"
+            endingLineNumber = "1003"
             landmarkName = "-homeCourseChooseAction:"
             landmarkType = "7">
          </BreakpointContent>
@@ -94,8 +94,8 @@
             filePath = "KulexiuForStudent/Common/Base/KSBaseWKWebViewController.m"
             startingColumnNumber = "9223372036854775807"
             endingColumnNumber = "9223372036854775807"
-            startingLineNumber = "412"
-            endingLineNumber = "412"
+            startingLineNumber = "414"
+            endingLineNumber = "414"
             landmarkName = "-handleScriptMessageSource:"
             landmarkType = "7">
          </BreakpointContent>
@@ -135,32 +135,32 @@
       <BreakpointProxy
          BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
          <BreakpointContent
-            uuid = "A3C32FE0-AD9A-4379-901B-E9810C9CFB46"
-            shouldBeEnabled = "Yes"
+            uuid = "C4FA01F2-D5AA-4A5F-9771-ACBE64179067"
+            shouldBeEnabled = "No"
             ignoreCount = "0"
             continueAfterRunningActions = "No"
-            filePath = "KulexiuForStudent/Module/Mine/Controller/MineViewController.m"
+            filePath = "KulexiuForStudent/Module/Home/Controller/HomeViewController.m"
             startingColumnNumber = "9223372036854775807"
             endingColumnNumber = "9223372036854775807"
-            startingLineNumber = "107"
-            endingLineNumber = "107"
-            landmarkName = "-refreshView"
+            startingLineNumber = "400"
+            endingLineNumber = "400"
+            landmarkName = "-requestCourseInfo"
             landmarkType = "7">
          </BreakpointContent>
       </BreakpointProxy>
       <BreakpointProxy
          BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
          <BreakpointContent
-            uuid = "C84BD0DC-0BFF-4590-92AE-4B73418B916C"
-            shouldBeEnabled = "Yes"
+            uuid = "3D9A202F-F457-4AD7-B097-BA348C19E5C4"
+            shouldBeEnabled = "No"
             ignoreCount = "0"
             continueAfterRunningActions = "No"
-            filePath = "KulexiuForStudent/Module/Mine/Controller/MineViewController.m"
+            filePath = "KulexiuForStudent/Module/Home/Controller/HomeViewController.m"
             startingColumnNumber = "9223372036854775807"
             endingColumnNumber = "9223372036854775807"
-            startingLineNumber = "91"
-            endingLineNumber = "91"
-            landmarkName = "-requsetUserMessage"
+            startingLineNumber = "1261"
+            endingLineNumber = "1261"
+            landmarkName = "-showNewsWithSource:"
             landmarkType = "7">
          </BreakpointContent>
       </BreakpointProxy>

+ 1055 - 1
KulexiuForStudent/KulexiuForStudent/Common/Base/KSAccompanyWebViewController.m

@@ -6,18 +6,1072 @@
 //
 
 #import "KSAccompanyWebViewController.h"
+#import "RecordCheckManager.h"
+#import "KSAQRecordManager.h"
+#import "KSWebSocketManager.h"
+#import "KSVideoRecordManager.h"
+#import "KSWebNavView.h"
 
-@interface KSAccompanyWebViewController ()
+#import "KSAudioSessionManager.h"
+#import "KSCloudBeatView.h"
+#import "MidiPlayerEngine.h"
 
+#define KSMidiSongFileKey (@"KSDownloadMidiSong")
+
+@interface KSAccompanyWebViewController ()<KSAQRecordManagerDelegate,KSAudioSessionManagerDelegate,PlayerEngineDelegate>
+
+@property (nonatomic, strong) KSAudioSessionManager *audioSessionManager;
+
+@property (nonatomic, assign) MetronomeType beatType;
+
+@property (nonatomic, strong) MidiPlayerEngine *playerEngine; // player
+
+@property (nonatomic, assign) BOOL initEngineSuccess; // 加载引擎是否成功
+
+@property (nonatomic, assign) float songOriginalSpeed;
+
+@property (nonatomic, assign) float currentSpeed; // 当前播放的速度
+
+@property (nonatomic, assign) BOOL isPlaying; // 是否正在播放的状态
+
+@property (nonatomic, strong) NSString *currentSongId; // song id
+
+@property (nonatomic, strong) NSDictionary *configEngineParm; // 初始化mid播放引擎的参数
+
+@property (nonatomic, strong) NSMutableDictionary *metronomeParm; // 节拍器播放的参数
+
+// 是否发送了musicXML信息
+@property (nonatomic, assign) BOOL hasSendStartMessage;
+
+@property (nonatomic, strong) KSAQRecordManager *AQManager;
+
+@property (nonatomic, strong) KSWebSocketManager *socketManager;
+
+@property (nonatomic, strong) NSMutableDictionary *evaluatParm;
+
+// 录制的message
+@property (nonatomic, strong) NSMutableDictionary *recordParm;
+
+// 评测开始时发送recordStart消息的标识
+@property (nonatomic, assign) BOOL isCompareStart;
+// 校音开始时发送 checkStart消息标识
+@property (nonatomic, assign) BOOL isSoundCheckStart;
+
+// 自定义UI控件容器
+@property (nonatomic, strong) UIView *viewContainer;
+
+@property (nonatomic, strong) KSVideoRecordManager *videoRecordManager;
+
+@property (nonatomic, strong) NSDictionary *endRecordParm;
+
+@property (nonatomic, assign) BOOL isCameraOpen;
 @end
 
 @implementation KSAccompanyWebViewController
 
+- (void)handerAudioInterruption {
+    if (_playerEngine) {
+        [self stopPlayAction];
+    }
+}
+
+- (void)resumeAudioSession {
+    // 恢复
+    if (_playerEngine) {
+        [self.playerEngine resumeAUGraph];
+    }
+}
+
+// 打断处理
+- (void)audioInterruption {
+    if (_videoRecordManager) {
+        [self.videoRecordManager resetSession];
+    }
+    [self handerAudioInterruption];
+}
+
+- (void)stopSession {
+    if (_videoRecordManager) {
+        [self.videoRecordManager stopSession];
+    }
+}
+
 - (void)viewDidLoad {
     [super viewDidLoad];
     // Do any additional setup after loading the view.
+    [self configAudioSession];
+    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appEnterBackground) name:@"appEnterBackground" object:nil];
+}
+
+- (void)connectSocketService {
+    
+    MJWeakSelf;
+    self.socketManager.didReceiveMessage = ^(id  _Nonnull message) {
+        dispatch_async(dispatch_get_main_queue(), ^{
+            // api
+            NSMutableDictionary *sendMessage = [NSMutableDictionary dictionary];
+            [sendMessage setValue:@"sendResult" forKey:@"api"];
+            [sendMessage setValue:message forKey:@"content"];
+            // 服务返回数据传给H5
+            [weakSelf postMessage:sendMessage];
+        });
+    };
+    self.socketManager.connectionStatus = ^(BOOL isSuccess) {
+        dispatch_async(dispatch_get_main_queue(), ^{
+            
+            if (!isSuccess) {
+                NSLog(@"-----连接失败");
+                if (weakSelf.hasSendStartMessage) {
+                    if (weakSelf.AQManager.isRunning) {
+                        NSDictionary *postParm = @{@"api" : @"cancelEvaluating",
+                                                   @"content" : @{@"reson":@"服务异常,请重试"}
+                        };
+                        [weakSelf postMessage:postParm];
+                        [weakSelf stopRecordService];
+                        weakSelf.isCompareStart = NO;
+                        weakSelf.isSoundCheckStart = NO;
+                    }
+                }
+                else if (weakSelf.evaluatParm) {
+                    NSMutableDictionary *sendParm = [NSMutableDictionary dictionaryWithDictionary:weakSelf.evaluatParm];
+                    NSLog(@"%@",sendParm);
+                    NSDictionary *valueDic = [weakSelf.evaluatParm dictionaryValueForKey:@"content"];
+                    [valueDic setValue:@"服务异常,请重试" forKey:@"reson"];
+                    NSLog(@"%@",valueDic);
+                    [sendParm setValue:valueDic forKey:@"content"];
+                    [weakSelf postMessage:sendParm];
+                    weakSelf.hasSendStartMessage = YES;
+                    weakSelf.isCompareStart = NO;
+                    weakSelf.isSoundCheckStart = NO;
+                }
+                else { // 其他断开
+                    if (weakSelf.AQManager.isRunning) {
+                        NSDictionary *postParm = @{@"api" : @"cancelEvaluating",
+                                                   @"content" : @{@"reson":@"服务异常,请重试"}
+                        };
+                        [weakSelf postMessage:postParm];
+                        [weakSelf stopRecordService];
+                        weakSelf.isCompareStart = NO;
+                        weakSelf.isSoundCheckStart = NO;
+                    }
+                }
+            }
+            else {
+                NSLog(@"-----连接成功");
+                if (weakSelf.hasSendStartMessage == NO && weakSelf.evaluatParm) {
+                    NSDictionary *content = [weakSelf.evaluatParm dictionaryValueForKey:@"content"];
+                    NSString *sendData = [weakSelf configDataCommond:@"musicXml" body:content type:@"SOUND_COMPARE"];
+                    [weakSelf sendDataToSocketService:sendData];
+                    [weakSelf postMessage:weakSelf.evaluatParm];
+                    weakSelf.hasSendStartMessage = YES;
+                    NSLog(@"---- send musicXML message");
+                }
+            }
+        });
+    };
+    [self.socketManager SRWebSocketOpen];
+}
+
+- (void)appEnterBackground {
+    NSDictionary *parm = @{@"api":@"suspendPlay"};
+    [self postMessage:parm];
+    [self handerAudioInterruption];
+}
+
+- (void)initWebView {
+    [self.view addSubview:self.navView];
+    CGFloat topHeight = kNaviBarHeight;
+    [self.navView mas_makeConstraints:^(MASConstraintMaker *make) {
+        make.left.right.top.mas_equalTo(self.view);
+        make.height.mas_equalTo(kNaviBarHeight);
+    }];
+    if (self.hiddenNavBar) {
+        topHeight = 0.0f;
+        [self.navView mas_updateConstraints:^(MASConstraintMaker *make) {
+            make.height.mas_equalTo(0);
+        }];
+    }
+    
+    [self.view addSubview:self.viewContainer];
+    self.viewContainer.frame = CGRectMake(0, 0, kScreenWidth, kScreen_Height);
+    
+    if (self.myWebView == nil) {
+        
+        WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
+        config.selectionGranularity = WKSelectionGranularityDynamic;
+        config.allowsInlineMediaPlayback = YES;
+        if (@available(iOS 10.0, *)) {
+            config.mediaTypesRequiringUserActionForPlayback = NO;
+        } else {
+            // Fallback on earlier versions
+            config.requiresUserActionForMediaPlayback = NO;
+        }
+        config.processPool = [KSBaseWKWebViewController singleWkProcessPool];
+        config.websiteDataStore = [WKWebsiteDataStore defaultDataStore];
+        //自定义的WKScriptMessageHandler 是为了解决内存不释放的问题
+        WeakWebViewScriptMessageDelegate *weakScriptMessageDelegate = [[WeakWebViewScriptMessageDelegate alloc] initWithDelegate:self];
+        //这个类主要用来做native与JavaScript的交互管理
+        WKUserContentController * wkUController = [[WKUserContentController alloc] init];
+        [wkUController addScriptMessageHandler:weakScriptMessageDelegate name:@"DAYA"];
+        config.userContentController = wkUController;
+        
+        WKPreferences *preferences = [WKPreferences new];
+        // 是否支出javaScript
+        preferences.javaScriptEnabled = YES;
+        //不通过用户交互,是否可以打开窗口
+        preferences.javaScriptCanOpenWindowsAutomatically = YES;
+        config.preferences = preferences;
+        self.myWebView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:config];
+        self.myWebView.opaque = NO;
+        self.myWebView.UIDelegate = self;
+        self.myWebView.navigationDelegate = self;
+        self.myWebView.scrollView.bounces = NO;
+        self.myWebView.backgroundColor = [UIColor clearColor];
+        self.myWebView.scrollView.backgroundColor = [UIColor clearColor];
+        // 加载进度条和title
+        [self.myWebView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil];
+        [self.myWebView addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionNew context:NULL];
+        [self.view addSubview:self.myWebView];
+        [self.myWebView mas_makeConstraints:^(MASConstraintMaker *make) {
+            make.left.right.mas_equalTo(self.view);
+            make.top.mas_equalTo(self.view.mas_top).offset(topHeight);
+            make.bottom.mas_equalTo(self.view.mas_bottom);
+        }];
+        if (@available(iOS 11.0, *)) {
+            self.myWebView.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
+        } else {
+            // Fallback on earlier versions
+        }
+        [self setupProgress];
+        // 修改userAgent
+        [self setUserAgent];
+    }
+    else {
+        [self.myWebView reload];
+    }
+}
+
+- (void)configRecordManager {
+    self.AQManager = [[KSAQRecordManager alloc] init];
+    self.AQManager.delegate = self;
+}
+
+- (void)viewWillAppear:(BOOL)animated {
+    [super viewWillAppear:animated];
+    [self connectSocketService];
+    if (self.isCameraOpen) {
+        [self.videoRecordManager configSessiondisplayInView:self.viewContainer];
+    }
+}
+
+- (void)viewWillDisappear:(BOOL)animated {
+    [super viewWillDisappear:animated];
+    if (_AQManager) {
+        [_AQManager freeAudioQueue];
+        _AQManager = nil;
+    }
+    if (_socketManager) {
+        [self.socketManager SRWebSocketClose];
+        _socketManager = nil;
+    }
+    [self stopSession];
+    
+    // 如果退出评测页面 清除 playerEngine
+    if (self.playerEngine) {
+        [self.playerEngine cleanup];
+        self.playerEngine = nil;
+    }
+    
+}
+
+- (void)sendDataToSocketService:(id)data {
+    if (_socketManager) {
+        [self.socketManager sendData:data];
+    }
+}
+
+#pragma mark --- WKScriptMessageHandler
+
+- (void)userContentController:(WKUserContentController *)userContentController
+      didReceiveScriptMessage:(WKScriptMessage *)message {
+    if ([message.name isEqualToString:@"DAYA"]) {
+        NSDictionary *parm = [self convertJsonStringToNSDictionary:message.body];
+        // 回到主线程
+        dispatch_async(dispatch_get_main_queue(), ^{
+             if ([[parm stringValueForKey:@"api"] isEqualToString:@"startEvaluating"]) { // 开始评测
+                  [self configRecordManager];
+                  self.hasSendStartMessage = NO;
+                  PREMISSIONTYPE isOk = [RecordCheckManager checkPermissionShowAlert:NO showInView:nil];
+                  if (isOk == PREMISSIONTYPE_YES) {
+                       self.evaluatParm = [NSMutableDictionary dictionaryWithDictionary:parm];
+                       // 如果socket 连上了
+                       if (self.socketManager.socketReadyState == SR_OPEN) {
+                            NSDictionary *content = [parm dictionaryValueForKey:@"content"];
+                            NSString *sendData = [self configDataCommond:@"musicXml" body:content type:@"SOUND_COMPARE"];
+                            [self sendDataToSocketService:sendData];
+                            [self postMessage:parm];
+                            self.hasSendStartMessage = YES;
+                       }
+                       else {
+                            [self connectSocketService];
+                       }
+                  }
+                  else {
+                       NSDictionary *postParm = @{@"api" : @"cancelEvaluating",
+                                                  @"content" : @{@"reson":@"没有麦克风权限"}
+                       };
+                       [self postMessage:postParm];
+                  }
+             }
+            else if ([[parm stringValueForKey:@"api"] isEqualToString:@"endEvaluating"]) {// 停止评测
+                 self.evaluatParm = nil;
+                 [self stopRecordService];
+                 [self postMessage:parm];
+                 [self sendEndMessage];
+            }
+            else if ([[parm stringValueForKey:@"api"] isEqualToString:@"cancelEvaluating"]) { // 取消评测
+                 self.evaluatParm = nil;
+                 [self stopRecordService];
+                 [self postMessage:parm];
+                 [self sendEndMessage];
+            }
+            else if ([[parm stringValueForKey:@"api"] isEqualToString:@"startRecording"]) { //  开始录制
+//                 self.recordParm = [NSMutableDictionary dictionaryWithDictionary:parm];
+                 [self postMessage:parm];
+                 self.isCompareStart = YES;
+                 [self startRecordService];
+            }
+            else if ([[parm stringValueForKey:@"api"] isEqualToString:@"endRecording"]) { // 停止录音
+                 self.recordParm = nil;
+                 [self stopRecordService];
+                 [self postMessage:parm];
+            }
+            else if ([[parm stringValueForKey:@"api"] isEqualToString:@"pauseRecording"]) {
+                [self puaseRecordService];
+                [self postMessage:parm];
+            }
+            else if ([[parm stringValueForKey:@"api"] isEqualToString:@"resumeRecording"]) {
+                [self resumeRecordService];
+                [self postMessage:parm];
+            }
+            else if ([[parm stringValueForKey:@"api"] isEqualToString:@"isWiredHeadsetOn"]) {
+                [self configAudioDeviceType:parm];
+            }
+            // 发送消息给socket service
+            else if ([[parm stringValueForKey:@"api"] isEqualToString:@"proxyMessage"]) {
+                 [self sendMessageToSocket:parm];
+            }
+            else if ([[parm stringValueForKey:@"api"] isEqualToString:@"proxyServiceMessage"]) {
+                 NSDictionary *content = [parm dictionaryValueForKey:@"content"];
+                 NSString *sendData = [content mj_JSONString];
+                 NSLog(@"proxyServiceMessage ------- %@",sendData);
+                 [self sendDataToSocketService:sendData];
+            }
+            else if ([[parm stringValueForKey:@"api"] isEqualToString:@"openCamera"]) { // 开启摄像头
+                 PREMISSIONTYPE canOpenCamera = [RecordCheckManager checkCameraPremissionAvaiable:YES showInView:self.view];
+                 if (canOpenCamera == PREMISSIONTYPE_YES) {
+                      self.isCameraOpen = YES;
+                      [self.videoRecordManager configSessiondisplayInView:self.viewContainer];
+                      [self postMessage:parm];
+                 }
+            }
+            else if ([[parm stringValueForKey:@"api"] isEqualToString:@"closeCamera"]) { // 关闭摄像头
+                self.isCameraOpen = NO;
+                if (self->_videoRecordManager) {
+                    [self.videoRecordManager removeDisplay];
+                }
+                [self postMessage:parm];
+            }
+            else if ([[parm stringValueForKey:@"api"] isEqualToString:@"startCapture"]) { // 开始录制
+                [RecordCheckManager checkPhotoLibraryPremissionAvaiable:YES showInView:self.view];
+                self.videoRecordManager.audioUrl = self.AQManager.audioUrl;
+                [self.videoRecordManager startRecord];
+                [self postMessage:parm];
+            }
+            else if ([[parm stringValueForKey:@"api"] isEqualToString:@"endCapture"]) { // 结束录制
+                if (self->_videoRecordManager) {
+                    self.endRecordParm = parm;
+                    [self.videoRecordManager stopRecord];
+                }
+                else {
+                    [self postMessage:parm];
+                }
+            }
+            else if ([[parm stringValueForKey:@"api"] isEqualToString:@"setCaptureMode"]) {
+                NSString *modeString = [[parm dictionaryValueForKey:@"content"] stringValueForKey:@"mode"];
+                BOOL isIgnoreAudio = [modeString isEqualToString:@"evaluating"];
+                [self postMessage:parm];
+                [self.videoRecordManager setIgnoreAudio:isIgnoreAudio];
+            }
+            else if ([[parm stringValueForKey:@"api"] isEqualToString:@"startSoundCheck"]) { // 开始校音
+                [self configRecordManager];
+                [self postMessage:parm];
+                self.isCompareStart = NO;
+                self.isSoundCheckStart = YES;
+                [self startRecordService];
+            }
+            else if ([[parm stringValueForKey:@"api"] isEqualToString:@"endSoundCheck"]) {
+                // 结束校音
+                self.isSoundCheckStart = NO;
+                [self stopRecordService];
+                [self postMessage:parm];
+            }
+            else if ([[parm stringValueForKey:@"api"] isEqualToString:@"videoUpdate"]) { // 上传
+                NSMutableDictionary *sendParm = [NSMutableDictionary dictionaryWithDictionary:parm];
+                NSMutableDictionary *contentParm = [NSMutableDictionary dictionaryWithDictionary:[sendParm dictionaryValueForKey:@"content"]];
+                if (self.videoRecordManager) {
+                    [self.videoRecordManager uploadRecordVideoSuccess:^(NSString * _Nonnull videoUrl) {
+                        [contentParm setValue:@"success" forKey:@"type"];
+                        [contentParm setValue:videoUrl forKey:@"filePath"];
+                        [contentParm setValue:@"上传成功" forKey:@"message"];
+                        [sendParm setValue:contentParm forKey:@"content"];
+                        
+                        [self postMessage:sendParm];
+                    } failure:^(NSString * _Nonnull desc) {
+                        [contentParm setValue:@"error" forKey:@"type"];
+                        [contentParm setValue:desc forKey:@"message"];
+                        [sendParm setValue:contentParm forKey:@"content"];
+                        [self postMessage:sendParm];
+                    }];
+                }
+            }
+#pragma mark -------- 云教练原生播放对接
+            else if ([[parm stringValueForKey:@"api"] isEqualToString:@"cloudDetail"]) { // 初始化方法
+                /**
+                 api: 'cloudDetail',
+                     content: {
+                         midi: '',
+                         denominator: 4,
+                         numerator: 4,
+                         // xml整体原始速度
+                         originalSpeed: 90,
+                         // 间隔(ms)
+                         interval: 50
+                     }
+                 */
+                
+                [self configAudioSession];
+                // 重置track num array
+                self.configEngineParm = [parm copy];
+                NSDictionary *content = [parm dictionaryValueForKey:@"content"];
+                NSString *midiUrl = [content stringValueForKey:@"midi"];
+                NSInteger denominator = [content integerValueForKey:@"denominator"];
+                NSInteger numerator = [content integerValueForKey:@"numerator"];
+                NSString *beatString = [NSString stringWithFormat:@"%zd/%zd", numerator,denominator];
+                self.beatType = [self getBeatTypeFromString:beatString];
+                
+                float originalSpeed = [content floatValueForKey:@"originalSpeed"];
+                self.songOriginalSpeed = originalSpeed;
+                self.currentSpeed = originalSpeed;
+                NSInteger reportInterval = [content integerValueForKey:@"interval"];
+                // 下载midi文件
+                BOOL hasSaveSong = [self checkSongHasSaveWithSongUrl:midiUrl];
+                NSString *fileName = [[midiUrl componentsSeparatedByString:@"/"] lastObject];
+                NSString *filePath = [self getFilePathWithName:fileName];
+                if (hasSaveSong) {
+                    [self configPlayerEngineWithSong:filePath reportTime:reportInterval];
+                }
+                else {
+                    MJWeakSelf;
+                    [self downloadMidiFile:midiUrl success:^{
+                        [weakSelf configPlayerEngineWithSong:filePath reportTime:reportInterval];
+                    } faliure:^{
+                        
+                    }];
+                }
+                        
+            }
+
+            else if ([[parm stringValueForKey:@"api"] isEqualToString:@"cloudGetMediaStatus"]) { // 获取播放状态
+            /**
+             api: 'cloudGetMediaStatus',
+                 content: {
+                     status: 'init' | 'play' | 'suspend'
+                 }
+             */
+                NSString *engineStatus = @"init";
+                if (self.initEngineSuccess) {
+                    if (self.isPlaying) {
+                        engineStatus = @"play";
+                    }
+                    else {
+                        engineStatus = @"suspend";
+                    }
+                }
+                NSMutableDictionary *valueDic = [NSMutableDictionary dictionaryWithDictionary:[parm dictionaryValueForKey:@"content"]];
+                [valueDic setValue:engineStatus forKey:@"status"];
+                NSMutableDictionary *sendParm = [NSMutableDictionary dictionaryWithDictionary:parm];
+                [sendParm setValue:valueDic forKey:@"content"];
+                [self postMessage:sendParm];
+            }
+            // 播放、暂停、进度、播放结束、跳转指定位置
+            else if ([[parm stringValueForKey:@"api"] isEqualToString:@"cloudPlay"]) { // 播放
+                /**
+                 api: 'cloudPlay',
+                     content: {
+                         // 当前曲目id
+                         songID: 0,
+                         // xml整体原始速度
+                         originalSpeed: 90,
+                         // 当前选择速度
+                         speed: 90,
+                         // 开始时间(ms)
+                         startTime: 0
+                         // 播放频率
+                         hertz:440
+                     }
+                 */
+                NSDictionary *content = [parm dictionaryValueForKey:@"content"];
+                self.currentSongId = [content stringValueForKey:@"songID"];
+                float speed = [content floatValueForKey:@"speed"];
+                float originalSpeed = [content floatValueForKey:@"originalSpeed"];
+                double rate = speed / originalSpeed;
+                self.currentSpeed = speed;
+                float hertz = [content floatValueForKey:@"hertz"];
+                // 播放的hertz
+                [self.playerEngine adjustPitchByHZ:hertz];
+                
+                [self.playerEngine setMusicPlayerSpeed:rate];
+                Float64 startTime = [content doubleValueForKey:@"startTime"];
+                NSLog(@"------%@", [content stringValueForKey:@"startTime"]);
+                [self.playerEngine setProgressTime:(startTime/1000)];
+                // 播放
+                [self playAction];
+                [self postMessage:parm];
+            }
+            else if ([[parm stringValueForKey:@"api"] isEqualToString:@"cloudSuspend"]) { // 暂停
+                 /**
+                  api: 'cloudSuspend',
+                  content: {
+                  // 当前曲目id
+                  songID: 0,
+                  }
+                  */
+                 [self stopPlayAction];
+                 [self postMessage:parm];
+                 
+            }
+            else if ([[parm stringValueForKey:@"api"] isEqualToString:@"cloudSetCurrentTime"]) { // // 跳转指定位置
+                /**
+                 api: 'cloudSetCurrentTime',
+                     content: {
+                         // 当前曲目id
+                         songID: 0,
+                         // 指定位置时间(ms)
+                         currentTime: 0
+                     }
+                 */
+                NSDictionary *content = [parm dictionaryValueForKey:@"content"];
+                Float64 currentTime = [content doubleValueForKey:@"currentTime"];
+                if (self.playerEngine) {
+                    [self.playerEngine setProgressTime:(currentTime/1000)];
+                }
+                [self postMessage:parm];
+            }
+            else if ([[parm stringValueForKey:@"api"] isEqualToString:@"cloudChangeSpeed"]) { // 调速 范围(45-270整数)
+                /**
+                 api: 'cloudChangeSpeed',
+                     content: {
+                         // 当前曲目id
+                         songID: 0,
+                         // 调整速度
+                         speed: 90
+                         // xml整体原始速度
+                         originalSpeed: 90,
+                     }
+                 */
+                NSDictionary *content = [parm dictionaryValueForKey:@"content"];
+                float speed = [content floatValueForKey:@"speed"];
+                float originalSpeed = [content floatValueForKey:@"originalSpeed"];
+                double rate = speed / originalSpeed;
+                self.currentSpeed = speed;
+                [self.playerEngine setMusicPlayerSpeed:rate];
+                // 回报信息
+                [self postMessage:parm];
+            }
+            // 设置每个轨道音量
+            else if ([[parm stringValueForKey:@"api"] isEqualToString:@"cloudVolume"]) {
+                /**
+                 api: 'cloudVolume',
+                     content: {
+                         parts: [
+                             {
+                                 name: '',
+                                 volume: 90,//0-100
+                             }
+                         ]
+                     }
+                 */
+//                NSDictionary *content = [parm dictionaryValueForKey:@"content"];
+//                for (NSDictionary *dic in [content arrayValueForKey:@"parts"]) {
+//                   NSInteger trackId = [self.playerEngine getSingleTrackNumByName:[dic stringValueForKey:@"name"]];
+//                    if (trackId >= 0) {
+//                        float volume = [dic floatValueForKey:@"volume"];
+//
+//                        [self.playerEngine volume:volume/100 WithTrack:(UInt32)trackId];
+//                    }
+//                }
+                [self postMessage:parm];
+                [self.playerEngine getAllTrackVolume];
+            }
+            else if ([[parm stringValueForKey:@"api"] isEqualToString:@"cloudMetronome"]) { // 节拍器播放
+                 self.metronomeParm = [NSMutableDictionary dictionaryWithDictionary:parm];
+                 NSDictionary *content = [parm dictionaryValueForKey:@"content"];
+                 NSInteger repeatCount = [content integerValueForKey:@"repeat"];
+                 NSInteger denominator = [content integerValueForKey:@"denominator"];
+                 NSInteger numerator = [content integerValueForKey:@"numerator"];
+                 NSInteger supplement = [content integerValueForKey:@"supplement"];
+                 NSString *beatString = [NSString stringWithFormat:@"%zd/%zd", numerator,denominator];
+                 self.beatType = [self getBeatTypeFromString:beatString];
+                 [self showBeatViewRepeatCount:repeatCount supplement:supplement];
+            }
+            else if ([[parm stringValueForKey:@"api"] isEqualToString:@"cloudDestroy"]) { // 销毁播放器
+                self.initEngineSuccess = NO;
+                // 重置track num array
+                self.isPlaying = NO;
+                if (self.playerEngine) {
+                    [self.playerEngine cleanup];
+                    self.playerEngine = nil;
+                }
+            }
+            else {
+                [super handleScriptMessageSource:parm];
+            }
+            
+        });
+    }
+}
+    
+
+- (void)showBeatViewRepeatCount:(NSInteger)repeatCount supplement:(NSInteger)supplement {
+    KSCloudBeatView *beatView = [KSCloudBeatView shareInstanceWithBeatType:self.beatType speed:self.currentSpeed repeatCount:repeatCount supplement:supplement];
+    MJWeakSelf;
+    [beatView startPlayWithEndCallback:^(BOOL isCancle) {
+        if (isCancle) { // 取消
+            [weakSelf sendEndMetronomeMessage:YES];
+        }
+        else { // 播放完成
+            [weakSelf sendEndMetronomeMessage:NO];
+        }
+    }];
+    [self.view addSubview:beatView];
+}
+
+
+- (void)sendEndMetronomeMessage:(BOOL)isCancel {
+    NSString *status = isCancel ? @"cancel" : @"finish";
+    NSMutableDictionary *valueDic = [NSMutableDictionary dictionaryWithDictionary:[self.metronomeParm dictionaryValueForKey:@"content"]];
+    [valueDic setValue:status forKey:@"status"];
+    NSMutableDictionary *sendParm = [NSMutableDictionary dictionaryWithDictionary:self.metronomeParm];
+    [sendParm setValue:valueDic forKey:@"content"];
+    [self postMessage:sendParm];
+}
+
+- (void)downloadMidiFile:(NSString *)midiUrl success:(void(^)(void))success faliure:(void(^)(void))faliure {
+    [self showhud];
+    [KSNetworkingManager downloadFileRequestWithFileUrl:midiUrl progress:^(int64_t bytesRead, int64_t totalBytes) {
+        
+    } success:^(NSURL * _Nonnull fileUrl) {
+        [self removehub];
+        if ([self saveMidiFileWithUrl:fileUrl midiUrl:midiUrl]) {
+            success();
+        }
+    } faliure:^(NSError * _Nonnull error) {
+        faliure();
+    }];
+}
+
+- (BOOL)saveMidiFileWithUrl:(NSURL *)fileUrl midiUrl:(NSString *)midiUrl {
+    NSData *sourceData = [NSData dataWithContentsOfURL:fileUrl];
+    NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
+    NSString *filePath= [cachePath stringByAppendingPathComponent:@"MidiSong"];
+    // 先创建子目录
+    NSFileManager *fileManager = [NSFileManager defaultManager];
+    if (![fileManager fileExistsAtPath:filePath]) {
+        [fileManager createDirectoryAtPath:filePath withIntermediateDirectories:YES attributes:nil error:nil];
+    }else{
+        NSLog(@"已创建文件夹");
+    }
+    NSString *fileName = [[midiUrl componentsSeparatedByString:@"/"] lastObject];
+    NSString *tempPath = [filePath stringByAppendingPathComponent:fileName];
+    BOOL success = [sourceData writeToFile:tempPath atomically:NO];
+    return success;
+}
+
+- (NSString *)getFilePathWithName:(NSString *)fileName {
+    NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
+    NSString *filePath= [cachePath stringByAppendingPathComponent:@"MidiSong"];
+    return [filePath stringByAppendingPathComponent:fileName];;
+}
+
+- (BOOL)checkSongHasSaveWithSongUrl:(NSString *)songUrl {
+    BOOL hasSaveFile = NO;
+    NSString *fileName = [[songUrl componentsSeparatedByString:@"/"] lastObject];
+    NSString *filePath = [self getFilePathWithName:fileName];
+    
+    NSFileManager *fileManager = [NSFileManager defaultManager];
+    if ([fileManager fileExistsAtPath:filePath]) {
+        hasSaveFile = YES;
+    }
+    return hasSaveFile;
+}
+
+
+- (void)sendMessageToSocket:(NSDictionary *)parm {
+    NSString *messageHeader = @"proxyMessage";
+    NSString *sendMessage = [self configDataCommond:messageHeader body:[parm dictionaryValueForKey:@"content"]];
+    [self sendDataToSocketService:sendMessage];
+}
+
+- (void)configAudioDeviceType:(NSDictionary *)parm {
+    AUDIODEVICE_TYPE type = [KSAQRecordManager queryAudioOutputDeviceType];
+    NSString *valueStr = @"";
+    BOOL checkIsWired = NO;
+    switch (type) {
+        case AUDIODEVICE_TYPE_HEADPHONE:
+        {
+            valueStr = @"有线耳机";
+            checkIsWired = YES;
+        }
+            break;
+        case AUDIODEVICE_TYPE_BLUETOOTH:
+        {
+            valueStr = @"蓝牙耳机";
+            checkIsWired = YES;
+        }
+            break;
+        case AUDIODEVICE_TYPE_NONE:
+        {
+            valueStr = @"";
+            checkIsWired = NO;
+        }
+            break;
+        default:
+            break;
+    }
+    NSMutableDictionary *valueDic = [NSMutableDictionary dictionaryWithDictionary:[parm dictionaryValueForKey:@"content"]];
+    [valueDic setValue:valueStr forKey:@"type"];
+    [valueDic setValue:[NSNumber numberWithBool:checkIsWired] forKey:@"checkIsWired"];
+    NSMutableDictionary *sendParm = [NSMutableDictionary dictionaryWithDictionary:parm];
+    [sendParm setValue:valueDic forKey:@"content"];
+    [self postMessage:sendParm];
+}
+
+- (void)startRecordService {
+    if (self.AQManager.isRunning) {
+        [self.AQManager stopRecord];
+    }
+    [self.AQManager startRecord];
+}
+
+- (void)stopRecordService {
+    if (self.AQManager.isRunning) {
+        [self.AQManager stopRecord];
+    }
+}
+
+- (void)puaseRecordService {
+    if (self.AQManager.isRunning) {
+        [self.AQManager pauserRecord];
+    }
+}
+
+- (void)resumeRecordService {
+    [self.AQManager resumeRecord];
+}
+
+- (void)sendEndMessage {
+    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
+        // 上传停止的信息 发送给服务端
+        NSString *endMessage = @"recordEnd";
+        NSString *endData = [self configDataCommond:endMessage body:nil type:@"SOUND_COMPARE"];
+        [self sendDataToSocketService:endData];
+        self.isCompareStart = NO;
+        NSLog(@"---- send end message");
+    });
+}
+
+#pragma mark-------- KSAQRecordManagerDelegate
+- (void)audioRecordInterruption {
+    NSDictionary *postParm = @{@"api" : @"cancelEvaluating",
+                               @"content" : @{@"reson":@"录制错误,请重试"}
+    };
+    [self postMessage:postParm];
+}
+
+
+
+- (void)audioRouteChange:(AUDIODEVICE_TYPE)type {
+    NSString *valueStr = @"";
+    BOOL checkIsWired = NO;
+    switch (type) {
+        case AUDIODEVICE_TYPE_HEADPHONE:
+        {
+            valueStr = @"有线耳机";
+            checkIsWired = YES;
+        }
+            break;
+        case AUDIODEVICE_TYPE_BLUETOOTH:
+        {
+            valueStr = @"蓝牙耳机";
+            checkIsWired = YES;
+        }
+            break;
+        case AUDIODEVICE_TYPE_NONE:
+        {
+            valueStr = @"";
+            checkIsWired = NO;
+        }
+            break;
+        default:
+            break;
+    }
+    NSDictionary *postParm = @{@"api" : @"listenerWiredStatus",
+                               @"content" : @{@"type":valueStr,
+                                              @"checkIsWired":[NSNumber numberWithBool:checkIsWired]
+                               }
+    };
+    [self postMessage:postParm];
+}
+
+
+
+
+
+- (void)audioRecord:(KSAQRecordManager *)audioRecord didRecordAudioData:(void *)data length:(UInt32)length {
+    
+    if (self.socketManager.socketReadyState != SR_OPEN) {
+        return;
+    }
+    NSData *pushData = [[NSData alloc] initWithBytes:data length:length];
+    if (self.isCompareStart) { // 发送评测开始消息
+        dispatch_async(dispatch_get_main_queue(), ^{
+            NSDate *date = [NSDate date];
+            NSTimeInterval inteveral = [date timeIntervalSince1970];
+            double beginTime = inteveral - audioRecord.sampleTime;
+            NSDictionary *parm = @{
+                @"api" : @"recordStartTime",
+                @"content" : @{@"inteveral" : [NSNumber numberWithDouble:beginTime]}
+            };
+            [self postMessage:parm];
+        });
+        
+        NSLog(@"--------- send start message");
+        _isCompareStart = NO;
+        NSString *startMessage = @"recordStart";
+        NSString *startString = [self configDataCommond:startMessage body:nil type:@"SOUND_COMPARE"];
+        [self sendDataToSocketService:startString];
+         
+//         // 发送开始录制的消息给H5
+//         if (self.recordParm) {
+//              [self postMessage:self.recordParm];
+//         }
+    }
+    else if (self.isSoundCheckStart) { // 校音开始
+        
+        NSLog(@"--------- send check start message");
+        _isSoundCheckStart = NO;
+        NSString *checkStartMessage = @"start";
+        NSString *startString = [self configDataCommond:checkStartMessage body:nil type:@"PITCH_DETECTION"];
+        [self sendDataToSocketService:startString];
+    }
+        NSLog(@"--------- send audio data length %d", length);
+    [self sendDataToSocketService:pushData];
+}
+
+- (NSString *)configDataCommond:(NSString *)commond body:(id)bodyMessage type:(NSString *)dataType {
+    NSMutableDictionary *parm = [NSMutableDictionary dictionary];
+    if (bodyMessage) {
+        [parm setValue:bodyMessage forKey:@"body"];
+    }
+    NSMutableDictionary *headerParm = [NSMutableDictionary dictionary];
+    if ([NSString isEmptyString:commond]) {
+        [headerParm setValue:@"" forKey:@"commond"];
+    }
+    else {
+        [headerParm setValue:commond forKey:@"commond"];
+    }
+    if (![NSString isEmptyString:dataType]) {
+        [headerParm setValue:dataType forKey:@"type"];
+    }
+    [headerParm setValue:@(200) forKey:@"status"];
+    [parm setValue:headerParm forKey:@"header"];
+    return [parm mj_JSONString];
 }
 
+
+- (NSString *)configDataCommond:(NSString *)commond body:(id)bodyMessage {
+    NSMutableDictionary *parm = [NSMutableDictionary dictionary];
+    if (bodyMessage) {
+        [parm setValue:bodyMessage forKey:@"body"];
+    }
+    if ([NSString isEmptyString:commond]) {
+        [parm setValue:@{@"commond":@""} forKey:@"header"];
+    }
+    else {
+        [parm setValue:@{@"commond":commond} forKey:@"header"];
+    }
+    return [parm mj_JSONString];
+}
+
+- (KSWebSocketManager *)socketManager {
+    if (!_socketManager) {
+        _socketManager = [[KSWebSocketManager alloc] init];
+    }
+    return _socketManager;
+}
+
+#pragma mark --- lazying
+- (UIView *)viewContainer {
+    if (!_viewContainer) {
+        _viewContainer = [[UIView alloc] init];
+    }
+    return _viewContainer;
+}
+
+- (KSVideoRecordManager *)videoRecordManager {
+    if (!_videoRecordManager) {
+        MJWeakSelf;
+        _videoRecordManager = [[KSVideoRecordManager alloc] initSessionRecordCallback:^(BOOL isSuccess, NSString * _Nullable message) {
+            if (isSuccess) {
+                [weakSelf showSuccessMessage:message];
+            }
+            else {
+                if (![NSString isEmptyString:message]) {
+                    [weakSelf MBPShow:message];
+                }
+                
+            }
+        }];
+    }
+    return _videoRecordManager;
+}
+
+- (void)showSuccessMessage:(NSString *)message {
+    [self MBPShow:message];
+    // 成功
+    if (self.endRecordParm) {
+        [self postMessage:self.endRecordParm];
+        self.endRecordParm = nil;
+    }
+}
+
+- (MetronomeType)getBeatTypeFromString:(NSString *)typeString {
+    if ([typeString isEqualToString:@"1/4"]) {
+        return MetronomeType1V4;
+    }
+    else if ([typeString isEqualToString:@"2/4"]) {
+        return MetronomeType2V4;
+    }
+    else if ([typeString isEqualToString:@"3/4"]) {
+        return MetronomeType3V4;
+    }
+    else if ([typeString isEqualToString:@"4/4"]) {
+        return MetronomeType4V4;
+    }
+    else if ([typeString isEqualToString:@"3/8"]) {
+        return MetronomeType3V8;
+    }
+    else if ([typeString isEqualToString:@"6/8"]) {
+        return MetronomeType6V8;
+    }
+    else {
+        return MetronomeType4V4;
+    }
+}
+
+
+#pragma mark ------- midi 播放相关
+- (void)configPlayerEngineWithSong:(NSString *)songPath reportTime:(NSInteger)reportTime {
+    self.initEngineSuccess = NO;
+    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+        self.playerEngine = [[MidiPlayerEngine alloc] init];
+        self.playerEngine.reportTime = reportTime;
+        self.playerEngine.delegate = self;
+        [self.playerEngine configSoundFilePath:[[NSBundle mainBundle]
+                                                pathForResource:@"synthgms" ofType:@"sf2"]];
+        [self.playerEngine loadMIDIFileWithString:songPath];
+    });
+}
+
+- (void)configAudioSession {
+    self.audioSessionManager = [[KSAudioSessionManager alloc] init];
+    self.audioSessionManager.delegate = self;
+    [self.audioSessionManager configAudioSession:AUDIOCONFIG_PLAYANDRECORD];
+}
+
+#pragma mark ---- PlayerEngineDelegate
+/** 进度更新
+ api: 'cloudTimeUpdae',
+ content: {
+ // 当前曲目id
+ songID: 0,
+ // 当前位置时间(ms)
+ currentTime: 0
+ }
+ */
+/** 播放结束事件
+ api: 'cloudplayed',
+ content: {
+ // 当前曲目id
+ songID: 0,
+ }
+ */
+- (void)ProgressUpdated:(float)progress currentTime:(MusicTimeStamp)currentTime {
+    if (self.isPlaying == NO) {
+        return;
+    }
+    // 回调
+    NSMutableDictionary *sendParm = [NSMutableDictionary dictionary];
+    [sendParm setValue:@"cloudTimeUpdae" forKey:@"api"];
+    NSMutableDictionary *content = [NSMutableDictionary dictionary];
+    [content setValue:self.currentSongId forKey:@"songID"];
+    [content setValue:@(currentTime*1000) forKey:@"currentTime"];
+    [sendParm setValue:content forKey:@"content"];
+    [self postMessage:sendParm];
+//     NSLog(@"------time source %@", content);
+}
+
+- (void)playEnd {
+    [self stopPlayAction];
+    NSMutableDictionary *sendParm = [NSMutableDictionary dictionary];
+    [sendParm setValue:@"cloudplayed" forKey:@"api"];
+    NSMutableDictionary *content = [NSMutableDictionary dictionary];
+    [content setValue:self.currentSongId forKey:@"songID"];
+    [sendParm setValue:content forKey:@"content"];
+    [self postMessage:sendParm];
+}
+
+- (void)initPlayerEngineSuccess:(float)totalTime {
+    self.initEngineSuccess = YES;
+    if (self.configEngineParm) {
+        NSMutableDictionary *sendParm = [NSMutableDictionary dictionaryWithDictionary:self.configEngineParm];
+        NSMutableDictionary *content = [NSMutableDictionary dictionaryWithDictionary:[sendParm dictionaryValueForKey:@"content"]];
+        [content setValue:@(totalTime*1000) forKey:@"midiDuration"];
+        [sendParm setValue:content forKey:@"content"];
+        [self postMessage:sendParm];
+        self.configEngineParm = nil;
+    }
+}
+
+// 总时长
+- (void)GetMusicTotalTime:(float)time {
+    
+}
+
+
+#pragma mark ----- 播放控制
+- (void)playAction {
+    self.isPlaying = YES;
+    [self.playerEngine playMIDIFile];
+}
+
+- (void)stopPlayAction {
+    self.isPlaying = NO;
+    if ([self.playerEngine isPlayingFile]) {
+        [self.playerEngine stopPlayingMIDIFile];
+    }
+}
 /*
 #pragma mark - Navigation
 

+ 12 - 10
KulexiuForStudent/KulexiuForStudent/Common/Base/KSBaseWKWebViewController.m

@@ -207,17 +207,19 @@
 
 - (NSString *)url {
     if (_url) {
-        if ([_url containsString:@"Authorization="]) {
-            NSRange range = [_url rangeOfString:@"Authorization="];
-            if (range.location != NSNotFound) {
-                _url = [_url substringToIndex:range.location-1];
+        if ([_url containsString:hostURL]) {
+            if ([_url containsString:@"Authorization="]) {
+                NSRange range = [_url rangeOfString:@"Authorization="];
+                if (range.location != NSNotFound) {
+                    _url = [_url substringToIndex:range.location-1];
+                }
+            }
+            NSString *sepectString = [_url containsString:@"?"] ? @"&" : @"?";
+            NSString *tokenStr = UserDefault(TokenKey);
+            if (![NSString isEmptyString:tokenStr]) {
+                NSString *token = [[NSString stringWithFormat:@"Authorization=%@ %@", UserDefault(Token_type), tokenStr] stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
+                _url = [NSString stringWithFormat:@"%@%@%@",_url, sepectString, token];
             }
-        }
-        NSString *sepectString = [_url containsString:@"?"] ? @"&" : @"?";
-        NSString *tokenStr = UserDefault(TokenKey);
-        if (![NSString isEmptyString:tokenStr]) {
-            NSString *token = [[NSString stringWithFormat:@"Authorization=%@ %@", UserDefault(Token_type), tokenStr] stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
-            _url = [NSString stringWithFormat:@"%@%@%@",_url, sepectString, token];
         }
     }
     return _url;

+ 10 - 0
KulexiuForStudent/KulexiuForStudent/Common/Base/KSNetworkingManager.h

@@ -63,6 +63,16 @@ NS_ASSUME_NONNULL_BEGIN
 // 多文件下载
 + (void)mutiDownloadFileRequest:(NSArray *)fileUrl progress:(void (^)(int64_t bytesRead, int64_t totalBytes))progress success:(void(^)(NSArray *dics))success faliure:(void(^)(NSError *error))faliure;
 
+#pragma mark ---- 云教练提交反馈
+
+/// 云教练反馈
+/// @param post post
+/// @param content content
+/// @param type type  SMART_PRACTICE
+/// @param success 成功
+/// @param faliure 失败
++ (void)cloudFeedbackRequest:(NSString *)post content:(NSString *)content type:(NSString *)type success:(void(^)(NSDictionary *dic))success faliure:(void(^)(NSError *error))faliure;
+
 #pragma mark ----- 登录相关
 /**
  手机号密码登录

+ 18 - 0
KulexiuForStudent/KulexiuForStudent/Common/Base/KSNetworkingManager.m

@@ -362,6 +362,24 @@
     [self mutilDownTaskWithUrl:fileUrl progress:progress successBlock:success failBlock:faliure];
 }
 
+#pragma mark ---- 云教练提交反馈
+
+/// 云教练反馈
+/// @param post post
+/// @param content content
+/// @param type type  SMART_PRACTICE
+/// @param success 成功
+/// @param faliure 失败
++ (void)cloudFeedbackRequest:(NSString *)post content:(NSString *)content type:(NSString *)type success:(void(^)(NSDictionary *dic))success faliure:(void(^)(NSError *error))faliure {
+    [self configRequestMethodForm];
+    NSMutableDictionary *parm = [NSMutableDictionary dictionary];
+    [parm setValue:@"iOS" forKey:@"clientType"];
+    [parm setValue:content forKey:@"content"];
+    [parm setValue:@"SMART_PRACTICE" forKey:@"type"];
+    NSString *url = [NSString stringWithFormat:@"%@%@",hostURL, @"/api-student/sysSuggestion/add"];
+    [self request:post andWithUrl:url and:parm success:success faliure:faliure];
+}
+
 #pragma mark ----- 登录相关
 + (void)LoginRequest:(NSString *)post phone:(NSString *)phone password:(NSString *)password success:(void (^)(NSDictionary * _Nonnull))success faliure:(void (^)(NSError * _Nonnull))faliure {
     [self configRequestMethodForm];

+ 40 - 0
KulexiuForStudent/KulexiuForStudent/Common/Base/KSVideoRecordManager.h

@@ -0,0 +1,40 @@
+//
+//  KSVideoRecordManager.h
+//  TeacherDaya
+//
+//  Created by Kyle on 2021/8/16.
+//  Copyright © 2021 DayaMusic. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+typedef void(^KSVideoRecordCallback)(BOOL isSuccess, NSString * _Nullable message);
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface KSVideoRecordManager : NSObject
+
+
+@property (nonatomic, strong) NSURL *audioUrl;
+
+@property (nonatomic, assign) BOOL ignoreAudio;
+
+- (instancetype)initSessionRecordCallback:(KSVideoRecordCallback)callback;
+
+- (void)configSessiondisplayInView:(UIView *)containerView;
+
+- (void)removeDisplay;
+
+- (void)stopSession;
+
+- (void)resetSession;
+
+- (void)startRecord;
+
+- (void)stopRecord;
+
+- (void)uploadRecordVideoSuccess:(void(^)(NSString *videoUrl))success failure:(void(^)(NSString *desc))faliure;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 434 - 0
KulexiuForStudent/KulexiuForStudent/Common/Base/KSVideoRecordManager.m

@@ -0,0 +1,434 @@
+//
+//  KSVideoRecordManager.m
+//  TeacherDaya
+//
+//  Created by Kyle on 2021/8/16.
+//  Copyright © 2021 DayaMusic. All rights reserved.
+//
+
+#import "KSVideoRecordManager.h"
+#import <AVFoundation/AVFoundation.h>
+#import <AssetsLibrary/AssetsLibrary.h>
+#import "TZImageManager.h"
+#import "KSVideoEditor.h"
+
+@interface KSVideoRecordManager ()<AVCaptureFileOutputRecordingDelegate>
+
+//会话 负责输入和输出设备之间的数据传递
+@property (nonatomic, strong) AVCaptureSession *captureSession;
+
+@property (nonatomic, strong) AVCaptureDeviceInput *videoCaptureDeviceInput;
+
+@property (nonatomic, strong) AVCaptureDeviceInput *audioCaptureDeviceInput;
+
+// 视频流输出
+@property (nonatomic, strong) AVCaptureMovieFileOutput *captureMovieFileOutput;
+
+// 相机拍摄预览图层
+@property (nonatomic, strong) AVCaptureVideoPreviewLayer *captureVideoPreviewLayer;
+
+@property (nonatomic, strong) NSURL *videoFileURL;
+
+@property (nonatomic, assign) BOOL recordEnable;
+
+// 是否正在录制
+@property (nonatomic, assign) BOOL isRecording;
+
+@property (nonatomic, copy) KSVideoRecordCallback callback;
+
+@property (nonatomic, strong) PHAsset *videoAsset;
+
+@property (nonatomic, strong) NSString *presentName;
+
+@property (strong, nonatomic) MBProgressHUD *HUD;
+
+@end
+
+@implementation KSVideoRecordManager
+
+- (instancetype)initSessionRecordCallback:(KSVideoRecordCallback)callback {
+    self = [super init];
+    if (self) {
+        if (callback) {
+            self.callback = callback;
+        }
+    }
+    return self;
+}
+
+- (void)setIgnoreAudio:(BOOL)ignoreAudio {
+    _ignoreAudio = ignoreAudio;
+    [self resetSession];
+}
+
+- (void)configSessiondisplayInView:(UIView *)containerView {
+    _captureSession = [[AVCaptureSession alloc] init];
+
+    // 设置YES 播放web伴奏会导致打断
+    _captureSession.automaticallyConfiguresApplicationAudioSession = NO;
+    // 初始化会话对象
+    if ([_captureSession canSetSessionPreset:AVCaptureSessionPresetInputPriority]) {
+        _captureSession.sessionPreset = AVCaptureSessionPresetInputPriority;
+    }
+    NSError *error = nil;
+    
+    // 获取视频输出对象
+    AVCaptureDevice *videoCaptureDevice = [self cameraDeviceWithPosition:(AVCaptureDevicePositionFront)];
+    if (!videoCaptureDevice) {
+        if (self.callback) {
+            self.callback(NO, @"获取后置摄像头失败!");
+        }
+    }
+    _videoCaptureDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:videoCaptureDevice error:&error];
+    if (error) {
+        if (self.callback) {
+            self.callback(NO, @"获取视频设备输入出错!");
+        }
+        return;
+    }
+    if ([_captureSession canAddInput:_videoCaptureDeviceInput]) {
+        [_captureSession addInput:_videoCaptureDeviceInput];
+    }
+    else {
+        if (self.callback) {
+            self.callback(NO, @"无法添加视频输入对象");
+        }
+    }
+    if (_ignoreAudio == NO) {
+        // 获取音频输入对象
+        AVCaptureDevice *audioCaptureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
+        _audioCaptureDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:audioCaptureDevice error:&error];
+        if (error) {
+            if (self.callback) {
+                self.callback(NO, @"获取音频设备输入出错!");
+            }
+            return;
+        }
+        //将设备输入添加到会话中
+        if ([_captureSession canAddInput:_audioCaptureDeviceInput]) {
+            [_captureSession addInput:_audioCaptureDeviceInput];
+        }
+        else {
+            if (self.callback) {
+                self.callback(NO, @"无法添加音频输入对象!");
+            }
+        }
+    }
+    
+    // 初始化设备输出对象
+    _captureMovieFileOutput = [[AVCaptureMovieFileOutput alloc] init];
+    _captureMovieFileOutput.movieFragmentInterval = kCMTimeInvalid;
+    
+    //将设备输出添加到会话中
+    if ([_captureSession canAddOutput:_captureMovieFileOutput]) {
+        AVCaptureConnection *captureConnection = [_captureMovieFileOutput connectionWithMediaType:AVMediaTypeVideo];
+        //防抖功能
+        if ([captureConnection isVideoStabilizationSupported]) {
+            captureConnection.preferredVideoStabilizationMode = AVCaptureVideoStabilizationModeAuto;
+        }
+        [_captureSession addOutput:_captureMovieFileOutput];
+    }
+    
+    //创建视频预览图层
+    _captureVideoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:_captureSession];
+    containerView.layer.masksToBounds = YES;
+    _captureVideoPreviewLayer.frame = containerView.bounds;
+    _captureVideoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
+    _captureVideoPreviewLayer.connection.videoOrientation = AVCaptureVideoOrientationLandscapeRight;
+    [containerView.layer addSublayer:_captureVideoPreviewLayer];
+    
+    // 一定要在添加了 input 和 output之后~
+    AVCaptureConnection *captureConnection = [_captureMovieFileOutput connectionWithMediaType:AVMediaTypeVideo];
+    captureConnection.videoOrientation = AVCaptureVideoOrientationLandscapeRight;
+    
+    [self.captureSession startRunning];
+}
+
+- (void)removeDisplay {
+    [self stopSession];
+    if (_captureVideoPreviewLayer) {
+        [_captureVideoPreviewLayer removeFromSuperlayer];
+    }
+}
+
+- (void)resetSession {
+    if (_ignoreAudio == NO) {
+        [_captureSession beginConfiguration];
+        if (_audioCaptureDeviceInput) {
+            [_captureSession removeInput:_audioCaptureDeviceInput];
+        }
+        
+        NSError *error = nil;
+        // 获取音频输入对象
+        AVCaptureDevice *audioCaptureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
+        _audioCaptureDeviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:audioCaptureDevice error:&error];
+        if (error) {
+            if (self.callback) {
+                self.callback(NO, @"获取音频设备输入出错!");
+            }
+            return;
+        }
+        if ([_captureSession canAddInput:_audioCaptureDeviceInput]) {
+            [_captureSession addInput:_audioCaptureDeviceInput];
+        }
+        else {
+            if (self.callback) {
+                self.callback(NO, @"无法添加音频输入对象");
+            }
+        }
+        
+        [_captureSession commitConfiguration];
+        
+    }
+    [self.captureSession startRunning];
+    
+}
+
+- (void)stopSession {
+    if (_captureSession) {
+        [self.captureSession stopRunning];
+        self.captureSession = nil;
+    }
+}
+
+- (void)startRecord {
+    if (_captureMovieFileOutput) {
+        // 开始录制
+        [self.captureMovieFileOutput startRecordingToOutputFileURL:[NSURL fileURLWithPath:[self getRecordFilePath]] recordingDelegate:self];
+    }
+    
+}
+
+- (void)stopRecord {
+    if (_captureMovieFileOutput) {
+        [self.captureMovieFileOutput stopRecording];
+    }
+    [self resetSession];
+}
+
+
+- (void)removeVideoWithPath:(NSString *)videoUrl {
+    NSFileManager *fileMamager = [NSFileManager defaultManager];
+    if ([fileMamager fileExistsAtPath:videoUrl]) {
+        [fileMamager removeItemAtPath:videoUrl error:nil];
+    }
+}
+
+/**取得指定位置的摄像头*/
+- (AVCaptureDevice *)cameraDeviceWithPosition:(AVCaptureDevicePosition)position {
+    NSArray *cameras = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
+    for (AVCaptureDevice *camera in cameras) {
+        if ([camera position] == position) {
+            return camera;
+        }
+    }
+    return nil;
+}
+// 切换摄像头
+- (void)swapCameras {
+    // Assume the session is already running
+    NSArray *inputs =self.captureSession.inputs;
+    for (AVCaptureDeviceInput *input in inputs ) {
+        AVCaptureDevice *device = input.device;
+        if ( [device hasMediaType:AVMediaTypeVideo] ) {
+            AVCaptureDevicePosition position = device.position;
+            AVCaptureDevice *newCamera =nil;
+            AVCaptureDeviceInput *newInput =nil;
+            
+            if (position ==AVCaptureDevicePositionFront)
+                newCamera = [self cameraDeviceWithPosition:AVCaptureDevicePositionBack];
+            else
+                newCamera = [self cameraDeviceWithPosition:AVCaptureDevicePositionFront];
+            newInput = [AVCaptureDeviceInput deviceInputWithDevice:newCamera error:nil];
+            
+            // beginConfiguration ensures that pending changes are not applied immediately
+            [self.captureSession beginConfiguration];
+            
+            [self.captureSession removeInput:input];
+            [self.captureSession addInput:newInput];
+            
+            // Changes take effect once the outermost commitConfiguration is invoked.
+            [self.captureSession commitConfiguration];
+            break;
+        }
+    }
+}
+
+#pragma mark --------  AVCaptureFileOutputRecordingDelegate  ----------
+- (void)captureOutput:(AVCaptureFileOutput *)captureOutput didStartRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray *)connections {
+    NSLog(@"开始录制");
+    _isRecording = YES;
+}
+
+- (void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray *)connections error:(NSError *)error {
+    if (error) {
+        NSLog(@"error desc :%@", error.description);
+    }
+    NSLog(@"录制结束");
+    _isRecording = NO;
+    [self.captureSession stopRunning];
+    // 暂时存储文件地址
+    self.videoFileURL = outputFileURL;
+   // 保存文件
+    if (_ignoreAudio == NO) {
+        [self saveVideoToAsset:self.videoFileURL];
+    }
+    else {
+        [self addBackgroundMuisc:self.audioUrl];
+    }
+}
+
+// 生成文件 合并音轨
+- (void)addBackgroundMuisc:(NSURL *)audioUrl {
+    AVURLAsset* audioAsset =[AVURLAsset URLAssetWithURL:audioUrl options:nil];
+    
+    CMTime audioDuration = audioAsset.duration;
+    
+    float audioDurationSeconds = CMTimeGetSeconds(audioDuration);
+    
+    NSLog(@"%f",audioDurationSeconds);
+    [KSVideoEditor addBackgroundMiusicWithVideoUrlStr:self.videoFileURL audioUrl:audioUrl start:0 end:audioDurationSeconds isOriginalSound:NO oriVolume:0 newVolume:100 completion:^(NSString * _Nonnull outPath, BOOL isSuccess) {
+        if (isSuccess) {
+            [self saveVideoToAsset:[NSURL fileURLWithPath:outPath]];
+        }
+        else {
+            
+        }
+    }];
+}
+
+
+
+// 保存到相册
+- (void)saveVideoToAsset:(NSURL *)videoUrl {
+    [MBProgressHUD ksShowHUDWithText:@"视频处理中..."];
+    [[TZImageManager manager] saveVideoWithUrl:videoUrl completion:^(PHAsset *asset, NSError *error) {
+        if (!error) {
+            self.videoAsset = asset;
+            dispatch_main_async_safe(^{
+                [MBProgressHUD ksHideHUD];
+                if (self.callback) {
+                    self.callback(YES, @"保存成功");
+                }
+                
+                // 删除文件
+                [self removeVideoWithPath:self.videoFileURL.path];
+                [self removeVideoWithPath:videoUrl.path];
+                // 重置
+                [self resetSession];
+                
+            });
+        }
+        else {
+            dispatch_main_async_safe(^{
+                [MBProgressHUD ksHideHUD];
+                if (self.callback) {
+                    self.callback(NO, @"保存视频错误");
+                }
+                // 删除文件
+                [self removeVideoWithPath:self.videoFileURL.path];
+                [self removeVideoWithPath:videoUrl.path];
+                // 重置
+                [self resetSession];
+                
+            });
+        }
+    }];
+}
+
+// 上传视频
+- (void)uploadRecordVideoSuccess:(void (^)(NSString * _Nonnull))success failure:(void (^)(NSString * _Nonnull))faliure {
+    if (self.videoAsset) {
+        dispatch_main_async_safe(^{
+            [MBProgressHUD ksShowHUDWithText:@"视频导出中..."];
+        });
+        [[TZImageManager manager] getVideoOutputPathWithAsset:self.videoAsset presetName:self.presentName success:^(NSString *outputPath) {
+            dispatch_main_async_safe(^{
+                [MBProgressHUD ksHideHUD];
+            });
+            NSLog(@"视频导出到本地完成,沙盒路径为:%@",outputPath);
+            NSData *outputData = [NSData dataWithContentsOfURL:[NSURL fileURLWithPath:outputPath]]; //压缩后的视频
+            NSLog(@"导出后的视频:%@",[NSString stringWithFormat:@"%.2fM",(CGFloat)outputData.length/(1024*1024)]);
+            // 上传
+            dispatch_main_async_safe(^{
+                [self sendVideoActionWith:outputPath success:success failure:faliure];
+            });
+            
+        } failure:^(NSString *errorMessage, NSError *error) {
+            dispatch_main_async_safe(^{
+                [MBProgressHUD ksHideHUD];
+                faliure(@"视频导出失败");
+            });
+            NSLog(@"视频导出失败:%@,error:%@",errorMessage, error);
+            
+        }];
+    }
+    else {
+        faliure(@"未找到视频资源");
+    }
+}
+
+
+- (void)sendVideoActionWith:(NSString *)fileUrl success:(void (^)(NSString * _Nonnull))success failure:(void (^)(NSString * _Nonnull))faliure {
+    [self hudTipWillShow:YES];
+    NSData *fileData = [NSData dataWithContentsOfURL:[NSURL fileURLWithPath:fileUrl]];
+    NSString *suffix = [NSString stringWithFormat:@".%@",[fileUrl pathExtension]];
+    [[KSUploadManager shareInstance] videoUpload:fileData fileName:@"video" fileSuffix:suffix progress:^(int64_t bytesWritten, int64_t totalBytes) {
+        // 显示进度
+        if (self.HUD) {
+            self.HUD.progress = bytesWritten / totalBytes;// progress是回调进度
+        }
+    } successCallback:^(NSMutableArray * _Nonnull fileUrlArray) {
+        [self hudTipWillShow:NO];
+        NSString *fileUrl = [fileUrlArray lastObject];
+        success(fileUrl);
+        
+    } faliure:^(NSError * _Nullable error, NSString * _Nullable descMessaeg) {
+        [self hudTipWillShow:NO];
+        faliure(descMessaeg);
+    }];
+}
+
+- (void)hudTipWillShow:(BOOL)willShow{
+    if (willShow) {
+        
+        UIWindow *keyWindow = [NSObject getKeyWindow];
+        if (!_HUD) {
+            _HUD = [MBProgressHUD showHUDAddedTo:keyWindow animated:YES];
+            _HUD.label.textColor = [UIColor whiteColor];
+            _HUD.mode = MBProgressHUDModeDeterminateHorizontalBar;
+            _HUD.label.text = @"正在上传视频...";
+            _HUD.contentColor = [UIColor whiteColor];
+            _HUD.removeFromSuperViewOnHide = YES;
+            _HUD.bezelView.style = MBProgressHUDBackgroundStyleSolidColor;
+            _HUD.bezelView.backgroundColor = [UIColor colorWithHexString:@"#000000" alpha:0.8];
+        }else{
+            _HUD.progress = 0;
+            [keyWindow addSubview:_HUD];
+            [_HUD showAnimated:YES];
+        }
+    }else{
+        [_HUD hideAnimated:YES];
+    }
+}
+
+
+#pragma mark ------ 设置录制地址
+- (NSString *)getRecordFilePath {
+    NSString *path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)lastObject] stringByAppendingPathComponent:@"AccompanyVideoData"];
+    NSFileManager *fileManager = [NSFileManager defaultManager];
+    BOOL isDir = FALSE;
+    BOOL isDirExist = [fileManager fileExistsAtPath:path isDirectory:&isDir];
+    if(!(isDirExist && isDir)) {
+        BOOL bCreateDir = [fileManager createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
+        if(!bCreateDir){
+            NSLog(@"创建文件夹失败!");
+        }
+        NSLog(@"创建文件夹成功,文件路径%@",path);
+    }
+    NSString *songName = @"recordSong";
+    NSString *fileName = [NSString stringWithFormat:@"%@.mp4",songName];
+    NSString *filePath = [path stringByAppendingPathComponent:fileName];
+    return filePath;
+}
+@end

+ 1 - 0
KulexiuForStudent/KulexiuForStudent/Common/Define/Common.h

@@ -17,6 +17,7 @@
 #import "NSString+Extension.h"
 #import <UIImageView+WebCache.h>
 #import "MSSBrowseDefine.h"
+#import "NSObject+KeyWindow.h"
 
 #define FONT_COLOR (HexRGB(0x333333))
 

+ 19 - 0
KulexiuForStudent/KulexiuForStudent/Common/Tools/NSObject+KeyWindow.h

@@ -0,0 +1,19 @@
+//
+//  NSObject+KeyWindow.h
+//  StudentDaya
+//
+//  Created by 王智 on 2022/5/17.
+//  Copyright © 2022 DayaMusic. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface NSObject (KeyWindow)
+
++ (UIWindow *)getKeyWindow;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 24 - 0
KulexiuForStudent/KulexiuForStudent/Common/Tools/NSObject+KeyWindow.m

@@ -0,0 +1,24 @@
+//
+//  NSObject+KeyWindow.m
+//  StudentDaya
+//
+//  Created by 王智 on 2022/5/17.
+//  Copyright © 2022 DayaMusic. All rights reserved.
+//
+
+#import "NSObject+KeyWindow.h"
+
+@implementation NSObject (KeyWindow)
+
++ (UIWindow *)getKeyWindow {
+    UIWindow *applicationWindow;
+    if ([[[UIApplication sharedApplication] delegate] respondsToSelector:@selector(window)]) {
+        applicationWindow = [[[UIApplication sharedApplication] delegate] window];
+    }
+    else {
+        applicationWindow = [[UIApplication sharedApplication] keyWindow];
+    }
+    return applicationWindow;
+}
+
+@end

+ 4 - 1
KulexiuForStudent/KulexiuForStudent/Module/Chat/Controller/KSChatListViewController.m

@@ -11,6 +11,8 @@
 #import "KSSearchHistoryMessageController.h"
 #import "KSChatConversationViewController.h"
 #import "KSTabBarViewController.h"
+#import "KSChatListCell.h"
+
 @interface KSChatListViewController ()
 
 @property (nonatomic, strong) KSChatListSearchView *searchBar;
@@ -172,12 +174,13 @@
     upperRowAction.backgroundColor = THEMECOLOR;
     return @[deleteRowAction,upperRowAction];
 }
+
 //- (RCConversationBaseCell *)rcConversationListTableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
 //    RCConversationModel *model = self.conversationListDataSource[indexPath.row];
 //    if (model.conversationType == ConversationType_GROUP) {
 //        
 //    }
-//    
+//    return nil;
 //}
 
 - (UIView *)stateView {

+ 16 - 0
KulexiuForStudent/KulexiuForStudent/Module/Chat/View/KSChatListCell.h

@@ -0,0 +1,16 @@
+//
+//  KSChatListCell.h
+//  KulexiuForStudent
+//
+//  Created by 王智 on 2022/5/17.
+//
+
+#import <RongIMKit/RongIMKit.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface KSChatListCell : RCConversationBaseCell
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 23 - 0
KulexiuForStudent/KulexiuForStudent/Module/Chat/View/KSChatListCell.m

@@ -0,0 +1,23 @@
+//
+//  KSChatListCell.m
+//  KulexiuForStudent
+//
+//  Created by 王智 on 2022/5/17.
+//
+
+#import "KSChatListCell.h"
+
+@implementation KSChatListCell
+
+- (void)awakeFromNib {
+    [super awakeFromNib];
+    // Initialization code
+}
+
+- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
+    [super setSelected:selected animated:animated];
+
+    // Configure the view for the selected state
+}
+
+@end

+ 76 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/BASSMidiPlayer/BassMidiEngine.h

@@ -0,0 +1,76 @@
+//
+//  BassMidiEngine.h
+//  StudentDaya
+//
+//  Created by Kyle on 2022/3/8.
+//  Copyright © 2022 DayaMusic. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@protocol BassMidiEngineDelegate <NSObject>
+
+/// 加载成功回调
+- (void)initEngineSuccess:(float)totalTime;
+
+/*    范围:[0,1],每10ms更新一次   */
+- (void)progressUpdated:(float)progress currentTime:(float)currentTime;
+
+/* 播放结束回调 */
+- (void)midiplayEnd;
+
+
+- (void)getMusicTotalTime:(float)totalTime;
+
+@end
+
+
+@interface BassMidiEngine : NSObject
+
+// 定义汇报进度频率 默认10ms
+@property (nonatomic, assign) NSInteger reportTime;
+// 每个track 对应的名称
+@property (nonatomic, strong) NSMutableArray *instrumentTrackNameArray;
+
+// 播放状态
+@property (nonatomic, assign) BOOL isPlaying;
+
+/* 选段播放控制 */
+
+// 选段的开始时间
+@property (nonatomic, assign) float startTime;
+// 选段的结束时间
+@property (nonatomic, assign) float endTime;
+
+/// 播放器回调delegate
+@property (nonatomic,weak)id<BassMidiEngineDelegate> delegate;
+
+
+- (void)configSoundFilePath:(NSString *)filePath;
+
+/*  加载MIDI文件  */
+- (BOOL)loadMIDIFileWithString:(NSString *)filePath;
+
+/* 播放器当前状态 */
+- (BOOL)isPlayingFile;
+/// 开始播放
+- (void)startPlay;
+/// 停止播放
+- (void)stopPlay;
+
+
+/// 释放引擎
+- (void)freeEngine;
+
+#pragma mark ------ 变调 调整hz
+// 转成442HZ播放
+- (void)transformTo442Hz;
+
+// hz调整 440 - 442。。。。。。。
+- (void)adjustPitchByHZ:(float)newHZ;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 159 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/BASSMidiPlayer/BassMidiEngine.m

@@ -0,0 +1,159 @@
+//
+//  BassMidiEngine.m
+//  StudentDaya
+//
+//  Created by Kyle on 2022/3/8.
+//  Copyright © 2022 DayaMusic. All rights reserved.
+//
+
+#import "BassMidiEngine.h"
+#import "bass.h"
+#import "bassmidi.h"
+#import <AVFoundation/AVFoundation.h>
+@interface BassMidiEngine ()
+{
+    HSTREAM _stream;
+}
+@property (nonatomic, strong) NSString *soundFileUrl;
+
+@property (nonatomic, strong) NSString *midFilePath;
+
+
+
+@end
+
+@implementation BassMidiEngine
+
+- (instancetype)init {
+    self = [super init];
+    if (self) {
+        [self configDefault];
+    }
+    return self;
+}
+
+- (void)configDefault {
+    self.soundFileUrl = [[NSBundle mainBundle]
+                         pathForResource:@"synthgms" ofType:@"sf2"];
+    [self setupAudioSession];
+}
+
+- (void)setupAudioSession {
+    
+    AVAudioSession *audioSession = [AVAudioSession sharedInstance];
+    NSError *err = nil;
+    AVAudioSessionCategory category = AVAudioSessionCategoryPlayAndRecord;
+    if (@available(iOS 10.0, *)) {
+        [audioSession setCategory:category mode:AVAudioSessionModeDefault options:AVAudioSessionCategoryOptionAllowBluetooth|AVAudioSessionCategoryOptionAllowBluetoothA2DP|AVAudioSessionCategoryOptionDefaultToSpeaker|AVAudioSessionCategoryOptionMixWithOthers error:&err];
+    }
+    else {
+        [audioSession setCategory:category withOptions:AVAudioSessionCategoryOptionAllowBluetooth|AVAudioSessionCategoryOptionDefaultToSpeaker|AVAudioSessionCategoryOptionMixWithOthers error:&err];
+    }
+    [audioSession setActive:YES error:&err];
+}
+
+- (void)configSoundFilePath:(NSString *)filePath {
+    self.soundFileUrl = filePath;
+}
+
+- (BOOL)setupEngineWithFile:(NSString *)filePath {
+    //BASS init
+    BASS_Init(-1, 44100, 0, 0, NULL);
+    _stream = BASS_MIDI_StreamCreateFile(FALSE, [filePath UTF8String], 0, 0, 0, 44100);
+    if (BASS_ErrorGetCode()) {
+        NSLog(@"Bass error: %i", BASS_ErrorGetCode());
+        return NO;
+    }
+    // set font
+    BASS_MIDI_FONT font = [self getBassMidiFont];
+    BASS_MIDI_StreamSetFonts(_stream, &font, 1);
+    
+    return YES;
+}
+
+- (BASS_MIDI_FONT)getBassMidiFont {
+    BASS_MIDI_FONT midiFont;
+    midiFont.font = BASS_MIDI_FontInit([self.soundFileUrl UTF8String], 0);
+    midiFont.preset = -1;
+    midiFont.bank = 0;
+    return midiFont;
+}
+
+- (BOOL)loadMIDIFileWithString:(NSString *)filePath {
+    // 判断文件是否存在
+    BOOL isExist = [[NSFileManager defaultManager] fileExistsAtPath:filePath];
+    if (isExist) {
+        return [self setupEngineWithFile:filePath];
+    }
+    else {
+        NSLog(@"文件不存在 !!!");
+        return NO;
+    }
+}
+
+
+- (BOOL)isPlayingFile {
+    if (_stream == 0) {
+        return NO;
+    }
+    //    0 - BASS_ACTIVE_STOPPED   The channel is not active, or handle is not a valid channel.
+    //    1 - BASS_ACTIVE_PLAYING   The channel is playing (or recording).
+    //    2 - BASS_ACTIVE_PAUSED    The channel is paused.
+    //    3 - BASS_ACTIVE_STALLED   Playback of the stream has been stalled due to a lack of sample data.
+    //                              The playback will automatically resume once there is sufficient data to do so.
+    int status = BASS_ChannelIsActive(_stream);
+    switch (status) {
+        case BASS_ACTIVE_STOPPED: // 停止
+        {
+            
+        }
+            break;
+        case BASS_ACTIVE_PLAYING: // 播放中
+        {
+            
+        }
+            break;
+        case BASS_ACTIVE_PAUSED:
+        case BASS_ACTIVE_STALLED:
+        {
+            
+        }
+            break;
+        default:
+            break;
+    }
+    if (status == 1) {
+        return YES;
+    }
+    else {
+        return NO;
+    }
+}
+
+- (void)startPlay {
+    if (_stream == 0) {
+        // 未初始化stream
+        return;
+    }
+    if (self.isPlaying) {
+        [self stopPlay];
+    }
+    
+    BASS_ChannelPlay(_stream, 0);
+    self.isPlaying = YES;
+    NSLog(@"testMidiFile complete");
+}
+
+- (void)stopPlay {
+    BASS_ChannelStop(_stream);
+    self.isPlaying = NO;
+}
+
+/// 释放引擎
+- (void)freeEngine {
+    if (_stream) {
+        
+    }
+}
+
+@end

+ 20 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/Controller/KSCloudViewController.h

@@ -0,0 +1,20 @@
+//
+//  KSCloudViewController.h
+//  StudentDaya
+//
+//  Created by Kyle on 2021/12/8.
+//  Copyright © 2021 DayaMusic. All rights reserved.
+//
+
+#import "BaseViewController.h"
+#import "CloudSongMessageModel.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface KSCloudViewController : BaseViewController
+
+- (void)configMessage:(CloudSongMessageModel *)songMessage;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 2215 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/Controller/KSCloudViewController.m

@@ -0,0 +1,2215 @@
+//
+//  KSCloudViewController.m
+//  StudentDaya
+//
+//  Created by Kyle on 2021/12/8.
+//  Copyright © 2021 DayaMusic. All rights reserved.
+//
+
+#import "KSCloudViewController.h"
+#import "UIDevice+TFDevice.h"
+#import "AppDelegate.h"
+#import "StaffImageDisplayView.h"
+#import "KYSourceParseManager.h"
+#import "MidiPlayerEngine.h"
+#import "CloudControlButton.h"
+#import "KSCloudSettingView.h"
+#import "KSXMLInfoParse.h"
+#import "ScoreAnimationView.h"
+#import "EvaluateResultAlert.h"
+#import "NoWiredTipsAlert.h"
+#import "LFPopupMenu.h"
+#import "KSSliderView.h"
+#import "SoundCheckView.h"
+#import "KSBaseWKWebViewController.h"
+#import <SSZipArchive/SSZipArchive.h>
+#import "CloudHelpView.h"
+#import "CloudFeedbackView.h"
+#import "KSAudioSessionManager.h"
+#import "KSCloudBeatView.h"
+
+#import "RecordCheckManager.h"
+#import "KSAQRecordManager.h"
+#import "KSWebSocketManager.h"
+#import "KSVideoRecordManager.h"
+#import "TrackChooseView.h"
+
+#define PositionRate (12)    // 图片上点的位置和位置文件的系数
+
+#define Left_Space (37)
+#define Button_Width (34)
+#define Button_Height (60)
+#define Button_Space (20)
+
+#define BACKVIEW_ALPHA (0.6f)
+typedef NS_ENUM(NSInteger, BUTTON_TAG) {
+    BUTTON_TAG_EVALUATING = 1000, // 评测
+    BUTTON_TAG_SELECT,  // 选段
+    BUTTON_TAG_PLAY,    // 播放
+    BUTTON_TAG_SWITCH,  // 切换
+    BUTTON_TAG_REPEAT,  // 重播
+    BUTTON_TAG_SPEED,   // 调速
+    BUTTON_TAG_SETTING, // 设置
+};
+
+typedef NS_ENUM(NSInteger,COLOR_DISPLAYTYPE) {
+    COLOR_DISPLAYTYPE_NOMAL,
+    COLOR_DISPLAYTYPE_PROTECT,
+};
+
+@interface KSCloudViewController ()<PlayerEngineDelegate,KSAQRecordManagerDelegate,KSSliderDelegate,KSAudioSessionManagerDelegate>
+
+@property (nonatomic, strong) KSAudioSessionManager *audioSessionManager;
+
+@property (nonatomic, assign) MetronomeType beatType;
+
+@property (nonatomic, assign) BOOL keepOrientation;
+
+@property (nonatomic, strong) UIView *backView;
+
+@property (nonatomic, strong) UIView *bgColorView; // 容器颜色视图
+
+@property (nonatomic, assign) COLOR_DISPLAYTYPE bgColorType; // 容器颜色类型
+
+@property (nonatomic, strong) UIScrollView *displayScrollView;
+
+@property (nonatomic, strong) UIButton *helpButton; // 帮助按钮
+
+@property (nonatomic, strong) KSSliderView *sliderView;
+
+@property (nonatomic, assign) BOOL scorllEnable;  // 是否能滑动
+
+@property (nonatomic, strong) NSMutableArray *pageArray;
+
+@property (nonatomic, strong) KYSourceParseManager *manager;
+
+@property (nonatomic, assign) CGFloat scaleRate;
+
+@property (nonatomic, assign) CGFloat pageViewHeight;
+
+@property (nonatomic, assign) BOOL isChooseMeasure; // 是否小节选择模式
+
+@property (nonatomic, assign) NSInteger startMeasure;
+
+@property (nonatomic, assign) NSInteger endMeasure;
+
+@property (nonatomic, strong) UIView *cursorView;  // 光标
+
+@property (nonatomic, strong) UIButton *evaluateButton; // 评测按钮
+
+@property (nonatomic, assign) BOOL isEvaluating;  // 是否开启评测模式
+
+@property (nonatomic, strong) MidiPlayerEngine *playerEngine; // player
+
+@property (nonatomic, strong) UIView *headView;
+
+@property (nonatomic, assign) NSInteger lastPlayNodeIndex; // 上次播放的音符序号
+
+@property (nonatomic, strong) NSString *songPath; // 音频文件路径
+
+@property (nonatomic, strong) NSString *songName;  // 曲目名称
+
+@property (nonatomic, assign) BOOL isPlaying; // 是否正在播放的状态
+
+@property (nonatomic, assign) BOOL isNeedRepeat; // 是否需要重复
+
+@property (nonatomic, assign) BOOL soundCheckOn; // 是否校音
+
+@property (nonatomic, assign) BOOL showFinger;   // 显示指法开关
+
+@property (nonatomic, assign) BOOL needShowFinger;   // 是否需要显示指法
+
+@property (nonatomic, assign) BOOL cameraOn;     // 是否开启摄像头
+
+@property (nonatomic, assign) BOOL eyeShieldOn;   // 护眼模式是否开启
+
+@property (nonatomic, assign) BOOL saveVideo;     // 是否保存视频
+
+@property (nonatomic, strong) NSString *level;   // 评测级别
+
+@property (nonatomic, assign) BOOL accompanyOn;   // 是否开启伴奏
+
+@property (nonatomic, assign) NSInteger currentSpeed; // 当前速度
+// 是否发送了musicXML信息
+@property (nonatomic, assign) BOOL hasSendStartMessage;
+
+@property (nonatomic, strong) KSAQRecordManager *AQManager;
+
+@property (nonatomic, strong) KSWebSocketManager *socketManager;
+
+@property (nonatomic, strong) NSMutableDictionary *evaluatParm;
+
+// 评测开始时发送recordStart消息的标识
+@property (nonatomic, assign) BOOL isCompareStart;
+// 校音开始时发送 checkStart消息标识
+@property (nonatomic, assign) BOOL isSoundCheckStart;
+
+// 自定义UI控件容器
+@property (nonatomic, strong) UIView *viewContainer;
+
+@property (nonatomic, strong) KSVideoRecordManager *videoRecordManager;
+
+@property (nonatomic, strong) NSMutableArray *nodeArray;
+
+@property (nonatomic, assign) NSInteger recordId;
+
+@property (nonatomic, strong) EvaluateResultAlert *resultAlert;
+
+@property (nonatomic, strong) NoWiredTipsAlert *wiredAlert;
+
+@property (nonatomic, assign) NSInteger checkPitch;
+
+@property (nonatomic, assign) long long correctTimeInterval;
+
+@property (nonatomic, strong) SoundCheckView *checkView;
+
+@property (nonatomic, strong) CloudSongMessageModel *songMessageSource;
+
+@property (nonatomic, assign) BOOL isVertical;
+
+@property (nonatomic, strong) NSDictionary *fingerImageDic;
+
+@property (nonatomic, strong) NSString *fullDicImage;
+
+@property (nonatomic, strong) UIView *fingerDisplayView;
+
+@property (nonatomic, strong) UIView *fingerContentView;
+
+@property (nonatomic, assign) CGRect defaultFrame;
+
+@property (nonatomic, assign) CGRect resizeFrame;
+
+@property (nonatomic, strong) NSMutableArray *zipFileArray;
+
+@property (nonatomic, strong) CloudFeedbackView *feedbackView;
+
+@property (nonatomic, strong) TrackChooseView *trackChooseView;
+
+@end
+
+@implementation KSCloudViewController
+
+- (NSString *)getSaveZipPath {
+    //  在Documents目录下创建一个名为AudioSpeedFile的文件夹
+    NSString *path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)lastObject] stringByAppendingPathComponent:@"AudioZipFile"];
+    NSLog(@"%@",path);
+    
+    NSFileManager *fileManager = [NSFileManager defaultManager];
+    BOOL isDir = FALSE;
+    BOOL isDirExist = [fileManager fileExistsAtPath:path isDirectory:&isDir];
+    if(!(isDirExist && isDir)) {
+        BOOL bCreateDir = [fileManager createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
+        if(!bCreateDir){
+            NSLog(@"创建文件夹失败!");
+        }
+        NSLog(@"创建文件夹成功,文件路径%@",path);
+    }
+    
+    NSLog(@"file path:%@",path);
+    return path;
+}
+
+
+#pragma mark ----- life cycle
+- (void)viewDidLoad {
+    [super viewDidLoad];
+    // Do any additional setup after loading the view.
+    self.ks_prefersNavigationBarHidden = YES;
+    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appEnterBackground) name:@"appEnterBackground" object:nil];
+    self.view.backgroundColor = UIColor.whiteColor;
+    [self displayBaseConfig];
+}
+
+- (void)viewWillAppear:(BOOL)animated {
+    [super viewWillAppear:animated];
+    [self connectSocketService];
+    // 切换到横屏
+    [self changeOrientation:YES];
+    [[UIApplication sharedApplication] setIdleTimerDisabled:YES];
+}
+
+- (void)viewDidAppear:(BOOL)animated {
+    [super viewDidAppear:animated];
+    if (self.songMessageSource != nil) {
+        NSString *filePath = [self getSaveZipPath];
+        NSString *floderName = [[[[self.songMessageSource.zipUrl componentsSeparatedByString:@"/"] lastObject] componentsSeparatedByString:@"."] firstObject];
+        filePath = [NSString stringWithFormat:@"%@/%@",filePath,floderName];
+        NSError *error = nil;
+        NSArray *folderContents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:filePath error:&error];
+        if (folderContents.count == 0) {
+            [self downloadZipFileWithFloderName:floderName];
+        }
+        else {
+            [self configUnZipFilePathWithFloderName:floderName];
+        }
+    }
+}
+
+- (void)viewWillDisappear:(BOOL)animated {
+    [super viewWillDisappear:animated];
+    [[UIApplication sharedApplication] setIdleTimerDisabled:NO];
+    if (_AQManager) {
+        [_AQManager freeAudioQueue];
+        _AQManager = nil;
+    }
+    if (_socketManager) {
+        [self.socketManager SRWebSocketClose];
+        _socketManager = nil;
+    }
+    [self stopSession];
+
+    if (self.keepOrientation == NO) {
+        [self changeOrientation:NO];
+        // 如果退出评测页面 清除 playerEngine
+        if (self.playerEngine) {
+            [self.playerEngine cleanup];
+            self.playerEngine = nil;
+        }
+    }
+    self.keepOrientation = NO;
+}
+
+- (void)changeOrientation:(BOOL)isLandScape {
+    if (isLandScape) {
+        // 切换到横屏
+        AppDelegate* delegate = (AppDelegate*)[UIApplication sharedApplication].delegate;
+        delegate.allowAutoRotate = YES;
+        [UIDevice switchNewOrientation:UIInterfaceOrientationLandscapeRight];
+    }
+    else {
+        AppDelegate* delegate = (AppDelegate*)[UIApplication sharedApplication].delegate;
+        delegate.allowAutoRotate = NO;
+        [UIDevice switchNewOrientation:UIInterfaceOrientationPortrait];
+    }
+}
+
+
+#pragma mark ---- 获取和配置曲目相关参数
+- (void)configMessage:(CloudSongMessageModel *)songMessage {
+    self.songMessageSource = songMessage;
+    self.songName = songMessage.examSongName;
+}
+
+- (void)configUnZipFilePathWithFloderName:(NSString *)name {
+    self.zipFileArray = [NSMutableArray array];
+    NSString *filePath = [self getSaveZipPath];
+    filePath = [NSString stringWithFormat:@"%@/%@",filePath,name];
+    NSError *error = nil;
+    NSArray *folderContents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:filePath error:&error];
+    for (NSString *fileName in folderContents) {
+        NSString *path = [NSString stringWithFormat:@"%@/%@", filePath, fileName];
+        [self.zipFileArray addObject:path];
+    }
+    NSLog(@"------%@",name);
+    [self querySourceMessage];
+}
+
+- (void)downloadZipFileWithFloderName:(NSString *)name {
+    
+    [self showhud];
+    [KSNetworkingManager downloadFileRequestWithFileUrl:self.songMessageSource.zipUrl progress:^(int64_t bytesRead, int64_t totalBytes) {
+        
+    } success:^(NSURL * _Nonnull fileUrl) {
+
+        // 解压zip包
+        NSString *filePath = [self getSaveZipPath];
+        BOOL success = [SSZipArchive unzipFileAtPath:fileUrl.path toDestination:filePath];
+        NSLog(@"%d", success);
+        if (success) {
+            [self removehub];
+            [self configUnZipFilePathWithFloderName:name];
+        }
+        else {
+            [self removehub];
+            [self MBPShow:@"资源解压失败"];
+        }
+    } faliure:^(NSError * _Nonnull error) {
+        [self removehub];
+        [self MBPShow:@"下载配置资源失败"];
+    }];
+}
+
+- (void)displayBaseConfig {
+    self.beatType = MetronomeType4V4;
+    NSString *savePath = [CloudSongMessageModel getSaveSpeedPath];
+    NSDictionary *speedDic = [NSMutableDictionary dictionaryWithContentsOfFile:savePath];
+    NSInteger speed = [speedDic integerValueForKey:self.songMessageSource.examSongId];
+    if (speed != 0) {
+        self.currentSpeed = speed;
+    }
+    [self queryFingerConfig];
+    [self queryCloudConfig];
+}
+
+- (void)queryFingerConfig {
+    
+    NSDictionary *fingerSubjectDic = [NSMutableDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"SubjectFinger" ofType:@"plist"]];
+    
+    NSDictionary *imageDict = [NSMutableDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"FingerList" ofType:@"plist"]];
+    // 根据当前声部获取对应的音高对应图片
+    NSString *subjectId = self.songMessageSource.subjectId;
+    
+    NSDictionary *subjectDic = [fingerSubjectDic dictionaryValueForKey:subjectId];
+    if (subjectDic) {
+        NSString *subjectName = [subjectDic stringValueForKey:@"name"];
+        BOOL isVertical = [subjectDic boolValueForKey:@"vertical"];
+        self.isVertical = isVertical;
+        
+        NSDictionary *fingerSource = [imageDict dictionaryValueForKey:subjectName];
+        self.fingerImageDic = [NSDictionary dictionaryWithDictionary:fingerSource];
+        
+        NSString *fullImageName = [NSString stringWithFormat:@"%@full",subjectName];
+        UIImage *bgImage = [UIImage imageNamed:fullImageName];
+        
+        CGRect frame = CGRectZero;
+        CGFloat width = bgImage.size.width;
+        CGFloat height = bgImage.size.height;
+        if (isVertical) {
+            CGFloat contentHeight = CGRectGetHeight(self.defaultFrame);
+            if (height > contentHeight) {
+                width = contentHeight / height * width;
+                height = contentHeight;
+            }
+            frame = CGRectMake(CGRectGetWidth(self.defaultFrame) - width, (CGRectGetHeight(self.defaultFrame) - height) / 2.0f, width, height);
+            self.resizeFrame = CGRectMake(0, 0, CGRectGetWidth(self.defaultFrame)-width, CGRectGetHeight(self.defaultFrame));
+        }
+        else {
+            CGFloat contentWidth = CGRectGetWidth(self.defaultFrame);
+            if (width > contentWidth) {
+                height = width / contentWidth * height;
+                width = contentWidth;
+            }
+            
+            frame = CGRectMake((CGRectGetWidth(self.defaultFrame) - width) / 2.0f, CGRectGetHeight(self.defaultFrame) - height, width, height);
+            
+            self.resizeFrame = CGRectMake(0, 0, CGRectGetWidth(self.defaultFrame), CGRectGetHeight(self.defaultFrame) - height);
+        }
+        self.fingerDisplayView = [[UIView alloc] initWithFrame:frame];
+        self.fingerDisplayView.backgroundColor = [UIColor clearColor];
+        UIImageView *bgImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, width, height)];
+        [bgImageView setImage:bgImage];
+        [self.fingerDisplayView addSubview:bgImageView];
+        
+        self.fingerContentView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, width, height)];
+        self.fingerContentView.backgroundColor = [UIColor clearColor];
+        [self.fingerDisplayView addSubview:self.fingerContentView];
+    }
+}
+
+- (void)setShowFinger:(BOOL)showFinger {
+    _showFinger = showFinger;
+    if (showFinger && self.fingerImageDic != nil) {
+        self.needShowFinger = YES;
+    }
+    else {
+        self.needShowFinger = NO;
+    }
+}
+
+- (void)queryCloudConfig {
+    NSDictionary *config = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"cloudConfig"];
+    if (config == nil || config.allKeys.count == 0) {
+        NSMutableDictionary *newConfig = [NSMutableDictionary dictionary];
+        [newConfig setValue:@(NO) forKey:@"eyeshield"];
+        [newConfig setValue:@(NO) forKey:@"soundCheck"];
+        [newConfig setValue:@(NO) forKey:@"cameraOn"];
+        [newConfig setValue:@(YES) forKey:@"repeatOn"];
+        [newConfig setValue:@(YES) forKey:@"showFinger"];
+        [newConfig setValue:@"ADVANCED" forKey:@"level"];
+        [newConfig setValue:@(YES) forKey:@"accompanyOn"];
+        [newConfig setValue:@(NO) forKey:@"save"];
+        [newConfig setValue:self.songMessageSource.behaviorId forKey:@"behaviorId"];
+        [newConfig setValue:@"" forKey:@"tips-status"];
+        [[NSUserDefaults standardUserDefaults] setObject:newConfig forKey:@"cloudConfig"];
+        [[NSUserDefaults standardUserDefaults] synchronize];
+        config = [NSDictionary dictionaryWithDictionary:newConfig];
+    }
+    
+    self.eyeShieldOn = [config boolValueForKey:@"eyeshield"];
+    self.soundCheckOn = [config boolValueForKey:@"soundCheck"];
+    self.cameraOn = [config boolValueForKey:@"cameraOn"];
+    self.isNeedRepeat = [config boolValueForKey:@"repeatOn"];
+    self.showFinger = [config boolValueForKey:@"showFinger"];
+    self.level = [config stringValueForKey:@"level"];
+    self.saveVideo = [config boolValueForKey:@"save"];
+    self.accompanyOn = [config boolValueForKey:@"accompanyOn"];
+    
+    NSMutableDictionary *saveConfig = [NSMutableDictionary dictionaryWithDictionary:config];
+    [saveConfig setValue:self.songMessageSource.behaviorId forKey:@"behaviorId"];
+    [[NSUserDefaults standardUserDefaults] setObject:saveConfig forKey:@"cloudConfig"];
+    [[NSUserDefaults standardUserDefaults] synchronize];
+    
+}
+
+- (void)querySourceMessage {
+    
+    dispatch_async(dispatch_queue_create("CloudSource", DISPATCH_QUEUE_SERIAL), ^{
+        // 未选择小节设置为-1;
+        self.startMeasure = -1;
+        self.endMeasure = -1;
+        NSString *measurePath = @"";
+        NSString *nodeSourePath = @"";
+        NSString *xmlFile = @"";
+        NSString *songPath = @"";
+        for (NSString *filePath in self.zipFileArray) {
+            if ([filePath containsString:@".mpos"]) {
+                measurePath = filePath;
+            }
+            else if ([filePath containsString:@".spos"]) {
+                nodeSourePath = filePath;
+            }
+            else if ([filePath containsString:@".musicxml"] || [filePath containsString:@".xml"]) {
+                xmlFile = filePath;
+            }
+            else if ([filePath containsString:@".mid"]) {
+                songPath = filePath;
+                self.songPath = songPath;
+            }
+        }
+        
+//        measurePath = [[NSBundle mainBundle] pathForResource:@"mus" ofType:@"mpos"];
+//        nodeSourePath = [[NSBundle mainBundle] pathForResource:@"mus" ofType:@"spos"];
+//        xmlFile = [[NSBundle mainBundle] pathForResource:@"Danza_Del_Fuego" ofType:@"xml"];
+//        songPath = [[NSBundle mainBundle] pathForResource:@"Danza_Del_Fuego" ofType:@"mid"];
+        
+        NSDictionary *xmlParm = [KSXMLInfoParse getInfoFromXMLMusicFile:xmlFile];
+        self.nodeArray = [NSMutableArray arrayWithArray:[xmlParm arrayValueForKey:@"noteInfo"]];
+        self.beatType = [self getBeatTypeFromString:[xmlParm stringValueForKey:@"beatType"]];
+        self.manager = [[KYSourceParseManager alloc] init];
+        [self.manager parseMessageSource:measurePath nodeSource:nodeSourePath];
+        
+        [self configAudioSession];
+        // 预加载mid文件
+        [self configPlayerEngineWithSong:songPath];
+        dispatch_main_async_safe(^{
+            [self displayView];
+        });
+        
+    });
+}
+
+- (MetronomeType)getBeatTypeFromString:(NSString *)typeString {
+    if ([typeString isEqualToString:@"1/4"]) {
+        return MetronomeType1V4;
+    }
+    else if ([typeString isEqualToString:@"2/4"]) {
+        return MetronomeType2V4;
+    }
+    else if ([typeString isEqualToString:@"3/4"]) {
+        return MetronomeType3V4;
+    }
+    else if ([typeString isEqualToString:@"4/4"]) {
+        return MetronomeType4V4;
+    }
+    else if ([typeString isEqualToString:@"3/8"]) {
+        return MetronomeType3V8;
+    }
+    else if ([typeString isEqualToString:@"6/8"]) {
+        return MetronomeType6V8;
+    }
+    else {
+        return MetronomeType4V4;
+    }
+}
+
+- (void)configAudioSession {
+    self.audioSessionManager = [[KSAudioSessionManager alloc] init];
+    self.audioSessionManager.delegate = self;
+    [self.audioSessionManager configAudioSession:AUDIOCONFIG_PLAYANDRECORD];
+}
+
+#pragma mark ----- 音频打断相关处理
+
+- (void)handerAudioInterruption {
+    // 处理
+    if (_isEvaluating) { // 如果是评测就停止评测
+        [self cancelEvaluating];
+        // 页面重置
+    }
+    else if (_isPlaying) { // 如果是播放就停止播放
+        [self stopPlayAction];
+    }
+}
+- (void)resumeAudioSession {
+    self.lastPlayNodeIndex = 0;
+    [self.playerEngine resumeAUGraph];
+}
+// 退到后台
+- (void)appEnterBackground {
+    [self handerAudioInterruption];
+}
+
+// 打断处理
+- (void)audioInterruption {
+    if (_videoRecordManager) {
+        [self.videoRecordManager resetSession];
+    }
+    [self handerAudioInterruption];
+}
+
+- (void)stopSession {
+    if (_videoRecordManager) {
+        [self.videoRecordManager stopSession];
+    }
+}
+
+
+#pragma mark ----- socket 相关处理
+
+- (void)connectSocketService {
+    
+    MJWeakSelf;
+    self.socketManager.didReceiveMessage = ^(id  _Nonnull message) {
+        dispatch_async(dispatch_get_main_queue(), ^{
+            // api
+            NSMutableDictionary *sendMessage = [NSMutableDictionary dictionary];
+            [sendMessage setValue:@"sendResult" forKey:@"api"];
+            [sendMessage setValue:message forKey:@"content"];
+            NSLog(@"receive message -----%@",message);
+            // 处理服务端返回的数据
+            NSDictionary *messageDic = [weakSelf convertJsonStringToNSDictionary:message];
+            [weakSelf dealWithMessage:messageDic];
+        });
+    };
+    self.socketManager.connectionStatus = ^(BOOL isSuccess) {
+        dispatch_async(dispatch_get_main_queue(), ^{
+            
+            if (!isSuccess) {
+                NSLog(@"-----连接失败");
+                if (weakSelf.hasSendStartMessage) {
+                    if (weakSelf.AQManager.isRunning) {
+                        [weakSelf cancelEvaluating];
+                        [weakSelf showErrorMessage:@"服务异常,请重试"];
+                        // 处理断连事件
+                        [weakSelf stopRecordService];
+                        weakSelf.isCompareStart = NO;
+                        weakSelf.isSoundCheckStart = NO;
+                    }
+                }
+                else if (weakSelf.evaluatParm) {
+                    // 处理断连事件
+                    [weakSelf cancelEvaluating];
+                    [weakSelf showErrorMessage:@"服务异常,请重试"];
+                    weakSelf.hasSendStartMessage = YES;
+                    weakSelf.isCompareStart = NO;
+                    weakSelf.isSoundCheckStart = NO;
+                }
+                else { // 其他断开
+                    if (weakSelf.AQManager.isRunning) {
+
+                        // 处理断连事件 停止评测
+                        [weakSelf cancelEvaluating];
+                        [weakSelf showErrorMessage:@"服务异常,请重试"];
+                        
+                        [weakSelf stopRecordService];
+                        weakSelf.isCompareStart = NO;
+                        weakSelf.isSoundCheckStart = NO;
+                    }
+                }
+            }
+            else {
+                NSLog(@"-----连接成功");
+                if (weakSelf.hasSendStartMessage == NO && weakSelf.evaluatParm) {
+                    NSDictionary *content = weakSelf.evaluatParm;
+                    NSString *sendData = [weakSelf configDataCommond:@"musicXml" body:content type:@"SOUND_COMPARE"];
+                    [weakSelf sendDataToSocketService:sendData];
+                    [weakSelf showBeatBeforeEvaluating];
+                    weakSelf.hasSendStartMessage = YES;
+                    NSLog(@"---- send musicXML message");
+                }
+            }
+        });
+    };
+    [self.socketManager SRWebSocketOpen];
+}
+
+- (NSDictionary *)convertJsonStringToNSDictionary:(NSString *)jsonString {
+    if (jsonString == nil) {
+        return nil;
+    }
+    NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
+    NSError *error;
+    NSDictionary *json = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&error];
+    if (error) {
+        NSLog(@"jsonString解析失败:%@", error);
+        return nil;
+    }
+    return json;
+}
+
+- (void)sendDataToSocketService:(id)data {
+    if (_socketManager) {
+        [self.socketManager sendData:data];
+    }
+}
+
+// 处理socket返回数据
+- (void)dealWithMessage:(NSDictionary *)messageDic {
+    if (_isEvaluating == NO) { // 非评测状态下不处理
+        return;
+    }
+    NSString *commond = [[messageDic dictionaryValueForKey:@"header"] stringValueForKey:@"commond"];
+    if ([commond isEqualToString:@"measureScore"]) { // 评测返回数据
+        NSDictionary *body = [messageDic dictionaryValueForKey:@"body"];
+        NSInteger score = [body integerValueForKey:@"score"];
+        NSInteger measureIndex = [body integerValueForKey:@"measureIndex"];
+        [self displayScore:score measureIndex:measureIndex];
+    }
+    else if ([commond isEqualToString:@"overall"]) { // 弹窗结果
+        NSDictionary *body = [messageDic dictionaryValueForKey:@"body"];
+        self.recordId = [body integerValueForKey:@"recordId"];
+        NSInteger score = [body integerValueForKey:@"score"];
+        NSInteger intonation = [body integerValueForKey:@"intonation"];
+        NSInteger integrity = [body integerValueForKey:@"integrity"];
+        NSInteger cadence = [body integerValueForKey:@"cadence"];
+        self.resultAlert = [EvaluateResultAlert shareInstance];
+        self.resultAlert.frame = CGRectMake(0, 0, KLandscapeWidth, KLandscapeHeight);
+        [self.resultAlert configWithScore:score intonation:intonation cadence:cadence integrity:integrity];
+        MJWeakSelf;
+        [self.resultAlert resultCallback:^(EVALUATE_RESULT_ACTION action) {
+            [weakSelf resulteAlertAction:action];
+        }];
+        [self.view addSubview:self.resultAlert];
+    }
+    else if ([commond isEqualToString:@"checking"]) { // 校音
+        NSTimeInterval interval = [[NSDate date] timeIntervalSince1970];
+        long long currentMilliseconds = interval * 1000;
+        
+        NSDictionary *body = [messageDic dictionaryValueForKey:@"body"];
+        float frequency = [body doubleValueForKey:@"frequency"];
+        float baseFrequency = [KSXMLInfoParse parsePitchToHZ:self.checkPitch];
+        if (frequency - baseFrequency > 5.0) { // 高了
+            self.correctTimeInterval = 0.0f;
+            [self.checkView showLightDisplay:LIGHTDISPLAY_HIGH];
+        }
+        else if (frequency - baseFrequency < -5.0f) { // 低了
+            self.correctTimeInterval = 0.0f;
+            [self.checkView showLightDisplay:LIGHTDISPLAY_LOW];
+        }
+        else {
+            if (self.correctTimeInterval == 0.0f) {
+                self.correctTimeInterval = currentMilliseconds;
+            }
+            else { // 如果一直持续超过1s
+                long long duration = currentMilliseconds - self.correctTimeInterval;
+                if (duration >= 1000) {
+                    if (self.checkView) {
+                        self.correctTimeInterval = 0.0;
+                        [self.checkView showSuccessCheckEnd];
+                    }
+                }
+            }
+            [self.checkView showLightDisplay:LIGHTDISPLAY_RIGHT];
+        }
+    }
+}
+
+- (NSString *)configDataCommond:(NSString *)commond body:(id)bodyMessage type:(NSString *)dataType {
+    NSMutableDictionary *parm = [NSMutableDictionary dictionary];
+    if (bodyMessage) {
+        [parm setValue:bodyMessage forKey:@"body"];
+    }
+    NSMutableDictionary *headerParm = [NSMutableDictionary dictionary];
+    if ([NSString isEmptyString:commond]) {
+        [headerParm setValue:@"" forKey:@"commond"];
+    }
+    else {
+        [headerParm setValue:commond forKey:@"commond"];
+    }
+    if (![NSString isEmptyString:dataType]) {
+        [headerParm setValue:dataType forKey:@"type"];
+    }
+    [headerParm setValue:@(200) forKey:@"status"];
+    [parm setValue:headerParm forKey:@"header"];
+    return [parm mj_JSONString];
+}
+
+- (void)sendEndMessage {
+    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
+        // 上传停止的信息 发送给服务端
+        NSString *endMessage = @"recordEnd";
+        NSString *endData = [self configDataCommond:endMessage body:nil type:@"SOUND_COMPARE"];
+        [self sendDataToSocketService:endData];
+        self.isCompareStart = NO;
+        NSLog(@"---- send end message");
+    });
+}
+
+
+
+#pragma mark ------ 谱面相关
+// 将displayScroll view 显示的当前音符移动到显示的中间位置
+- (void)moveDisplayViewDisplay {
+    
+    KSParseMessageModel *currentNode = self.manager.nodeMessageArray[self.lastPlayNodeIndex];
+    
+    CGFloat positionY = (currentNode.positionY  + currentNode.elementHeight / 2.0f) / PositionRate * self.scaleRate + currentNode.pageIndex * self.pageViewHeight;
+    CGFloat offsetY = 0.0f;
+    if (positionY < CGRectGetHeight(self.displayScrollView.frame) / 2.0f) {
+        offsetY = 0.0f;
+    }
+    else {
+        offsetY = positionY - CGRectGetHeight(self.displayScrollView.frame) / 2.0f;
+    }
+    [self.displayScrollView setContentOffset:CGPointMake(0, offsetY)];
+}
+
+// 渲染当前小节和评分
+- (void)displayScore:(NSInteger)score measureIndex:(NSInteger)measureIndex {
+    for (KSParseMessageModel *model in self.manager.measureMessageArray) {
+        if (model.elementIndex == measureIndex) {
+            KSMeasureLocationModel *measureLocation = [[KSMeasureLocationModel alloc] init];
+            measureLocation.positionX = model.positionX / PositionRate * self.scaleRate;
+            measureLocation.positionY = model.positionY / PositionRate * self.scaleRate;
+            measureLocation.measureWidth = model.elementWidth / PositionRate * self.scaleRate;
+            measureLocation.measureHeight = model.elementHeight / PositionRate * self.scaleRate;
+            measureLocation.pageIndex = model.pageIndex;
+            
+            measureLocation.fillColor = [self getDisplayColorWithScore:score sourceLocation:measureLocation];
+            
+            NSInteger index = model.pageIndex;
+            StaffImageDisplayView *view = self.pageArray[index];
+            [view addColorView:measureLocation];
+            break;
+        }
+    }
+}
+
+- (UIColor *)getDisplayColorWithScore:(NSInteger)score sourceLocation:(KSMeasureLocationModel *)model {
+    UIColor *measureBgColor;
+    UIColor *titleColor;
+    NSString *imageName = @"";
+    
+    if (score >= 90) { // perfect
+        measureBgColor = HexRGBAlpha(0xffd4c0, 0.47f);
+        
+        titleColor = HexRGB(0x516AFF);
+        imageName = @"score_perfect";
+    }
+    else if (score >= 70) { // great
+        measureBgColor = HexRGBAlpha(0xffb252, 0.48f);
+        titleColor = HexRGB(0xFF8E5A);
+        imageName = @"score_great";
+    }
+    else if (score >= 40) { // good
+        measureBgColor = HexRGBAlpha(0x01c1b5, 0.17f);
+        titleColor = HexRGB(0xFF958B);
+        imageName = @"score_good";
+    }
+    else { // bad
+        measureBgColor = HexRGBAlpha(0xff8e8e, 0.32f);
+        titleColor = HexRGB(0xEE4C6A);
+        imageName = @"score_bad";
+    }
+    CGFloat viewHeight = 40.0f;
+    CGRect viewFrame = CGRectMake(model.positionX, model.positionY+viewHeight, model.measureWidth, viewHeight);
+    [self showScoreViewInPage:model.pageIndex frame:viewFrame titleColor:titleColor score:score imageName:imageName];
+    
+    return measureBgColor;
+}
+
+- (void)showScoreViewInPage:(NSInteger)pageIndex frame:(CGRect)frame titleColor:(UIColor *)titleColor score:(NSInteger)score imageName:(NSString *)imageName {
+    CGFloat distanceY = 20.0f;
+    ScoreAnimationView *view = [[ScoreAnimationView alloc] initWithFrame:frame animitionDistance:distanceY score:score titleColor:titleColor imageName:imageName];
+    
+    StaffImageDisplayView *pageView = self.pageArray[pageIndex];
+    [view showInView:pageView];
+}
+
+
+#pragma mark ------ 评测结果处理
+- (void)showReportView {
+
+    KSBaseWKWebViewController *reportCtrl = [[KSBaseWKWebViewController alloc] init];
+    reportCtrl.url = [NSString stringWithFormat:@"%@/accompany/#/report/%zd",WEBHOST,self.recordId];
+    reportCtrl.hiddenNavBar = YES;
+    self.keepOrientation = YES;
+    reportCtrl.keepOrientation = YES;
+    [self.navigationController pushViewController:reportCtrl animated:YES];
+}
+
+- (void)resulteAlertAction:(EVALUATE_RESULT_ACTION)action {
+    switch (action) {
+        case EVALUATE_RESULT_ACTION_CANCLE: // 退出评测模式
+        {
+            [self.resultAlert removeFromSuperview];
+            [self quitEvaluatingMode];
+        }
+            break;
+        case EVALUATE_RESULT_ACTION_SHARE:  // 分享
+        {
+            // share function
+            
+        }
+            break;
+        case EVALUATE_RESULT_ACTION_SUBMIT: // 上传到云端
+        {
+            if (self.cameraOn && self.saveVideo) { // 上传视频到云端
+                [self videoUpdate];
+            }
+            else { // 直接发送消息显示音频
+                [self sendUploadMessage:nil];
+            }
+        }
+            break;
+        case EVALUATE_RESULT_ACTION_PRACTICE:  // 练习
+        {
+            [self.resultAlert removeFromSuperview];
+            [self quitEvaluatingMode];
+        }
+            break;
+        case EVALUATE_RESULT_ACTION_TAYAGAIN:  // 再试一次
+        {
+            [self.resultAlert removeFromSuperview];
+            [self clearAllColorDisplayView];
+        }
+            break;
+        case EVALUATE_RESULT_ACTION_REPORT:   // 报告
+        {
+            [self showReportView];
+        }
+            break;
+        default:
+            break;
+    }
+}
+
+#pragma mark ------ recorder
+// 初始化录制引擎
+- (void)configRecordManager {
+    self.AQManager = [[KSAQRecordManager alloc] init];
+    self.AQManager.delegate = self;
+}
+// 开始录制
+- (void)startRecordService {
+    if (self.AQManager.isRunning) {
+        [self.AQManager stopRecord];
+    }
+    [self.AQManager startRecord];
+}
+// 停止录制
+- (void)stopRecordService {
+    if (self.AQManager.isRunning) {
+        [self.AQManager stopRecord];
+    }
+}
+
+
+#pragma mark-------- KSAQRecordManagerDelegate
+- (void)audioRecordInterruption {
+
+    [self cancelEvaluating];
+    [self showErrorMessage:@"录制错误,请重试"];
+}
+
+- (void)audioRouteChange:(AUDIODEVICE_TYPE)type {
+    NSString *valueStr = @"";
+    BOOL checkIsWired = NO;
+    switch (type) {
+        case AUDIODEVICE_TYPE_HEADPHONE:
+        {
+            valueStr = @"有线耳机";
+            checkIsWired = YES;
+        }
+            break;
+        case AUDIODEVICE_TYPE_BLUETOOTH:
+        {
+            valueStr = @"蓝牙耳机";
+            checkIsWired = YES;
+        }
+            break;
+        case AUDIODEVICE_TYPE_NONE:
+        {
+            valueStr = @"";
+            checkIsWired = NO;
+        }
+            break;
+        default:
+            break;
+    }
+    
+    if (checkIsWired == NO) {
+        [self showNoWiredTipsAlertCallback:^{
+            
+        }];
+    }
+
+}
+
+- (void)audioRecord:(KSAQRecordManager *)audioRecord didRecordAudioData:(void *)data length:(UInt32)length {
+    
+    if (self.socketManager.socketReadyState != SR_OPEN) {
+        return;
+    }
+    NSData *pushData = [[NSData alloc] initWithBytes:data length:length];
+    if (self.isCompareStart) { // 发送评测开始消息
+        NSLog(@"--------- send start message");
+        _isCompareStart = NO;
+        NSString *startMessage = @"recordStart";
+        NSString *startString = [self configDataCommond:startMessage body:nil type:@"SOUND_COMPARE"];
+        [self sendDataToSocketService:startString];
+    }
+    else if (self.isSoundCheckStart) { // 校音开始
+        
+        NSLog(@"--------- send check start message");
+        _isSoundCheckStart = NO;
+        NSString *checkStartMessage = @"start";
+        NSString *startString = [self configDataCommond:checkStartMessage body:nil type:@"PITCH_DETECTION"];
+        [self sendDataToSocketService:startString];
+    }
+    [self sendDataToSocketService:pushData];
+}
+
+
+#pragma mark ---  player 播放引擎 播放和停止播放UI
+// 初始化播放引擎
+- (void)configPlayerEngineWithSong:(NSString *)songPath {
+    [self showhud];
+    self.playerEngine = [[MidiPlayerEngine alloc] init];
+    self.playerEngine.accompanyVolume = 0.3f;
+    self.playerEngine.reportTime = 10;
+    self.playerEngine.delegate = self;
+    [self.playerEngine configSoundFilePath:[[NSBundle mainBundle]
+                                            pathForResource:@"synthgms" ofType:@"sf2"]];
+    // 获取当前主声部对应的乐器id
+    self.playerEngine.instrumentId = 73;
+    [self.playerEngine loadMIDIFileWithString:songPath];
+    
+    
+    NSLog(@"--------- instanment track --- %d", self.playerEngine.baseInstrumentTrack);
+//    [self.playerEngine volumeOtherTrackExcept:self.playerEngine.baseInstrumentTrack withVolume:0];
+    if (self.currentSpeed == 0) {
+        self.currentSpeed = self.playerEngine.baseRate;
+    }
+    dispatch_async(dispatch_get_main_queue(), ^{
+        [self MBPShow:@"加载完成"];
+        [self removehub];
+        CloudControlButton *speedButton = [self.headView viewWithTag:BUTTON_TAG_SPEED];
+        [speedButton showSpeed:self.currentSpeed];
+    });
+}
+
+/* 播放时,显示光标 曲谱不可手动滑动,根据播放进度自动移动位置
+   停止播放时,隐藏光标,光标回到初始位置,曲谱滑动禁用关闭。
+ */
+- (void)playAction {  // 播放
+    CloudControlButton *playButton = [self.headView viewWithTag:BUTTON_TAG_PLAY];
+    playButton.isSelected = YES;
+    self.isPlaying = YES;
+    self.displayScrollView.scrollEnabled = NO;
+    [self showCursor];
+    if ([self.playerEngine isPlayingFile]) {
+        [self.playerEngine stopPlayingMIDIFile];
+    }
+    
+    // 将displayScroll view 显示的当前音符移动到显示的中间位置
+    [self moveDisplayViewDisplay];
+    
+    if (self.startMeasure != -1) {
+
+        [self.playerEngine setProgressTime:self.playerEngine.startTime];
+        [self.playerEngine playMIDIFile];
+    }
+    else {
+        [self.playerEngine playMIDIFile];
+    }
+}
+
+- (void)stopPlayAction { // 停止播放
+    self.isPlaying = NO;
+    [self hideCursor];
+    self.displayScrollView.scrollEnabled = YES;
+    if ([self.playerEngine isPlayingFile]) {
+        [self.playerEngine stopPlayingMIDIFile];
+    }
+    CloudControlButton *playButton = [self.headView viewWithTag:BUTTON_TAG_PLAY];
+    playButton.isSelected = NO;
+}
+
+#pragma mark ---- PlayerEngineDelegate
+- (void)ProgressUpdated:(float)progress currentTime:(MusicTimeStamp)currentTime {
+    
+    // 如果是最后一个节点
+    if (self.lastPlayNodeIndex == self.manager.nodeMessageArray.count - 1) {
+        return;
+    }
+    KSParseMessageModel *nextNode = self.manager.nodeMessageArray[self.lastPlayNodeIndex+1];
+    if (nextNode.elementTimePosition <= currentTime*1000) {
+        KSParseMessageModel *currentNode = self.manager.nodeMessageArray[self.lastPlayNodeIndex];
+        
+        self.lastPlayNodeIndex +=1;
+        [self moveCursorViewWithNode:nextNode];
+        // 如果换行或者到下一页
+        if (currentNode.positionY < nextNode.positionY || currentNode.pageIndex < nextNode.pageIndex) {
+            [self moveDisplayViewDisplay];
+        }
+    }
+}
+    
+- (void)playEnd {
+    // 判断是否需要重播
+    if (_isNeedRepeat && _isPlaying && _isEvaluating == NO) {
+        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
+            [self stopPlayAction];
+            self.lastPlayNodeIndex = 0;
+            [self showBeatBeforePlay];
+        });
+    }
+    else {
+        if (_isEvaluating) { // 如果在评测状态下
+            [self endEvaluating];
+        }
+        [self resetPlayButtonStatus];
+    }
+}
+
+- (void)resetPlayButtonStatus {
+    [self.playerEngine setProgressTime:0.0f];
+    CloudControlButton *playButton = [self.headView viewWithTag:BUTTON_TAG_PLAY];
+    playButton.isSelected = NO;
+    self.lastPlayNodeIndex = 0;
+    [self hideCursor];
+    self.displayScrollView.scrollEnabled = YES;
+}
+
+
+- (void)GetMusicTotalTime:(float)time {
+    
+}
+
+
+- (void)quitEvaluatingMode {
+    self.isEvaluating = NO;
+    [self clearAllColorDisplayView];
+    CloudControlButton *evaluateButton = [self.headView viewWithTag:BUTTON_TAG_EVALUATING];
+    evaluateButton.isSelected = NO;
+}
+
+
+- (void)showErrorMessage:(NSString *)message {
+    [self MBPShow:message];
+}
+
+
+- (void)displayView {
+    [self.view addSubview:self.viewContainer];
+    _backView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, KLandscapeWidth, KLandscapeHeight)];
+    _backView.backgroundColor = HexRGB(0x01c1b5);
+    [self.view addSubview:_backView];
+    _bgColorView = [[UIView alloc] initWithFrame:self.defaultFrame];
+    _bgColorView.layer.cornerRadius = 14.0f;
+    [self.backView addSubview:_bgColorView];
+    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
+        if (self.cameraOn) {
+            self.backView.alpha = BACKVIEW_ALPHA;
+            [self.videoRecordManager configSessiondisplayInView:self.viewContainer];
+        }
+    });
+    _headView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, KLandscapeWidth, 78)];
+    _headView.backgroundColor = [UIColor clearColor];
+    [self.backView addSubview:_headView];
+    // 配置顶部按钮
+    [self configButtonInView:_headView];
+    
+    // 创建右侧帮助按钮
+    [self createHelpButton];
+
+    // 创建scorll view 容器添加图片
+    [self createDisplayScrollView];
+}
+
+
+
+- (void)createHelpButton {
+    self.helpButton = [UIButton buttonWithType:UIButtonTypeCustom];
+    self.helpButton.frame = CGRectMake(KLandscapeWidth - 46, (KLandscapeHeight - 86) /2.0f, 46, 86);
+    [self.helpButton setImage:[UIImage imageNamed:@"cloud_help"] forState:UIControlStateNormal];
+    [self.helpButton addTarget:self action:@selector(helpAction) forControlEvents:UIControlEventTouchUpInside];
+    [self.view addSubview:self.helpButton];
+}
+
+- (void)helpAction {
+    CloudHelpView *helpView = [CloudHelpView shareInstance];
+    helpView.frame = CGRectMake(0, 0, KLandscapeWidth, KLandscapeHeight);
+    MJWeakSelf;
+    [helpView feedbackCallback:^{
+        [weakSelf showFeedbackUI];
+    }];
+    [self.view addSubview:helpView];
+}
+
+- (void)showFeedbackUI {
+    MJWeakSelf;
+    self.feedbackView = [CloudFeedbackView shareInstance];
+    self.feedbackView.frame = CGRectMake(0, 0, KLandscapeWidth, KLandscapeHeight);
+    [self.feedbackView submitCallback:^(NSString * _Nonnull content, NSString * _Nonnull type) {
+        [weakSelf submitFeedbackAction:content type:type];
+    }];
+    [self.view addSubview:self.feedbackView];
+}
+
+- (void)submitFeedbackAction:(NSString *)content type:(NSString *)type {
+    [self showhud];
+    [KSNetworkingManager cloudFeedbackRequest:KS_POST content:content type:type success:^(NSDictionary * _Nonnull dic) {
+        [self removehub];
+        if ([dic integerValueForKey:@"code"] == 200 && [dic boolValueForKey:@"status"]) {
+            [self MBPShow:@"反馈成功"];
+            [self.feedbackView removeFromSuperview];
+        }
+        else {
+            [self MBPShow:MESSAGEKEY];
+        }
+    } faliure:^(NSError * _Nonnull error) {
+        [self removehub];
+    }];
+}
+
+- (void)createDisplayScrollView {
+    [self.pageArray removeAllObjects];
+    if (self.needShowFinger) {
+        self.displayScrollView.frame = CGRectMake(0, 0, CGRectGetWidth(self.resizeFrame), CGRectGetHeight(self.resizeFrame));
+    }
+    else {
+        self.displayScrollView.frame = CGRectMake(0, 0, CGRectGetWidth(self.defaultFrame), CGRectGetHeight(self.defaultFrame));
+    }
+    [self.bgColorView addSubview:self.displayScrollView];
+    // 获取当前文件夹中所有图片
+    NSMutableArray *imgArray = [NSMutableArray array];
+    for (NSString *filePath in self.zipFileArray) {
+        if ([filePath containsString:@".png"]) {
+            [imgArray addObject:filePath];
+        }
+    }
+    
+//    NSLog(@"%@", imgArray.description);
+//    for (int i = 1; i < 6; i++) {
+//        NSString *name = [NSString stringWithFormat:@"mus-%d",i];
+//        [imgArray addObject:[[NSBundle mainBundle] pathForResource:name ofType:@"png"]];
+//    }
+    
+    
+
+    UIImage *img = [UIImage imageNamed:imgArray[0]];
+    CGFloat width = img.size.width;
+    CGFloat height = img.size.height;
+    CGFloat displayWidth = CGRectGetWidth(self.displayScrollView.frame);
+    
+    self.scaleRate = displayWidth / width;
+    CGFloat viewHeight = self.scaleRate * height;
+    self.pageViewHeight = viewHeight;
+    
+    self.bgColorType = self.eyeShieldOn ? COLOR_DISPLAYTYPE_PROTECT : COLOR_DISPLAYTYPE_NOMAL;
+    
+    for (NSInteger i = 0; i < imgArray.count; i++) { // 创建 StaffImageDisplayView
+        
+        StaffImageDisplayView *displayView = [[StaffImageDisplayView alloc] initWithImage:imgArray[i] pageIndex:i frame:CGRectMake(0, i * viewHeight, displayWidth, viewHeight)];
+        displayView.tag = 1000 + i;
+        MJWeakSelf;
+        [displayView viewTapActionCallback:^(CGFloat positionX, CGFloat positionY, NSInteger pageIndex) {
+            [weakSelf dealWithPositionX:positionX positionY:positionY pageIndex:pageIndex];
+        }];
+        [self.pageArray addObject:displayView];
+        [self.displayScrollView addSubview:displayView];
+    }
+    CGFloat contentHeight = viewHeight * imgArray.count;
+    if (contentHeight < self.displayScrollView.bounds.size.height) {
+        contentHeight = self.displayScrollView.bounds.size.height;
+    }
+    self.displayScrollView.contentSize = CGSizeMake(displayWidth, contentHeight);
+    
+    if (self.needShowFinger) {
+        [self.bgColorView addSubview:self.fingerDisplayView];
+    }
+    if (self.isPlaying) {
+        self.displayScrollView.scrollEnabled = NO;
+        [self.displayScrollView addSubview:self.cursorView];
+    }
+    else {
+        self.displayScrollView.scrollEnabled = YES;
+    }
+}
+
+
+
+- (void)configButtonInView:(UIView *)displayView {
+    UIButton *backButton = [UIButton buttonWithType:UIButtonTypeCustom];
+    backButton.frame = CGRectMake(21, 19, 32, 32);
+    backButton.layer.cornerRadius = 16.0f;
+    [backButton setImage:[UIImage imageNamed:@"cloud_back"] forState:UIControlStateNormal];
+    [backButton setTitleColor:HexRGB(0x000000) forState:UIControlStateNormal];
+    backButton.backgroundColor = [UIColor whiteColor];
+    [backButton addTarget:self action:@selector(backAction) forControlEvents:UIControlEventTouchUpInside];
+    [displayView addSubview:backButton];
+    
+    if (![NSString isEmptyString:self.songName]) {
+        UIView *songNameView = [[UIView alloc] initWithFrame:CGRectMake(70, 17, 180, 40)];
+        songNameView.backgroundColor = [UIColor whiteColor];
+        songNameView.layer.cornerRadius = 20.0f;
+        [displayView addSubview:songNameView];
+        
+        UIImageView *logoImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"cloudSong_logo"]];
+        logoImage.frame = CGRectMake(7, 5, 28, 28);
+        [songNameView addSubview:logoImage];
+        
+        UIImageView *nextImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"cloudSong_next"]];
+        nextImage.frame = CGRectMake(CGRectGetWidth(songNameView.frame) - 9 - 14, 14, 9, 12);
+        [songNameView addSubview:nextImage];
+        
+        UILabel *songLabel = [[UILabel alloc] initWithFrame:CGRectMake(CGRectGetMaxX(logoImage.frame) + 6, 0, CGRectGetMinX(nextImage.frame) - 5 - CGRectGetMaxX(logoImage.frame) - 6, 40)];
+        songLabel.textAlignment = NSTextAlignmentLeft;
+        songLabel.textColor = HexRGB(0x000000);
+        songLabel.font = [UIFont systemFontOfSize:16.0f weight:UIFontWeightMedium];
+        songLabel.text = self.songName;
+        [songNameView addSubview:songLabel];
+    }
+    
+    NSArray *imageArray = @[@"evaluate_nomal",@"select_normal",@"play_play",@"switch_nomal",@"cloud_playback",@"cloud_speed",@"cloud_setting"];
+    NSArray *selectImageArray = @[@"evaluate_selected",@"select_choose",@"play_pause",@"switch_accompany",@"cloud_playback",@"cloud_speed",@"cloud_setting"];
+    NSArray *titleArray = @[@"评测",@"选段",@"播放",@"原声",@"重播",@"速度",@"设置"];
+    NSArray *selectTitleArray = @[@"评测",@"选段",@"暂停",@"伴奏",@"重播",@"速度",@"设置"];
+    
+    for (NSInteger i = imageArray.count - 1; i >= 0; i--) {
+        CGRect frame = CGRectMake((KLandscapeWidth - 46) - (imageArray.count - i) * Button_Width - (imageArray.count - 1 - i)*Button_Space, 9, 34, 60);
+        MJWeakSelf;
+        CloudControlButton *button = [CloudControlButton createButtonWithImage:imageArray[i] selectImage:selectImageArray[i] buttonTitle:titleArray[i] selectTitle:selectTitleArray[i] tag:1000+i frame:frame callback:^(CloudControlButton * _Nonnull sender, BOOL isSelected) {
+            [weakSelf buttonClickAction:sender isSelected:isSelected];
+        }];
+        if (i == 5 && self.currentSpeed != 0) {
+            [button showSpeed:self.currentSpeed];
+        }
+        [displayView addSubview:button];
+    }
+}
+
+- (void)buttonClickAction:(CloudControlButton *)sender isSelected:(BOOL)isSelected {
+    BUTTON_TAG type = sender.tag;
+    switch (type) {
+        case BUTTON_TAG_EVALUATING:  // 评测开关
+        {
+            if (isSelected) { // 开启时设置其他按钮禁用 重新布局曲谱页面
+                self.isEvaluating = YES;
+                
+                MJWeakSelf;
+                [self checkIsWiredHeadsetOnCallback:^{
+                    if (weakSelf.soundCheckOn && ![weakSelf.songMessageSource.subjectId isEqualToString:@"23"]) { // 如果是打击乐跳过
+                        [weakSelf startSoundCheck];
+                    }
+                }];
+            }
+            else { // 恢复按钮 重新恢复曲谱布局
+                self.isEvaluating = NO;
+            }
+        }
+            break;
+        case BUTTON_TAG_SELECT:
+        {
+            if (isSelected) {
+                self.isChooseMeasure = YES;
+            }
+            else {
+                [self stopPlayAction];
+                self.isChooseMeasure = NO;
+                // 清理全部
+                self.startMeasure = -1;
+                self.endMeasure = -1;
+                self.lastPlayNodeIndex = 0;
+                self.playerEngine.startTime = 0.0f;
+                self.playerEngine.endTime = 0.0f;
+                [self.playerEngine setProgressTime:self.playerEngine.startTime];
+                [self clearAllColorDisplayView];
+            }
+        }
+            break;
+        case BUTTON_TAG_PLAY:
+        {
+            if (isSelected) { // 播放
+
+                CGFloat rate = self.currentSpeed / self.playerEngine.baseRate;
+                [self.playerEngine setMusicPlayerSpeed:rate];
+                [self showBeatBeforePlay];
+            }
+            else { // 暂停
+                [self stopPlayAction];
+            }
+            
+        }
+            break;
+        case BUTTON_TAG_SWITCH:
+        {
+            [self.trackChooseView showInView:self.view];
+//            if (isSelected) { // 切换成伴奏
+//                [self.playerEngine muteTrack:YES WithTrack:self.playerEngine.baseInstrumentArray];
+//            }
+//            else { // 切换成原声
+//                [self.playerEngine muteTrack:NO WithTrack:self.playerEngine.baseInstrumentArray];
+//            }
+
+        }
+            break;
+        case BUTTON_TAG_REPEAT: // 从头开始播放
+        {
+            [self.playerEngine setProgressTime:0.0f];
+            [self displayCourseToFirstNode];
+            CloudControlButton *playButton = [self.headView viewWithTag:BUTTON_TAG_PLAY];
+            playButton.isSelected = YES;
+            [self showBeatBeforePlay];
+        }
+            break;
+        case BUTTON_TAG_SPEED: // 调速
+        {
+            // 显示调速控件
+            [self showSpeedView];
+        }
+            break;
+        case BUTTON_TAG_SETTING: // 设置
+        {
+            [self showSettingView];
+        }
+            break;
+        default:
+            break;
+    }
+}
+
+- (void)showSpeedView {
+    LFPopupMenu *popView = [[LFPopupMenu alloc] init];
+    popView.needBorder = YES;
+    popView.minWidth = 80;
+    [popView configWithCustomView:self.sliderView];
+    
+    CloudControlButton *playButton = [self.headView viewWithTag:BUTTON_TAG_SPEED];
+    [popView showArrowToView:playButton];
+}
+
+- (void)showSettingView {
+    KSCloudSettingView *settingView = [KSCloudSettingView shareInstance];
+    settingView.frame = CGRectMake(0, 0, KLandscapeWidth, KLandscapeHeight);
+    // 设置回调
+    MJWeakSelf;
+    [settingView settingCallback:^(CLOUDSETTING_TYPE type, BOOL isOn, NSString * _Nonnull level) {
+        [weakSelf configViewDisplayType:type isOn:isOn level:level];
+    }];
+    [self.view addSubview:settingView];
+}
+
+- (void)setBgColorType:(COLOR_DISPLAYTYPE)bgColorType {
+    _bgColorType = bgColorType;
+    if (bgColorType == COLOR_DISPLAYTYPE_PROTECT) {
+        self.bgColorView.backgroundColor = HexRGB(0xfff4e1);
+    }
+    else {
+        self.bgColorView.backgroundColor = [UIColor whiteColor];
+    }
+}
+
+
+- (void)configViewDisplayType:(CLOUDSETTING_TYPE)type isOn:(BOOL)isOn level:(NSString *)level {
+    switch (type) {
+        case CLOUDSETTING_TYPE_EYESHIELD: // 护眼模式
+        {
+            self.eyeShieldOn = isOn;
+            self.bgColorType = isOn ? COLOR_DISPLAYTYPE_PROTECT : COLOR_DISPLAYTYPE_NOMAL;
+        }
+            break;
+        case CLOUDSETTING_TYPE_SOUNDCHECK:
+        {
+            self.soundCheckOn = isOn;
+        }
+            break;
+        case CLOUDSETTING_TYPE_CAMERA:
+        {
+            self.backView.alpha = isOn ? BACKVIEW_ALPHA : 1.0f;
+            self.cameraOn = isOn;
+            [self openCamera:isOn];
+        }
+            break;
+        case CLOUDSETTING_TYPE_REPEAT:
+        {
+            self.isNeedRepeat = isOn;
+        }
+            break;
+        case CLOUDSETTING_TYPE_FINGER:
+        {
+            self.showFinger = isOn;
+            // 刷新页面布局
+            [self displayFingerView];
+        }
+            break;
+        case CLOUDSETTING_TYPE_LEVEL:
+        {
+            self.level = level;
+        }
+            break;
+        case CLOUDSETTING_TYPE_SAVEVIDEO:
+        {
+            self.saveVideo = isOn;
+        }
+            break;
+        case CLOUDSETTING_TYPE_ACCOMPANY:
+        {
+            self.accompanyOn = isOn;
+        }
+            break;
+        default:
+            break;
+    }
+}
+
+- (void)displayFingerView {
+    if (self.needShowFinger) {
+        [self.displayScrollView removeFromSuperview];
+        _displayScrollView = nil;
+        [self createDisplayScrollView];
+    }
+    else {
+        [self.fingerDisplayView removeFromSuperview];
+        [self.displayScrollView removeFromSuperview];
+        _displayScrollView = nil;
+        [self createDisplayScrollView];
+    }
+}
+
+- (void)openCamera:(BOOL)isOpen {
+    if (isOpen) { // 开启摄像头
+        PREMISSIONTYPE canOpenCamera = [RecordCheckManager checkCameraPremissionAvaiable:YES showInView:self.view];
+        if (canOpenCamera == PREMISSIONTYPE_YES) {
+            [self.videoRecordManager configSessiondisplayInView:self.viewContainer];
+        }
+    }
+    else {
+        if (self->_videoRecordManager) {
+            [self.videoRecordManager removeDisplay];
+        }
+    }
+}
+
+- (void)dealWithPositionX:(CGFloat)positionX positionY:(CGFloat)positionY pageIndex:(NSInteger)pageIndex {
+    //  如果不是选段模式 不计算
+    if (!self.isChooseMeasure) {
+        return;
+    }
+    
+    // 找到当前选择的小节
+    // 获取当前图片上对应的x,y所对应的位置文件中的 点
+    CGFloat locationX = positionX / self.scaleRate * PositionRate;
+    CGFloat locationY = positionY / self.scaleRate * PositionRate;
+    
+    for (KSParseMessageModel *model in self.manager.measureMessageArray) {
+        
+        if (model.pageIndex == pageIndex) { // 先找到当前的page数据
+            // 判断是否落在小节X轴范围区间
+            BOOL isLocationInX = (model.positionX < locationX && (model.positionX + model.elementWidth) >= locationX);
+            // 判断是否落在小节Y轴范围区间
+            BOOL isLocationInY = (model.positionY < locationY && (model.positionY + model.elementHeight) >= locationY);
+            if (isLocationInX && isLocationInY) { // 认为当前小节是被选中小节
+                NSInteger chooseMeasure = model.elementIndex;
+                [self evaluateChooseMeasure:chooseMeasure];
+                break;
+            }
+        }
+    }
+}
+
+- (void)evaluateChooseMeasure:(NSInteger)chooseMeasure {
+    if (self.startMeasure == -1 && self.endMeasure == -1) {
+        self.startMeasure = chooseMeasure;
+        self.endMeasure = chooseMeasure;
+    }
+    if (self.startMeasure > chooseMeasure) {
+        self.startMeasure = chooseMeasure;
+    }
+    else if (self.startMeasure < chooseMeasure) {
+        self.endMeasure = chooseMeasure;
+    }
+    // 获取选段时间
+    NSInteger index = self.startMeasure;
+    BOOL chooseEndMeasure = NO;
+    if (self.endMeasure == self.manager.measureMessageArray.count-1) {
+        chooseEndMeasure = YES;
+    }
+    NSInteger endIndex = chooseEndMeasure ? self.endMeasure : self.endMeasure+1;
+    // 1.根据小节找到小节开始的时值
+    for (KSParseMessageModel *model in self.manager.measureMessageArray) {
+        if (index == model.elementIndex) {
+            self.playerEngine.startTime = model.elementTimePosition / 1000;
+        }
+        else if (chooseEndMeasure) { // 如果结束是最后一小节
+            self.playerEngine.endTime = 0.0f;
+        }
+        // 如果不是最后一个小节
+        else if (!chooseEndMeasure && endIndex == model.elementIndex) {
+            self.playerEngine.endTime = model.elementTimePosition / 1000;
+        }
+    }
+    
+    // 重新绘制layer颜色
+    NSMutableArray *colorViewArray = [NSMutableArray array];
+    
+    for (KSParseMessageModel *model in self.manager.measureMessageArray) {
+        if (model.elementIndex >= self.startMeasure && model.elementIndex <= self.endMeasure) {
+            
+            KSMeasureLocationModel *measureLocation = [[KSMeasureLocationModel alloc] init];
+            measureLocation.positionX = model.positionX / PositionRate * self.scaleRate;
+            measureLocation.positionY = model.positionY / PositionRate * self.scaleRate;
+            measureLocation.measureWidth = model.elementWidth / PositionRate * self.scaleRate;
+            measureLocation.measureHeight = model.elementHeight / PositionRate * self.scaleRate;
+            measureLocation.pageIndex = model.pageIndex;
+            measureLocation.fillColor = HexRGB(0xffe7bd);
+            [colorViewArray addObject:measureLocation];
+        }
+    }
+    // 先清除全部color
+    [self clearAllColorDisplayView];
+    // 添加
+    for (KSMeasureLocationModel *model in colorViewArray) {
+        NSInteger index = model.pageIndex;
+        StaffImageDisplayView *view = self.pageArray[index];
+        [view addColorView:model];
+    }
+}
+
+- (void)clearAllColorDisplayView {
+    
+    for (StaffImageDisplayView *view in self.pageArray) {
+        [view clearAllColorView];
+    }
+}
+
+- (NSMutableArray *)getAllImageFile:(NSString *)path {
+    NSMutableArray *filePathArray = [NSMutableArray array];
+    NSFileManager *fileManger = [NSFileManager defaultManager];
+    BOOL isDir = NO;
+    BOOL isExist = [fileManger fileExistsAtPath:path isDirectory:&isDir];
+    if (isExist) {
+        if (isDir) {
+            NSArray * dirArray = [fileManger contentsOfDirectoryAtPath:path error:nil];
+            NSString * subPath = nil;
+            for (NSString * str in dirArray) {
+                subPath  = [path stringByAppendingPathComponent:str];
+                BOOL issubDir = NO;
+                [fileManger fileExistsAtPath:subPath isDirectory:&issubDir];
+                [self getAllImageFile:subPath];
+            }
+        }else{
+            NSString *fileName = [[path componentsSeparatedByString:@"/"] lastObject];
+            if ([fileName hasSuffix:@".png"]) {
+                //do anything you want
+                [filePathArray addObject:path];
+            }
+        }
+    }else{
+        NSLog(@"this path is not exist!");
+    }
+    return filePathArray;
+}
+
+- (void)backAction {
+    [self dismissViewControllerAnimated:YES completion:nil];
+}
+
+
+- (void)showFingerView {
+    
+    if (self.needShowFinger && self.fingerImageDic != nil) {
+        
+        NSMutableDictionary *parm  = self.nodeArray[self.lastPlayNodeIndex];
+        NSInteger noteValue = [parm integerValueForKey:@"note_value"];
+
+        NSArray *noteFingerArray = [self.fingerImageDic arrayValueForKey:[NSString stringWithFormat:@"%zd",noteValue]];
+        // 剔除
+        [self.fingerContentView removeAllSubViews];
+        
+        if (noteFingerArray.count) {
+            NSString *imgStr = [noteFingerArray firstObject];
+            NSArray *imgArray = [imgStr componentsSeparatedByString:@","];
+            
+            for (NSString *imgName in imgArray) {
+                UIImageView *fingerView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.fingerContentView.frame), CGRectGetHeight(self.fingerContentView.frame))];
+                [fingerView setImage:[UIImage imageNamed:imgName]];
+                [self.fingerContentView addSubview:fingerView];
+            }
+        }
+    }
+}
+
+
+- (void)showCursor {  // 显示光标
+    // 获取当前播放位置
+    [self.displayScrollView addSubview:self.cursorView];
+    if (self.startMeasure == -1) {
+        if (self.lastPlayNodeIndex == 0) {
+            [self displayCourseToFirstNode];
+        }
+    }
+    else {  // 获取当前选择小节
+        
+        NSInteger index = self.startMeasure;
+        Float64 timePosition = 0;
+        
+        // 1.根据小节找到小节开始的时值
+        for (KSParseMessageModel *model in self.manager.measureMessageArray) {
+            if (index == model.elementIndex) {
+                timePosition = model.elementTimePosition;
+                break;
+            }
+        }
+        // 2.根据时值获取对应音符位置
+        for (KSParseMessageModel *nodeModel in self.manager.nodeMessageArray) {
+            
+            if (nodeModel.elementTimePosition == timePosition) {
+                
+                [self moveCursorViewWithNode:nodeModel];
+                break;
+            }
+        }
+    }
+    self.cursorView.hidden = NO;
+}
+
+- (void)hideCursor {  // 隐藏光标
+    // 重置播放位置
+    self.cursorView.hidden = YES;
+    [self.cursorView removeFromSuperview];
+}
+
+- (void)displayCourseToFirstNode {
+    // 获取第一个音符位置
+    KSParseMessageModel *firstNode = self.manager.nodeMessageArray[0];
+    [self moveCursorViewWithNode:firstNode];
+}
+
+- (void)moveCursorViewWithNode:(KSParseMessageModel *)nodeModel {
+    self.lastPlayNodeIndex = nodeModel.elementIndex;
+    CGFloat positionX = nodeModel.positionX / PositionRate * self.scaleRate;
+    CGFloat positionY = nodeModel.positionY / PositionRate * self.scaleRate + self.pageViewHeight*(nodeModel.pageIndex);
+    CGFloat cursorWidth = nodeModel.elementWidth / PositionRate * self.scaleRate;
+    CGFloat cursorHeight = nodeModel.elementHeight / PositionRate * self.scaleRate;
+    self.cursorView.frame = CGRectMake(positionX, positionY, cursorWidth, cursorHeight);
+    
+    // 指法同步
+    [self showFingerView];
+}
+
+
+
+#pragma mark -------- lazying
+- (CGRect)defaultFrame {
+    return CGRectMake(Left_Space, 78, KLandscapeWidth - Left_Space*2, KLandscapeHeight - 78 - 10);
+}
+- (UIView *)cursorView {
+    if (!_cursorView) {
+        _cursorView = [[UIView alloc] initWithFrame:CGRectZero];
+        _cursorView.hidden = YES;
+        _cursorView.backgroundColor = HexRGBAlpha(0xff5a25, 0.4f);
+    }
+    return _cursorView;
+}
+- (UIScrollView *)displayScrollView {
+    if (!_displayScrollView) {
+        _displayScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.defaultFrame), CGRectGetHeight(self.defaultFrame))];
+        _displayScrollView.backgroundColor = [UIColor clearColor];
+        _displayScrollView.scrollEnabled = YES;
+        _displayScrollView.showsHorizontalScrollIndicator = NO;
+        _displayScrollView.showsVerticalScrollIndicator = NO;
+        _displayScrollView.bounces = NO;
+    }
+    return _displayScrollView;
+}
+
+- (NSMutableArray *)pageArray {
+    if (!_pageArray) {
+        _pageArray = [NSMutableArray array];
+    }
+    return _pageArray;
+}
+
+
+- (KSWebSocketManager *)socketManager {
+    if (!_socketManager) {
+        _socketManager = [[KSWebSocketManager alloc] init];
+    }
+    return _socketManager;
+}
+
+- (KSSliderView *)sliderView {
+    if (!_sliderView) {
+        _sliderView = [[KSSliderView alloc] initWithFrame:CGRectMake(0, 0, 50, 250)];
+        _sliderView.backgroundColor = [UIColor clearColor];
+        _sliderView.rateValue = self.currentSpeed;
+        _sliderView.isVertical = YES;
+        _sliderView.delegate = self;
+    }
+    return _sliderView;
+}
+
+- (NoWiredTipsAlert *)wiredAlert {
+    if (!_wiredAlert) {
+        _wiredAlert = [NoWiredTipsAlert shareInstance];
+        _wiredAlert.frame = CGRectMake(0, 0, KLandscapeWidth, KLandscapeHeight);
+    }
+    return _wiredAlert;
+}
+- (UIView *)viewContainer {
+    if (!_viewContainer) {
+        _viewContainer = [[UIView alloc] initWithFrame:CGRectMake(0, 0, KLandscapeWidth, KLandscapeHeight)];
+    }
+    return _viewContainer;
+}
+
+- (KSVideoRecordManager *)videoRecordManager {
+    if (!_videoRecordManager) {
+        MJWeakSelf;
+        _videoRecordManager = [[KSVideoRecordManager alloc] initSessionRecordCallback:^(BOOL isSuccess, NSString * _Nullable message) {
+            if (isSuccess) {
+                [weakSelf showSuccessMessage:message];
+            }
+            else {
+                if (![NSString isEmptyString:message]) {
+                    [weakSelf MBPShow:message];
+                }
+            }
+        }];
+    }
+    return _videoRecordManager;
+}
+
+- (UIButton *)evaluateButton {
+    if (!_evaluateButton) {
+        _evaluateButton = [UIButton buttonWithType:UIButtonTypeCustom];
+        _evaluateButton.layer.cornerRadius = 22.0f;
+        [_evaluateButton setBackgroundColor:HexRGB(0x01c1b5)];
+        _evaluateButton.frame = CGRectMake((KLandscapeWidth - 150)/2.0f, KLandscapeHeight - 44 - 20, 150, 44);
+        [_evaluateButton.titleLabel setFont:[UIFont systemFontOfSize:18.0f weight:UIFontWeightMedium]];
+        [_evaluateButton setTitle:@"开始演奏" forState:UIControlStateNormal];
+        [_evaluateButton setTitle:@"√结束演奏" forState:UIControlStateSelected];
+        [_evaluateButton addTarget:self action:@selector(evaluateButtonAction:) forControlEvents:UIControlEventTouchUpInside];
+    }
+    return _evaluateButton;
+}
+
+- (void)evaluateButtonAction:(UIButton *)sender {
+    sender.selected = !sender.selected;
+    if (sender.isSelected) {
+        [self startEvaluateing];
+    }
+    else {
+        [self endEvaluating];
+    }
+}
+
+- (void)showSuccessMessage:(NSString *)message {
+    [self MBPShow:message];
+}
+
+#pragma mark ----- KSSliderView delegate
+- (void)rateChangeAction:(NSInteger)rate {
+    self.currentSpeed = rate;
+    CloudControlButton *playButton = [self.headView viewWithTag:BUTTON_TAG_SPEED];
+    [playButton showSpeed:rate];
+    
+    // 速度写入本地文件中
+    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+        
+        NSString *filePath = [CloudSongMessageModel getSaveSpeedPath];
+        NSDictionary *speedDic = [NSMutableDictionary dictionaryWithContentsOfFile:filePath];
+        NSMutableDictionary *saveDic = [NSMutableDictionary dictionaryWithDictionary:speedDic];
+        [saveDic setValue:@(rate) forKey:self.songMessageSource.examSongId];
+        NSError *err = nil;
+        if (@available(iOS 11.0, *)) {
+            [saveDic writeToURL:[NSURL fileURLWithPath:filePath] error:&err];
+        } else {
+            // Fallback on earlier versions
+            [saveDic writeToURL:[NSURL fileURLWithPath:filePath] atomically:YES];
+        }
+    });
+    
+    
+    double playRate = rate / self.playerEngine.baseRate;
+    [self.playerEngine setMusicPlayerSpeed:playRate];
+}
+
+#pragma mark ------ cloud function
+// 点击开始评测按钮
+- (void)startEvaluateing {
+    [self configRecordManager];
+    self.hasSendStartMessage = NO;
+    PREMISSIONTYPE isOk = [RecordCheckManager checkPermissionShowAlert:NO showInView:nil];
+    if (isOk == PREMISSIONTYPE_YES) {
+        [self configXMLInfo];
+        // 如果socket 连上了
+        if (self.socketManager.socketReadyState == SR_OPEN) {
+            NSDictionary *content = self.evaluatParm;
+            NSString *sendData = [self configDataCommond:@"musicXml" body:content type:@"SOUND_COMPARE"];
+            [self sendDataToSocketService:sendData];
+            NSLog(@"------ send music xml info");
+            self.hasSendStartMessage = YES;
+            // 开始评测
+            [self showBeatBeforeEvaluating];
+        }
+        else {
+            [self connectSocketService];
+        }
+    }
+    else {
+        // 停止评测功能
+        [self cancelEvaluating];
+        [self showErrorMessage:@"没有麦克风权限"];
+    }
+}
+
+- (void)setIsEvaluating:(BOOL)isEvaluating {
+    _isEvaluating = isEvaluating;
+    if (isEvaluating) {
+        if (self.isPlaying) {
+            [self stopPlayAction];
+            
+        }
+        if (self.needShowFinger == YES) {
+            self.needShowFinger = NO;
+            [self displayFingerView];
+        }
+        [self.view addSubview:self.evaluateButton];
+        [self ForbiddenButton:YES];
+    }
+    else {
+        // 重置页面数据
+        if (_isPlaying) { // 如果在评测状态下
+            [self stopPlayAction];
+            [self endEvaluating];
+        }
+        [self displayCourseToFirstNode];
+        [self.playerEngine setProgressTime:0.0f];
+        [self.evaluateButton removeFromSuperview];
+        [self ForbiddenButton:NO];
+        if (self.showFinger == YES && self.fingerImageDic != nil) {
+            self.needShowFinger = YES;
+            [self displayFingerView];
+        }
+    }
+}
+
+- (void)ForbiddenButton:(BOOL)isForbidden {
+    for (NSInteger indexTag = 1001; indexTag < 1007; indexTag++) {
+        CloudControlButton *button = [self.headView viewWithTag:indexTag];
+        button.userInteractionEnabled = !isForbidden;
+        button.alpha =  isForbidden ? 0.6f : 1.0f;
+    }
+}
+
+
+#pragma mark ----- 节拍
+- (void)showBeatBeforePlay {
+    KSCloudBeatView *beatView = [KSCloudBeatView shareInstanceWithBeatType:self.beatType speed:self.currentSpeed repeatCount:1 supplement:0];
+    MJWeakSelf;
+    [beatView startPlayWithEndCallback:^(BOOL isCancle) {
+        if (isCancle) { // 取消
+            [weakSelf resetPlayButtonStatus];
+        }
+        else { // 开始播放
+            [weakSelf playAction];
+        }
+    }];
+    [self.view addSubview:beatView];
+}
+
+- (void)showBeatBeforeEvaluating {
+    KSCloudBeatView *beatView = [KSCloudBeatView shareInstanceWithBeatType:self.beatType speed:self.currentSpeed repeatCount:1 supplement:0];
+    MJWeakSelf;
+    [beatView startPlayWithEndCallback:^(BOOL isCancle) {
+        if (isCancle) { // 取消
+            [weakSelf cancelEvaluating];
+        }
+        else { // 开始播放
+            [weakSelf startPlayAndRecord];
+        }
+    }];
+    [self.view addSubview:beatView];
+}
+
+#pragma mark ---- 评测功能
+
+- (void)startPlayAndRecord {
+    if (self.cameraOn && self.saveVideo) {
+        [self startCapture];
+    }
+    if (self.isChooseMeasure) { // 重置选段
+        [self resetChooseMeasureStatus];
+    }
+    [self clearAllColorDisplayView];
+    CGFloat rate = self.currentSpeed / self.playerEngine.baseRate;
+    [self.playerEngine setMusicPlayerSpeed:rate];
+    [self.playerEngine setProgressTime:0.0f];
+    [self displayCourseToFirstNode];
+    [self playAction];
+    [self startRecording];
+    
+    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
+        // 发送时间校准消息
+        NSDictionary *bodyParm = @{
+            @"offsetTime" :@(0.000),
+        };
+        NSString *realStartTime = @"audioPlayStart";
+        NSString *realStart = [self configDataCommond:realStartTime body:bodyParm type:@"SOUND_COMPARE"];
+        [self sendDataToSocketService:realStart];
+        NSLog(@"--------- send check time message");
+    });
+}
+
+- (void)resetChooseMeasureStatus {
+    self.isChooseMeasure = NO;
+    // 清理全部
+    self.startMeasure = -1;
+    self.endMeasure = -1;
+    self.lastPlayNodeIndex = 0;
+    self.playerEngine.startTime = 0.0f;
+    self.playerEngine.endTime = 0.0f;
+    CloudControlButton *button = [self.headView viewWithTag:BUTTON_TAG_SELECT];
+    button.isSelected = NO;
+}
+
+- (void)configXMLInfo {
+    NSMutableDictionary *dic = [NSMutableDictionary dictionary];
+    [dic setValue:@(0) forKey:@"beatLength"];
+    [dic setValue:@"IOS" forKey:@"platform"];
+    
+    [dic setValue:self.songMessageSource.behaviorId forKey:@"behaviorId"];
+    [dic setValue:@"1638175609911244308" forKey:@"uuid"];
+    [dic setValue:self.songMessageSource.detailId forKey:@"detailId"];
+    [dic setValue:self.songMessageSource.examSongId forKey:@"examSongId"];
+    [dic setValue:self.songMessageSource.subjectId forKey:@"subjectId"];
+    [dic setValue:self.songMessageSource.examSongId forKey:@"id"];
+    [dic setValue:self.songMessageSource.xmlUrl forKey:@"xmlUrl"];
+    // head level
+    [dic setValue:self.level forKey:@"heardLevel"];
+    
+    // 设置速度
+    [dic setValue:@(self.currentSpeed) forKey:@"speed"];
+    NSMutableArray *newNodeArray = [self configNodeArrayWithSpeed:self.currentSpeed];
+    [dic setValue:newNodeArray forKey:@"musicXmlInfos"];
+    
+    self.evaluatParm = dic;
+}
+
+- (NSMutableArray *)configNodeArrayWithSpeed:(NSInteger)speed {
+    NSMutableArray *sourceArray = [NSMutableArray array];
+    if (speed == 120) {
+        sourceArray = [NSMutableArray arrayWithArray:self.nodeArray];
+        return sourceArray;
+    }
+    for (NSMutableDictionary *parm in self.nodeArray) {
+        NSMutableDictionary *node = [parm mutableCopy];
+        float timeSapmle = [node floatValueForKey:@"timeStamp"];
+        float duration = [node floatValueForKey:@"duration"];
+        timeSapmle = timeSapmle * (120.0 / speed);
+        duration = duration * (120.0 / speed);
+        [node setValue:@(timeSapmle) forKey:@"timeStamp"];
+        [node setValue:@(duration) forKey:@"duration"];
+        [sourceArray addObject:node];
+    }
+    return sourceArray;
+}
+
+/* 停止评测功能 */
+- (void)endEvaluating {
+    [self stopPlayAction];
+    self.evaluateButton.selected = NO;
+    self.evaluatParm = nil;
+    [self stopRecordService];
+    [self sendEndMessage];
+    [self stopCapatureVideo];
+}
+
+/* 取消评测*/
+- (void)cancelEvaluating {
+    [self stopPlayAction];
+    self.evaluateButton.selected = NO;
+    self.evaluatParm = nil;
+    [self stopRecordService];
+    [self sendEndMessage];
+    [self stopCapatureVideo];
+}
+
+// 如果开启了保存相册和摄像头 结束评测时停止录制
+- (void)stopCapatureVideo {
+    if (self.cameraOn && self.saveVideo) {
+        [self endCapture];
+    }
+}
+
+// 是否带耳机检测
+- (void)checkIsWiredHeadsetOnCallback:(void(^)(void))callback {
+    AUDIODEVICE_TYPE type = [KSAQRecordManager queryAudioOutputDeviceType];
+    NSString *valueStr = @"";
+    BOOL checkIsWired = NO;
+    switch (type) {
+        case AUDIODEVICE_TYPE_HEADPHONE:
+        {
+            valueStr = @"有线耳机";
+            checkIsWired = YES;
+        }
+            break;
+        case AUDIODEVICE_TYPE_BLUETOOTH:
+        {
+            valueStr = @"蓝牙耳机";
+            checkIsWired = YES;
+        }
+            break;
+        case AUDIODEVICE_TYPE_NONE:
+        {
+            valueStr = @"";
+            checkIsWired = NO;
+        }
+            break;
+        default:
+            break;
+    }
+    
+    if (checkIsWired == NO) { // 未带耳机弹窗提示
+        [self showNoWiredTipsAlertCallback:^{
+            callback();
+        }];
+    }
+    
+}
+
+- (void)showNoWiredTipsAlertCallback:(void(^)(void))callback {
+    if (![self.view.subviews containsObject:self.wiredAlert]) {
+        [self.wiredAlert showAlertInView:self.view callback:^{
+            callback();
+        }];
+    }
+}
+
+/* 开始录音*/
+- (void)startRecording {
+    self.isCompareStart = YES;
+    [self startRecordService];
+}
+
+/* 停止录音*/
+- (void)endRecording {
+    [self stopRecordService];
+}
+
+/* 开始录制*/
+- (void)startCapture {
+    // 评测的时候 生成video时会合成声轨
+    BOOL isIgnoreAudio = self.isEvaluating;
+    [self.videoRecordManager setIgnoreAudio:isIgnoreAudio];
+    [RecordCheckManager checkPhotoLibraryPremissionAvaiable:YES showInView:self.view];
+    self.videoRecordManager.audioUrl = self.AQManager.audioUrl;
+    [self.videoRecordManager startRecord];
+}
+
+/* 结束录制*/
+- (void)endCapture {
+    if (self->_videoRecordManager) {
+        [self.videoRecordManager stopRecord];
+    }
+}
+/* 开始校音*/
+- (void)startSoundCheck {
+    [self showSoundCheckView];
+    
+    [self configRecordManager];
+    self.isCompareStart = NO;
+    self.isSoundCheckStart = YES;
+    [self startRecordService];
+}
+
+- (void)showSoundCheckView {
+    self.checkView = [SoundCheckView shareInstance];
+    MJWeakSelf;
+    [self.checkView configSoundCheckWithSubjectId:self.songMessageSource.subjectId callback:^(CLOSEACTION action) {
+        if (action == CLOSEACTION_SUCCESS) {
+            [weakSelf showSuccessMessage:@"校音成功"];
+        }
+        else if (action == CLOSEACTION_CLOSECHECK) { // 关闭校音
+            weakSelf.soundCheckOn = NO;
+            NSDictionary *config = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"cloudConfig"];
+            NSMutableDictionary *configDict = [NSMutableDictionary dictionaryWithDictionary:config];
+            [configDict setValue:@(NO) forKey:@"soundCheck"];
+            [[NSUserDefaults standardUserDefaults] setObject:configDict forKey:@"cloudConfig"];
+            [[NSUserDefaults standardUserDefaults] synchronize];
+        }
+        [weakSelf endSoundCheck];
+    }];
+    self.checkPitch = self.checkView.pitch;
+    self.checkView.frame = CGRectMake(0, 0, KLandscapeWidth, KLandscapeHeight);
+    [self.view addSubview:self.checkView];
+}
+
+/* 结束校音*/
+- (void)endSoundCheck {
+    // 结束校音
+    self.isSoundCheckStart = NO;
+    [self stopRecordService];
+}
+
+/* 上传视频*/
+- (void)videoUpdate {
+    
+    if (self.videoRecordManager) {
+        MJWeakSelf;
+        [self.videoRecordManager uploadRecordVideoSuccess:^(NSString * _Nonnull videoUrl) {
+            if (videoUrl) {
+                [weakSelf sendUploadMessage:videoUrl];
+            }
+        } failure:^(NSString * _Nonnull desc) {
+            [weakSelf MBPShow:@"视频上传失败"];
+        }];
+    }
+}
+
+- (void)sendUploadMessage:(NSString *)videoUrl {
+    NSDictionary *header = @{@"commond" : @"videoUpdate",
+                             @"type" : @"SOUND_COMPARE",
+                             @"status" : @(200),
+    };
+    NSMutableDictionary *body = [NSMutableDictionary dictionary];
+    [body setValue:@(self.recordId) forKey:@"recordId"];
+    if ([NSString isEmptyString:videoUrl]) {
+        [body setValue:@(self.recordId) forKey:@"recordId"];
+    }
+        
+    NSMutableDictionary *content = [NSMutableDictionary dictionary];
+    [content setValue:header forKey:@"header"];
+    [content setValue:body forKey:@"body"];
+    [content setValue:@"1638175609911244308" forKey:@"uuid"];
+    
+    NSString *sendData = [content mj_JSONString];
+    [self sendDataToSocketService:sendData];
+    [self MBPShow:@"上传成功"];
+}
+
+- (void)dealloc {
+    [[NSNotificationCenter defaultCenter] removeObserver:self];
+    NSLog(@"------- cloud dealloc");
+}
+
+- (TrackChooseView *)trackChooseView {
+    if (!_trackChooseView) {
+        _trackChooseView = [TrackChooseView shareInstanceWithFullTrackArray:self.playerEngine.instrumentTrackNameArray];
+        MJWeakSelf;
+        [_trackChooseView chooseTrackCallback:^(NSMutableArray * _Nullable trackNameArray) {
+            [weakSelf operationWithTrackNameArray:trackNameArray];
+        }];
+    }
+    return _trackChooseView;
+}
+
+- (void)operationWithTrackNameArray:(NSMutableArray *)trackNameArray {
+    [self.playerEngine muteTrackWithTrackNameExclude:trackNameArray];
+}
+
+/*
+#pragma mark - Navigation
+
+// In a storyboard-based application, you will often want to do a little preparation before navigation
+- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
+    // Get the new view controller using [segue destinationViewController].
+    // Pass the selected object to the new view controller.
+}
+*/
+
+@end

+ 19 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/MidiPlayer/CAudioUnit.h

@@ -0,0 +1,19 @@
+//
+//  CAudioUnit.h
+//  MidiPlayer
+//
+//  Created by Kyle on 2021/11/30.
+//
+
+#import <Foundation/Foundation.h>
+#import <AudioUnit/AudioUnit.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface CAudioUnit : NSObject
+
+@property (readwrite) AudioUnit audioUnit;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 12 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/MidiPlayer/CAudioUnit.m

@@ -0,0 +1,12 @@
+//
+//  CAudioUnit.m
+//  MidiPlayer
+//
+//  Created by Kyle on 2021/11/30.
+//
+
+#import "CAudioUnit.h"
+
+@implementation CAudioUnit
+
+@end

+ 261 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/MidiPlayer/CoreAudioUtils.c

@@ -0,0 +1,261 @@
+//
+//  CoreAudioUtils.c
+//  MidiPlayer
+//
+//  Created by Kyle on 2021/11/30.(copy from Chris Adamson)
+//  Originally Created by Chris Adamson on 11/8/11.
+//  Copyright (c) 2011 Subsequently and Furthermore, Inc. All rights reserved.
+
+#include <stdio.h>
+#include "CoreAudioUtils.h"
+
+// generic error handler - if err is nonzero, prints error message and exits program.
+
+void CheckError(OSStatus error, const char *operation) {
+    if (error == noErr) {
+        return;
+    }
+    
+    char str[20];
+    // see if it appears to be a 4-char-code
+    *(UInt32 *)(str + 1) = CFSwapInt32HostToBig(error);
+    if (isprint(str[1]) && isprint(str[2]) && isprint(str[3]) && isprint(str[4])) {
+        str[0] = str[5] = '\'';
+        str[6] = '\0';
+    } else {
+        // no, format it as an integer
+        sprintf(str, "%d", (int)error);
+    }
+    fprintf(stderr, "Error: %s (%s)\n", operation, str);
+    
+    // from Audio Unit Processing Graph Services Reference
+    switch(error) {
+        case kAUGraphErr_NodeNotFound:
+            fprintf(stderr, "Error:kAUGraphErr_NodeNotFound \n");
+            break;
+        case kAUGraphErr_OutputNodeErr:
+            fprintf(stderr, "Error:kAUGraphErr_OutputNodeErr \n");
+            break;
+        case kAUGraphErr_InvalidConnection:
+            fprintf(stderr, "Error:kAUGraphErr_InvalidConnection \n");
+            break;
+        case kAUGraphErr_CannotDoInCurrentContext:
+            fprintf(stderr, "Error:kAUGraphErr_CannotDoInCurrentContext \n");
+            break;
+        case kAUGraphErr_InvalidAudioUnit:
+            fprintf(stderr, "Error:kAUGraphErr_InvalidAudioUnit \n");
+            break;
+        case kMIDIInvalidClient :
+            fprintf(stderr, "kMIDIInvalidClient ");
+            break;
+            
+        case kMIDIInvalidPort :
+            fprintf(stderr, "kMIDIInvalidPort ");
+            break;
+            
+        case kMIDIWrongEndpointType :
+            fprintf(stderr, "kMIDIWrongEndpointType");
+            break;
+            
+        case kMIDINoConnection :
+            fprintf(stderr, "kMIDINoConnection ");
+            break;
+            
+        case kMIDIUnknownEndpoint :
+            fprintf(stderr, "kMIDIUnknownEndpoint ");
+            break;
+            
+        case kMIDIUnknownProperty :
+            fprintf(stderr, "kMIDIUnknownProperty ");
+            break;
+            
+        case kMIDIWrongPropertyType :
+            fprintf(stderr, "kMIDIWrongPropertyType ");
+            break;
+            
+        case kMIDINoCurrentSetup :
+            fprintf(stderr, "kMIDINoCurrentSetup ");
+            break;
+            
+        case kMIDIMessageSendErr :
+            fprintf(stderr, "kMIDIMessageSendErr ");
+            break;
+            
+        case kMIDIServerStartErr :
+            fprintf(stderr, "kMIDIServerStartErr ");
+            break;
+            
+        case kMIDISetupFormatErr :
+            fprintf(stderr, "kMIDISetupFormatErr ");
+            break;
+            
+        case kMIDIWrongThread :
+            fprintf(stderr, "kMIDIWrongThread ");
+            break;
+            
+        case kMIDIObjectNotFound :
+            fprintf(stderr, "kMIDIObjectNotFound ");
+            break;
+            
+        case kMIDIIDNotUnique :
+            fprintf(stderr, "kMIDIIDNotUnique ");
+            break;
+            
+        case kAudioToolboxErr_InvalidSequenceType :
+            fprintf(stderr, " kAudioToolboxErr_InvalidSequenceType ");
+            break;
+            
+        case kAudioToolboxErr_TrackIndexError :
+            fprintf(stderr, " kAudioToolboxErr_TrackIndexError ");
+            break;
+            
+        case kAudioToolboxErr_TrackNotFound :
+            fprintf(stderr, " kAudioToolboxErr_TrackNotFound ");
+            break;
+            
+        case kAudioToolboxErr_EndOfTrack :
+            fprintf(stderr, " kAudioToolboxErr_EndOfTrack ");
+            break;
+            
+        case kAudioToolboxErr_StartOfTrack :
+            fprintf(stderr, " kAudioToolboxErr_StartOfTrack ");
+            break;
+            
+        case kAudioToolboxErr_IllegalTrackDestination    :
+            fprintf(stderr, " kAudioToolboxErr_IllegalTrackDestination");
+            break;
+            
+        case kAudioToolboxErr_NoSequence         :
+            fprintf(stderr, " kAudioToolboxErr_NoSequence ");
+            break;
+            
+        case kAudioToolboxErr_InvalidEventType        :
+            fprintf(stderr, " kAudioToolboxErr_InvalidEventType");
+            break;
+            
+        case kAudioToolboxErr_InvalidPlayerState    :
+            fprintf(stderr, " kAudioToolboxErr_InvalidPlayerState");
+            break;
+            
+            //        case kAudioToolboxErr_CannotDoInCurrentContext    :
+            //            fprintf(stderr, " kAudioToolboxErr_CannotDoInCurrentContext");
+            //            break;
+            
+        case kAudioUnitErr_InvalidProperty        :
+            fprintf(stderr, " kAudioUnitErr_InvalidProperty");
+            break;
+            
+        case kAudioUnitErr_InvalidParameter        :
+            fprintf(stderr, " kAudioUnitErr_InvalidParameter");
+            break;
+            
+        case kAudioUnitErr_InvalidElement        :
+            fprintf(stderr, " kAudioUnitErr_InvalidElement");
+            break;
+            
+        case kAudioUnitErr_NoConnection            :
+            fprintf(stderr, " kAudioUnitErr_NoConnection");
+            break;
+            
+        case kAudioUnitErr_FailedInitialization        :
+            fprintf(stderr, " kAudioUnitErr_FailedInitialization");
+            break;
+            
+        case kAudioUnitErr_TooManyFramesToProcess    :
+            fprintf(stderr, " kAudioUnitErr_TooManyFramesToProcess");
+            break;
+            
+        case kAudioUnitErr_InvalidFile            :
+            fprintf(stderr, " kAudioUnitErr_InvalidFile");
+            break;
+            
+        case kAudioUnitErr_FormatNotSupported        :
+            fprintf(stderr, " kAudioUnitErr_FormatNotSupported");
+            break;
+            
+        case kAudioUnitErr_Uninitialized        :
+            fprintf(stderr, " kAudioUnitErr_Uninitialized");
+            break;
+            
+        case kAudioUnitErr_InvalidScope            :
+            fprintf(stderr, " kAudioUnitErr_InvalidScope");
+            break;
+            
+        case kAudioUnitErr_PropertyNotWritable        :
+            fprintf(stderr, " kAudioUnitErr_PropertyNotWritable");
+            break;
+            
+        case kAudioUnitErr_InvalidPropertyValue        :
+            fprintf(stderr, " kAudioUnitErr_InvalidPropertyValue");
+            break;
+            
+        case kAudioUnitErr_PropertyNotInUse        :
+            fprintf(stderr, " kAudioUnitErr_PropertyNotInUse");
+            break;
+            
+        case kAudioUnitErr_Initialized            :
+            fprintf(stderr, " kAudioUnitErr_Initialized");
+            break;
+            
+        case kAudioUnitErr_InvalidOfflineRender        :
+            fprintf(stderr, " kAudioUnitErr_InvalidOfflineRender");
+            break;
+            
+        case kAudioUnitErr_Unauthorized            :
+            fprintf(stderr, " kAudioUnitErr_Unauthorized");
+            break;
+    }
+//    exit(1);
+}
+
+/*
+ core midi errors
+ kMIDIInvalidClient        = -10830,
+ kMIDIInvalidPort        = -10831,
+ kMIDIWrongEndpointType    = -10832,
+ kMIDINoConnection        = -10833,
+ kMIDIUnknownEndpoint    = -10834,
+ kMIDIUnknownProperty    = -10835,
+ kMIDIWrongPropertyType    = -10836,
+ kMIDINoCurrentSetup        = -10837,
+ kMIDIMessageSendErr        = -10838,
+ kMIDIServerStartErr        = -10839,
+ kMIDISetupFormatErr        = -10840,
+ kMIDIWrongThread        = -10841,
+ kMIDIObjectNotFound        = -10842,
+ kMIDIIDNotUnique        = -10843
+ 
+ toolbox
+ kAudioToolboxErr_InvalidSequenceType        = -10846,
+ kAudioToolboxErr_TrackIndexError             = -10859,
+ kAudioToolboxErr_TrackNotFound                = -10858,
+ kAudioToolboxErr_EndOfTrack                    = -10857,
+ kAudioToolboxErr_StartOfTrack                = -10856,
+ kAudioToolboxErr_IllegalTrackDestination    = -10855,
+ kAudioToolboxErr_NoSequence                 = -10854,
+ kAudioToolboxErr_InvalidEventType            = -10853,
+ kAudioToolboxErr_InvalidPlayerState            = -10852,
+ 
+these are dupes
+ kAudioToolboxErr_CannotDoInCurrentContext    = -10863
+ kAudioUnitErr_CannotDoInCurrentContext    = -10863,
+ 
+ au
+ kAudioUnitErr_InvalidProperty            = -10879,
+ kAudioUnitErr_InvalidParameter            = -10878,
+ kAudioUnitErr_InvalidElement            = -10877,
+ kAudioUnitErr_NoConnection                = -10876,
+ kAudioUnitErr_FailedInitialization        = -10875,
+ kAudioUnitErr_TooManyFramesToProcess    = -10874,
+ kAudioUnitErr_InvalidFile                = -10871,
+ kAudioUnitErr_FormatNotSupported        = -10868,
+ kAudioUnitErr_Uninitialized                = -10867,
+ kAudioUnitErr_InvalidScope                = -10866,
+ kAudioUnitErr_PropertyNotWritable        = -10865,
+ kAudioUnitErr_CannotDoInCurrentContext    = -10863,
+ kAudioUnitErr_InvalidPropertyValue        = -10851,
+ kAudioUnitErr_PropertyNotInUse            = -10850,
+ kAudioUnitErr_Initialized                = -10849,
+ kAudioUnitErr_InvalidOfflineRender        = -10848,
+ kAudioUnitErr_Unauthorized                = -10847
+ */

+ 15 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/MidiPlayer/CoreAudioUtils.h

@@ -0,0 +1,15 @@
+//
+//  CoreAudioUtils.h
+//  MidiPlayer
+//
+//  Created by Kyle on 2021/11/30.
+//
+
+#ifndef CoreAudioUtils_h
+#define CoreAudioUtils_h
+
+#import <AudioToolbox/AudioToolbox.h>
+
+void CheckError(OSStatus error, const char *operation);
+
+#endif /* CoreAudioUtils_h */

+ 24 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/MidiPlayer/GCDTimer.h

@@ -0,0 +1,24 @@
+//
+//  GCDTimer.h
+//  MidiPlayer
+//
+//  Created by Kyle on 2021/11/30.
+//
+
+#import <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface GCDTimer : NSObject
+
++(GCDTimer*)shared;
+
+-(void)scheduleGCDTimerWithName:(NSString*)timerName Interval:(uint64_t)interval Queue:(dispatch_queue_t)queue Repeats:(BOOL)repeats  Action:(dispatch_block_t)action;
+
+-(void)cancelTimerWithName:(NSString*)name;
+
+-(void)cancelAllTimer;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 96 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/MidiPlayer/GCDTimer.m

@@ -0,0 +1,96 @@
+//
+//  GCDTimer.m
+//  MidiPlayer
+//
+//  Created by Kyle on 2021/11/30.
+//
+
+#import "GCDTimer.h"
+
+@interface GCDTimer ()
+
+@property (nonatomic, strong) NSMutableDictionary *timerContrainer;
+
+@end
+
+@implementation GCDTimer
+
++ (GCDTimer *)shared {
+    
+    static GCDTimer *shared;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        shared = [[GCDTimer alloc] init];
+    });
+    return shared;
+}
+
+- (void)scheduleGCDTimerWithName:(NSString *)timerName Interval:(uint64_t)interval Queue:(dispatch_queue_t)queue Repeats:(BOOL)repeats Action:(dispatch_block_t)action {
+    
+    if (timerName == nil) {
+        return;
+    }
+    
+    if (queue == nil) {
+        queue = dispatch_queue_create("ksTimer", DISPATCH_QUEUE_SERIAL);
+    }
+    dispatch_source_t timer = [self.timerContrainer objectForKey:timerName];
+    if (!timer) {
+        
+        timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
+        [self.timerContrainer setObject:timer forKey:timerName];
+        // 启动定时器
+        dispatch_resume(timer);
+    }
+    
+    /*
+    第一个参数:定时器对象
+    第二个参数:DISPATCH_TIME_NOW 表示从现在开始计时
+    第三个参数:间隔时间 GCD里面的时间最小单位为 纳秒, 以毫秒为单位
+    第四个参数:精准度(表示允许的误差,0表示绝对精准)
+    */
+    dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, interval * NSEC_PER_MSEC), interval * NSEC_PER_MSEC, 0);
+    
+    __weak typeof(self) weakSelf = self;
+    dispatch_source_set_event_handler(timer, ^{
+        action();
+        if (!repeats) {
+            [weakSelf cancelTimerWithName:timerName];
+        }
+    });
+}
+/*  创建GCD定时器的方法
+     dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, <#dispatchQueue#>);
+     dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, <#intervalInSeconds#> * NSEC_PER_SEC, <#leewayInSeconds#> * NSEC_PER_SEC);
+     dispatch_source_set_event_handler(timer, ^{
+         <#code to be executed when timer fires#>
+     });
+     dispatch_resume(timer);
+ */
+
+- (NSMutableDictionary *)timerContrainer {
+    
+    if (!_timerContrainer) {
+        _timerContrainer = [[NSMutableDictionary alloc] init];
+    }
+    return _timerContrainer;
+}
+
+- (void)cancelTimerWithName:(NSString *)name {
+    dispatch_source_t timer = [self.timerContrainer objectForKey:name];
+    if (!timer) {
+        return;
+    }
+    [self.timerContrainer removeObjectForKey:name];
+    dispatch_source_cancel(timer);
+    
+}
+
+- (void)cancelAllTimer {
+    
+    [self.timerContrainer enumerateKeysAndObjectsUsingBlock:^(NSString* timerName, dispatch_source_t timer, BOOL * _Nonnull stop) {
+        [self.timerContrainer removeObjectForKey:timerName];
+        dispatch_source_cancel(timer);
+    }];
+}
+@end

+ 150 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/MidiPlayer/MidiPlayerEngine.h

@@ -0,0 +1,150 @@
+//
+//  MidiPlayerEngine.h
+//  MidiPlayer
+//
+//  Created by Kyle on 2021/11/30.
+//
+
+#import <Foundation/Foundation.h>
+#import <AudioToolbox/AudioToolbox.h>
+#import "CAudioUnit.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@protocol PlayerEngineDelegate <NSObject>
+
+/// 加载成功回调
+- (void)initPlayerEngineSuccess:(float)totalTime;
+
+/*    范围:[0,1],每10ms更新一次   */
+- (void)ProgressUpdated:(float)progress currentTime:(MusicTimeStamp)currentTime;
+
+/* 播放结束回调 */
+- (void)playEnd;
+
+/* 歌曲数据加载完,可返回歌曲总时长  */
+- (void)GetMusicTotalTime:(float)time;
+
+@end
+
+
+@interface MidiPlayerEngine : NSObject
+
+@property (nonatomic, strong) NSMutableArray *muteTrackArray; // 需要禁音的track
+
+@property (nonatomic, assign) float accompanyVolume; // 伴奏音量配置 默认0.2
+
+@property (nonatomic, assign) Float64 baseRate; // 基准rate
+// 主要乐器id
+@property (nonatomic, assign) NSInteger instrumentId; // 主乐器id
+
+@property (nonatomic, assign) UInt32 baseInstrumentTrack; // 主乐器轨道
+
+@property (nonatomic, strong) NSMutableArray *baseInstrumentArray; // 主乐器轨道
+
+// 定义汇报进度频率 默认10ms
+@property (nonatomic, assign) NSInteger reportTime;
+
+@property (nonatomic, strong) NSMutableArray *instrumentTrackNameArray; // 每个track 对应的名称
+
+
+/* 选段播放控制 */
+
+// 选段的开始时间
+@property (nonatomic, assign) MusicTimeStamp startTime;
+// 选段的结束时间
+@property (nonatomic, assign) MusicTimeStamp endTime;
+
+@property (nonatomic,weak)id<PlayerEngineDelegate> delegate;
+
+- (void)configSoundFilePath:(NSString *)filePath;
+
+/*  加载MIDI文件  */
+- (BOOL)loadMIDIFileWithString:(NSString *)filePath;
+
+/* 根据轨道名称获取单个轨道的编号*/
+- (NSInteger)getSingleTrackNumByName:(NSString *)trackName;
+/* 通过轨道名称获取对应的轨道*/
+- (NSMutableArray *)getTrackNumerFromName:(NSArray *)trackNameArray;
+
+- (void)getAllTrackVolume;
+
+/*  播放 */
+- (void)playMIDIFile;
+/*  暂停 */
+- (void)stopPlayingMIDIFile;
+
+/* 清空加载的音乐数据 */
+- (void)cleanup;
+
+/* 获取当前音乐的播放时间,单位:秒 */
+- (double)musicTotalTime;
+
+/* 获取当前音乐的播放时间,单位:秒 */
+- (double)musicCurrentTime;
+
+/* 播放器当前状态 */
+- (BOOL)isPlayingFile;
+
+/*  设置播放速度,快播:(1,+∞),慢放:(0,1)   */
+- (BOOL)setMusicPlayerSpeed:(double)speed;
+
+/* 设置播放进度 progress的设置范围[0,1]  精度会有影响*/
+- (void)setProgress:(double)progress;
+/* 设置播放的进度开始时间  */
+- (void)setProgressTime:(MusicTimeStamp)startTime;
+
+/* 屏蔽具体一种乐器的声音。注意:屏蔽操作需要在加载MIDI文件之前调用!!!  */ //
+- (void)muteWithInstrument:(UInt8)instrument;
+
+/* 屏蔽鼓组。注意:屏蔽操作需要在加载MIDI文件之前调用!!! */
+- (void)muteWithDrum;
+
+/*  調節某一軌的音量  範圍:[0,1] */
+- (void)volume:(float)volume WithTrack:(UInt32)trackNum;
+
+/* 调整除了 trackNum 之外的 其他轨道的音量 范围[0,1]*/
+- (void)volumeOtherTrackExcept:(UInt32)trackNum withVolume:(float)volume;
+
+/* 屏蔽某些轨道的的声音 可在进行中操作 (用于原声和伴奏的切换)*/
+- (void)muteTrack:(BOOL)isMute WithTrack:(NSMutableArray *)trackArray;
+
+- (void)muteTrackWithTrackNameExclude:(NSMutableArray *)trackNameArray;
+
+// 恢复AUGraph
+- (void)resumeAUGraph;
+
+#pragma mark ------ 变调 调整hz
+// 转成442HZ播放
+- (void)transformTo442Hz;
+
+// hz调整 440 - 442。。。。。。。
+- (void)adjustPitchByHZ:(float)newHZ;
+
+
+#pragma mark ---- 自动循环功能和播放时同步节拍器暂未实现
+// 是否重复播放
+@property (nonatomic, assign) BOOL isLooping;
+// 是否伴随节拍器
+@property (nonatomic, getter=isClickTrackEnabled) BOOL clickTrackEnabled;
+
+// 设置自动循环播放
+- (void)startPlaybackLoop;
+
+/// Starts playback of the music sequence from the specified position.
+/// @param position The MusicTimeStamp to begin playback from.
+- (void)startPlaybackFromPosition:(MusicTimeStamp)position;
+
+/**
+ *  Resumes playback of the music sequence from the MusicTimeStamp that the player last stopped at.
+ */
+- (void)resumePlayback;
+
+/**
+ *  Stops playback of the music seuqenece.
+ */
+- (void)stopPlayback;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 1006 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/MidiPlayer/MidiPlayerEngine.m

@@ -0,0 +1,1006 @@
+//
+//  MidiPlayerEngine.m
+//  MidiPlayer
+//
+//  Created by Kyle on 2021/11/30.
+//
+
+#import "MidiPlayerEngine.h"
+#import <AVFoundation/AVFoundation.h>
+#import <CoreAudio/CoreAudioTypes.h>
+#import "CoreAudioUtils.h"
+#import "GCDTimer.h"
+
+@interface MidiPlayerEngine ()
+
+@property (readwrite) AUNode pitchNode;
+@property (readwrite) AudioUnit pitchUnit;
+@property (readwrite) AUGraph processingGraph;
+@property (readwrite) AUNode samplerNode;
+@property (readwrite) AUNode ioNode;
+@property (readwrite) AUNode mixerNode;
+@property (readwrite) AudioUnit samplerUnit;
+@property (readwrite) AudioUnit mixerUnit;
+@property (readwrite) AudioUnit ioUnit;
+@property (nonatomic) NSMutableArray* samplerNodeList;
+@property (nonatomic) NSMutableArray* samplerUnitList;
+@property (nonatomic) MusicPlayer musicPlayer;
+@property (nonatomic) MusicSequence musicSequence;
+@property (nonatomic) MusicTrack musicTrack;
+@property (nonatomic) UInt32 trackCount;
+@property (nonatomic) BOOL playing;
+@property (readwrite) double totalTime;
+@property (nonatomic) double currentTime;
+@property (nonatomic) Boolean isMute;
+@property (nonatomic) Boolean muteDrum;
+@property (nonatomic) NSMutableArray* instrumentArray;
+@property (nonatomic) double timeRatio;
+
+@property (nonatomic, strong) NSString *soundFileUrl;
+
+@property (nonatomic, strong) MidiPlayerEngine *clickPlayer;
+
+@property (nonatomic, assign) BOOL isClickPlayer;
+@property (nonatomic, assign) MusicTimeStamp trackLength;
+
+@end
+
+@implementation MidiPlayerEngine
+
+- (instancetype)init {
+    self = [super init];
+    if (self) {
+        [self configDefault];
+    }
+    return self;
+}
+
+- (void)configDefault {
+    self.isMute = NO;
+    self.muteDrum = NO;
+    self.baseRate = 120.0f;
+    self.timeRatio = 1.0;
+    self.reportTime = 10;
+    self.soundFileUrl = [[NSBundle mainBundle]
+                         pathForResource:@"synthgms" ofType:@"sf2"];
+    self.accompanyVolume = 0.2f;
+}
+
+- (void)configSoundFilePath:(NSString *)filePath {
+    self.soundFileUrl = filePath;
+}
+
+#pragma mark ---- audio setup
+- (BOOL)createAUGraph {
+    CheckError(NewAUGraph(&_processingGraph), "NewAUGraph");
+    
+    /*
+     * 创建节点
+     * 1.添加音频器附件描述
+     * 2.音频处理图根据器件描述生成1个节点
+     * 节点数= track数
+     */
+    AudioComponentDescription desc = {0};
+    desc.componentType = kAudioUnitType_MusicDevice;
+    /* MIDI合成器,多声道附件*/
+    desc.componentSubType = kAudioUnitSubType_MIDISynth;
+    desc.componentManufacturer = kAudioUnitManufacturer_Apple;
+    desc.componentFlags = 0;
+    desc.componentFlagsMask = 0;
+    for (int i = 0; i < self.trackCount; i++) {
+        CheckError(AUGraphAddNode(self.processingGraph, &desc, &_samplerNode), "AUGraphAddNode");
+        [self.samplerNodeList addObject:[NSString stringWithFormat:@"%d", (int)_samplerNode]];
+        
+    }
+    /*
+     *  创建混合器节点
+     */
+    AudioComponentDescription mixerUnitDescription = {0};
+    mixerUnitDescription.componentType          = kAudioUnitType_Mixer;
+    mixerUnitDescription.componentSubType       = kAudioUnitSubType_MultiChannelMixer;
+    mixerUnitDescription.componentManufacturer  = kAudioUnitManufacturer_Apple;
+    mixerUnitDescription.componentFlags         = 0;
+    mixerUnitDescription.componentFlagsMask     = 0;
+    
+    CheckError(AUGraphAddNode(self.processingGraph,
+                              &mixerUnitDescription,
+                              &_mixerNode),
+               "AUGraphAddNode");
+    // I/O unit
+    /*
+     创建输出节点
+     */
+    AudioComponentDescription iOUnitDescription = {0};
+    iOUnitDescription.componentType          = kAudioUnitType_Output;
+    iOUnitDescription.componentSubType       = kAudioUnitSubType_RemoteIO;
+    iOUnitDescription.componentManufacturer  = kAudioUnitManufacturer_Apple;
+    iOUnitDescription.componentFlags         = 0;
+    iOUnitDescription.componentFlagsMask     = 0;
+    
+    CheckError(AUGraphAddNode(self.processingGraph,
+                              &iOUnitDescription,
+                              &_ioNode),
+               "AUGraphAddNode");
+    
+    // 变调
+    AudioComponentDescription pitchDesc = {0};
+    pitchDesc.componentType = kAudioUnitType_FormatConverter;
+    pitchDesc.componentSubType = kAudioUnitSubType_NewTimePitch;
+    pitchDesc.componentManufacturer = kAudioUnitManufacturer_Apple;
+    pitchDesc.componentFlags = 0;
+    pitchDesc.componentFlagsMask = 0;
+    AUGraphAddNode(self.processingGraph, &pitchDesc, &_pitchNode);
+    
+    
+    /*
+     打开AUGraph才能调用AUGraphNodeInfo获取节点对应的audio unit
+     */
+    CheckError(AUGraphOpen(self.processingGraph), "AUGraphOpen");
+    
+    
+    /*
+     保存sampler对应的audio unit
+     */
+    for(int i = 0; i< self.trackCount; i++){
+        CAudioUnit* sampleUnit = [[CAudioUnit alloc] init];
+        AudioUnit audioUnit = sampleUnit.audioUnit;
+        CheckError(AUGraphNodeInfo(self.processingGraph,
+                                   [self.samplerNodeList[i] intValue],
+                                   NULL,
+                                   &audioUnit),
+                   "AUGraphNodeInfo");
+        [self configMaxFramesPerSliceWithAudioUnit:audioUnit];
+        sampleUnit.audioUnit = audioUnit;
+        [self.samplerUnitList addObject:sampleUnit];
+        
+        
+        NSURL *bankURL;
+        /*
+         音色库
+         */
+        bankURL = [[NSURL alloc] initFileURLWithPath:self.soundFileUrl];
+        
+        // load sound bank
+        CheckError(AudioUnitSetProperty(audioUnit,
+                                        kMusicDeviceProperty_SoundBankURL,
+                                        kAudioUnitScope_Global,
+                                        0,
+                                        &bankURL,
+                                        sizeof(bankURL)),
+                   "kAUSamplerProperty_LoadPresetFromBank");
+    }
+    
+    /*
+     保存mixer对应的audio unit
+     */
+    CheckError(AUGraphNodeInfo(self.processingGraph,
+                               self.mixerNode,
+                               NULL,
+                               &_mixerUnit),
+               "AUGraphNodeInfo");
+    [self configMaxFramesPerSliceWithAudioUnit:_mixerUnit];
+    /*
+     保存输出对应的audio unit
+     */
+    CheckError(AUGraphNodeInfo(self.processingGraph,
+                               self.ioNode,
+                               NULL,
+                               &_ioUnit),
+               "AUGraphNodeInfo");
+    
+    [self configMaxFramesPerSliceWithAudioUnit:_ioUnit];
+    
+    /*
+     设置mixer输入的数量
+     */
+    UInt32 busCount = self.trackCount;
+    //set the mixer unit`s bus count
+    CheckError(AudioUnitSetProperty(_mixerUnit,
+                                    kAudioUnitProperty_ElementCount,
+                                    kAudioUnitScope_Input,
+                                    0,
+                                    &busCount,
+                                    sizeof(busCount)),
+               "AudioUnitSetProperty_SetMixerInputCount");
+
+        /*
+            设置mixer的采样率,44.1kHz是标准采样率
+         */
+
+        Float64 graphSampleRate = 44100.0;
+        //set mixer unit`s output sample rate format
+        CheckError(AudioUnitSetProperty(_mixerUnit,
+                                        kAudioUnitProperty_SampleRate,
+                                        kAudioUnitScope_Output,
+                                        0,
+                                        &graphSampleRate,
+                                        sizeof(graphSampleRate)),
+                   "AudioUnitSetProperty_SetMixerSampleRate");
+
+    
+    AudioUnitParameterValue defaultOutputVolume = 1.0;
+    CheckError(AudioUnitSetParameter(_mixerUnit, kMultiChannelMixerParam_Volume, kAudioUnitScope_Output, 0, defaultOutputVolume, 0), "AudioUnitSetParameter(kMultiChannelMixerParam_Volume)");
+    
+    
+    //connect samplers to mixer unit
+    /*
+     将sampler unit的输出连到mixer的输入
+     */
+    for(int i = 0;i < self.trackCount;i++) {
+        CheckError(AUGraphConnectNodeInput(self.processingGraph,
+                                           [self.samplerNodeList[i] intValue],
+                                           0,
+                                           self.mixerNode,
+                                           i),
+                   "AUGraphConnectNodeInput_ConnectSamplersToMixerUnit");
+    }
+    
+    
+    /*
+     保存pitchNode对应的audio unit
+     */
+    AUGraphNodeInfo(self.processingGraph, self.pitchNode, &pitchDesc, &_pitchUnit);
+    [self configMaxFramesPerSliceWithAudioUnit:_pitchUnit];
+    
+    AUGraphConnectNodeInput(self.processingGraph, self.mixerNode, 0, self.pitchNode, 0);
+    
+    //connect pitch  to output unit
+    /*
+     将pitch的输出连到remote I/O的输入
+     */
+    CheckError(AUGraphConnectNodeInput(self.processingGraph,
+                                       self.pitchNode,
+                                       0,
+                                       self.ioNode,
+                                       0),
+               "AUGraphConnectNodeInput_ConnectMixerToOutput");
+    
+    
+    
+    NSLog (@"AUGraph is configured");
+    
+    return YES;
+}
+
+- (void)configMaxFramesPerSliceWithAudioUnit:(AudioUnit)audioUnit {
+    AVAudioFrameCount maxFramesPerSlice = 4096;
+    CheckError(AudioUnitSetProperty(audioUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, 0, &maxFramesPerSlice, sizeof(maxFramesPerSlice)),"kAudioUnitProperty_MaximumFramesPerSlice");
+}
+
+- (void)startGraph {
+    /*
+       音频处理图的初始化和开启
+     */
+    if (self.processingGraph) {
+
+        Boolean outIsInitialized;
+        CheckError(AUGraphIsInitialized(self.processingGraph,
+                                        &outIsInitialized), "AUGraphIsInitialized");
+        if(!outIsInitialized)
+            CheckError(AUGraphInitialize(self.processingGraph), "AUGraphInitialize");
+        
+        Boolean isRunning;
+        CheckError(AUGraphIsRunning(self.processingGraph,
+                                    &isRunning), "AUGraphIsRunning");
+        if(!isRunning)
+            CheckError(AUGraphStart(self.processingGraph), "AUGraphStart");
+    }
+}
+
+- (void)stopAUGraph {
+    
+    NSLog (@"Stopping audio processing graph");
+    Boolean isRunning = false;
+    CheckError(AUGraphIsRunning (self.processingGraph, &isRunning), "AUGraphIsRunning");
+    
+    if (isRunning) {
+        CheckError(AUGraphStop(self.processingGraph), "AUGraphStop");
+        self.playing = NO;
+    }
+}
+
+- (void)setupAudioSession {
+    
+    AVAudioSession *audioSession = [AVAudioSession sharedInstance];
+    NSError *err = nil;
+    AVAudioSessionCategory category = AVAudioSessionCategoryPlayAndRecord;
+    if (@available(iOS 10.0, *)) {
+        [audioSession setCategory:category mode:AVAudioSessionModeDefault options:AVAudioSessionCategoryOptionAllowBluetooth|AVAudioSessionCategoryOptionAllowBluetoothA2DP|AVAudioSessionCategoryOptionDefaultToSpeaker|AVAudioSessionCategoryOptionMixWithOthers error:&err];
+    }
+    else {
+        [audioSession setCategory:category withOptions:AVAudioSessionCategoryOptionAllowBluetooth|AVAudioSessionCategoryOptionDefaultToSpeaker|AVAudioSessionCategoryOptionMixWithOthers error:&err];
+    }
+    [audioSession setActive:YES error:&err];
+}
+
+- (void)resumeAUGraph {
+    if (self.processingGraph) {
+        [self stopAUGraph];
+        Boolean isRunning;
+        CheckError(AUGraphIsRunning(self.processingGraph,
+                                    &isRunning), "AUGraphIsRunning");
+        if(!isRunning) {
+            CheckError(AUGraphStart(self.processingGraph), "AUGraphStart");
+        }
+    }
+}
+
+#pragma mark --- Audio control
+- (BOOL)loadMIDIFileWithString:(NSString *)filePath {
+    // 判断文件是否存在
+    BOOL isExist = [[NSFileManager defaultManager] fileExistsAtPath:filePath];
+    if (isExist) {
+        NSURL* midiFileURL = [[NSURL alloc] initFileURLWithPath:filePath];
+        //新建MusicSequence
+        CheckError(NewMusicSequence(&_musicSequence), "NewMusicSequence");
+        NSData *midi_data = [NSData dataWithContentsOfURL:midiFileURL];
+        //把文件加载到MusicSequence
+        NSLog(@"----- start load file");
+        // 提高加载速度 kMusicSequenceLoadSMF_ChannelsToTracks 会合并轨道 导致轨道速可能减少
+        CheckError(MusicSequenceFileLoadData(self.musicSequence, (__bridge CFDataRef)midi_data, kMusicSequenceFile_MIDIType, kMusicSequenceLoadSMF_PreserveTracks), "MusicSequenceFileLoad");
+//        NSLog(@"----- end load file");
+//        CheckError(MusicSequenceFileLoad(self.musicSequence,
+//                                         (__bridge CFURLRef) midiFileURL,
+//                                         0,
+//                                         kMusicSequenceLoadSMF_ChannelsToTracks), "MusicSequenceFileLoad");
+        NSLog(@"----- end load file");
+        
+        //读取MIDI文件轨道数量
+        CheckError(MusicSequenceGetTrackCount(self.musicSequence, &_trackCount), "MusicSequenceGetTrackCount");
+
+        NSLog(@"track ----%d",self.trackCount);
+        [self createAUGraph];
+        [self startGraph];
+        
+        CheckError(NewMusicPlayer(&_musicPlayer), "NewMusicPlayer");
+        
+        CheckError(MusicPlayerSetSequence(self.musicPlayer, self.musicSequence), "MusicPlayerSetSequence");
+        
+        //关联Sequence和AUGraph
+        CheckError(MusicSequenceSetAUGraph(self.musicSequence, self.processingGraph),
+        "MusicSequenceSetAUGraph");
+        
+        // 获取速度
+        [self getTempoMessage];
+        
+        MusicTrack track;
+        MusicTimeStamp maxTrackLength = 0;
+        self.instrumentTrackNameArray = [NSMutableArray array];
+        self.baseInstrumentArray = [NSMutableArray array];
+        for (int i = 0; i < self.trackCount; i++) {
+            CheckError(MusicSequenceGetIndTrack (self.musicSequence, i, &track), "MusicSequenceGetIndTrack");
+            
+            MusicTimeStamp track_length;
+            UInt32 tracklength_size = sizeof(MusicTimeStamp);
+            CheckError(MusicTrackGetProperty(track, kSequenceTrackProperty_TrackLength, &track_length, &tracklength_size), "kSequenceTrackProperty_TrackLength");
+
+            MusicTimeStamp track_offset = 0;
+            UInt32 trackoffset_size = sizeof(track_offset);
+            CheckError(MusicTrackGetProperty(track, kSequenceTrackProperty_OffsetTime, &track_offset, &trackoffset_size), "kSequenceTrackProperty_OffsetTime_TrackOffset");
+            
+            MusicTimeStamp trackLength = track_length + track_offset;
+            if(trackLength>maxTrackLength) maxTrackLength = trackLength;
+            
+            MusicTrackLoopInfo loopInfo;
+            UInt32 lisize = sizeof(MusicTrackLoopInfo);
+            CheckError(MusicTrackGetProperty(track,kSequenceTrackProperty_LoopInfo, &loopInfo, &lisize ), "kSequenceTrackProperty_LoopInfo");
+            NSLog(@"Loop info: duration %f", loopInfo.loopDuration);
+            
+            [self iterate:track TrackIndex:i];
+            
+            CheckError(MusicTrackSetDestNode(track, [self.samplerNodeList[i] intValue]), "MusicTrackSetDestNode");
+        }
+        self.trackLength = maxTrackLength;
+        // 获取总时长
+        CheckError(MusicSequenceGetSecondsForBeats(self.musicSequence, maxTrackLength, &_totalTime), "MusicSequenceGetSecondsForBeats");
+        NSLog(@"Music total time is: %f",self.totalTime);
+        
+        __weak typeof(self) weakSelf=self;
+        dispatch_async(dispatch_get_main_queue(), ^{
+            if([weakSelf.delegate respondsToSelector:@selector(GetMusicTotalTime:)]){
+                [weakSelf.delegate GetMusicTotalTime:weakSelf.totalTime];
+            }
+        });
+        
+        /*
+            播放准备
+         */
+
+        self.currentTime = 0;
+        // 初始化成功回调
+        dispatch_async(dispatch_get_main_queue(), ^{
+            if (weakSelf.delegate && [weakSelf.delegate respondsToSelector:@selector(initPlayerEngineSuccess:)]) {
+                [weakSelf.delegate initPlayerEngineSuccess:weakSelf.totalTime];
+            }
+        });
+        
+        return YES;
+    }
+    else {
+        NSLog(@"文件不存在 !!!");
+        return NO;
+    }
+}
+
+- (void)getTempoMessage {
+    
+    OSStatus result = noErr;
+    MusicTrack tempoTrack;
+    result = MusicSequenceGetTempoTrack(self.musicSequence, &tempoTrack);
+    if (noErr != result) { // 未获取到tempo轨道
+        NSLog(@"MusicSequenceGetTempoTrack, %d", (int)result);
+        return;
+    }
+    
+    MusicEventIterator iterator;
+    CheckError(NewMusicEventIterator(tempoTrack, &iterator), "NewMusicEventIterator");
+    
+    MusicEventType eventType;
+    MusicTimeStamp eventTimeStamp;
+    UInt32 eventDataSize;
+    const void *eventData;
+    Boolean hasCurrentEvent = NO;
+    
+    CheckError(MusicEventIteratorHasCurrentEvent(iterator, &hasCurrentEvent), "MusicEventIteratorHasCurrentEvent");
+    
+    while (hasCurrentEvent) {
+        MusicEventIteratorGetEventInfo(iterator, &eventTimeStamp, &eventType, &eventData, &eventDataSize);
+        switch (eventType) {
+            case kMusicEventType_ExtendedTempo:  // 速度
+            {
+                ExtendedTempoEvent *ext_tempo_evt = (ExtendedTempoEvent*)eventData;
+                NSLog(@"ExtendedTempoEvent, bpm %f", ext_tempo_evt->bpm);
+                self.baseRate = ext_tempo_evt->bpm;
+            }
+                break;
+                
+            default:
+                break;
+        }
+        CheckError(MusicEventIteratorHasNextEvent(iterator, &hasCurrentEvent), "MusicEventIteratorHasCurrentEvent");
+        CheckError(MusicEventIteratorNextEvent(iterator), "MusicEventIteratorNextEvent");
+    }
+}
+
+
+// 读取track 的event信息
+- (void)iterate:(MusicTrack)track TrackIndex:(int)index {
+    MusicEventIterator iterator;
+    CheckError(NewMusicEventIterator(track, &iterator), "NewMusicEventIterator");
+    
+    MusicEventType eventType;
+    MusicTimeStamp eventTimeStamp;
+    UInt32 eventDataSize;
+    const void *eventData;
+    
+    Boolean hasCurrentEvent = NO;
+    CheckError(MusicEventIteratorHasCurrentEvent(iterator, &hasCurrentEvent), "MusicEventIteratorHasCurrentEvent");
+    while (hasCurrentEvent)
+    {
+        MusicEventIteratorGetEventInfo(iterator, &eventTimeStamp, &eventType, &eventData, &eventDataSize);
+//        NSLog(@"event timeStamp %f ", eventTimeStamp);
+        switch (eventType) {
+                
+            case kMusicEventType_ExtendedNote : {
+                
+            }
+                break;
+                
+            case kMusicEventType_ExtendedTempo : { // 速度
+                
+//                ExtendedTempoEvent* ext_tempo_evt = (ExtendedTempoEvent*)eventData;
+//                NSLog(@"ExtendedTempoEvent, bpm %f", ext_tempo_evt->bpm);
+//                self.baseRate = ext_tempo_evt->bpm;
+            }
+                break;
+                
+            case kMusicEventType_User : {
+//                MusicEventUserData* user_evt = (MusicEventUserData*)eventData;
+//                NSLog(@"MusicEventUserData, data length %u", (unsigned int)user_evt->length);
+            }
+                break;
+                
+            case kMusicEventType_Meta : {
+                MIDIMetaEvent* meta_evt = (MIDIMetaEvent*)eventData;
+//                NSLog(@"MIDIMetaEvent, event type %d", meta_evt->metaEventType);
+                if (meta_evt->metaEventType == 0x03) { // 音序或 track 的名称
+                    
+                    NSString *name = [[NSString alloc] initWithBytes:meta_evt->data length:meta_evt->dataLength encoding:NSUTF8StringEncoding];
+                    name = [[name componentsSeparatedByString:@","] lastObject];
+                    name = [name replaceAll:@"\0" WithString:@""];
+//                    NSLog(@"------- name -----%@",name);
+                    [self.instrumentTrackNameArray addObject:name];
+                }
+
+            }
+                break;
+                
+            case kMusicEventType_MIDINoteMessage : { // 音符信息
+//                MIDINoteMessage* note_evt = (MIDINoteMessage*)eventData;
+//                NSLog(@"note event channel %d", note_evt->channel);
+//                NSLog(@"note event note %d", note_evt->note);
+//                NSLog(@"note event duration %f", note_evt->duration);
+//                NSLog(@"note event velocity %d", note_evt->velocity);
+            }
+                break;
+                
+            case kMusicEventType_MIDIChannelMessage : { // channel message
+                MIDIChannelMessage* channel_evt = (MIDIChannelMessage*)eventData;
+                
+                if((channel_evt->status& 0xF0) == 0xC0 ) {
+                    
+//                     CHANNEL_PROGRAM_CHANGE 获取instrument id
+//                    NSLog(@"-- track index %d ------ instrument %d ", index, channel_evt->data1);
+                    
+                    if (channel_evt->data1 == self.instrumentId) {
+                        self.baseInstrumentTrack = index;
+                        [self.baseInstrumentArray addObject:@(index)];
+                    }
+                    
+                    //                    /*
+//                     静音处理
+//                     */
+//                    Boolean isSet = NO; /* 判断轨道是否设置静音 */
+//                    if (self.isMute){
+//
+//                        for (int i = 0; i < self.instrumentArray.count; i++){
+//                            int instrument = [self.instrumentArray[i] intValue];
+//                            if(channel_evt->data1 == instrument){
+//                                isSet = YES;
+//                                CheckError(MusicTrackSetProperty(track, kSequenceTrackProperty_MuteStatus, &_isMute, sizeof(_isMute)), "SetMusicTrackMute");
+//                            }
+//                        }
+//
+//                    }
+//                    if (self.muteDrum && ((channel_evt->status& 0x0F) == 9)){
+//                        isSet = YES;
+//                        CheckError(MusicTrackSetProperty(track, kSequenceTrackProperty_MuteStatus, &_muteDrum, sizeof(_muteDrum)), "SetMusicTrackMuteDrum");
+//                    }
+                }
+                else if ((channel_evt->status& 0xF0) == 0xB7) { // 音量控制
+//                    NSLog(@"channel event status %X", channel_evt->status);
+//                    NSLog(@"channel event d1 %X", channel_evt->data1);
+//                    NSLog(@"channel event d2 %X", channel_evt->data2);
+                }
+                else {
+//                    NSLog(@"channel event status %X", channel_evt->status);
+//                    NSLog(@"channel event d1 %X", channel_evt->data1);
+//                    NSLog(@"channel event d2 %X", channel_evt->data2);
+                }
+            }
+                break ;
+                
+            case kMusicEventType_MIDIRawData : {
+//                MIDIRawData* raw_data_evt = (MIDIRawData*)eventData;
+//                NSLog(@"MIDIRawData, length %lu", raw_data_evt->length);
+
+            }
+                break ;
+                
+            case kMusicEventType_Parameter : {
+//                ParameterEvent* parameter_evt = (ParameterEvent*)eventData;
+//                NSLog(@"ParameterEvent, parameterid %lu", parameter_evt->parameterID);
+
+            }
+                break ;
+                
+            default :
+                break ;
+        }
+        
+        CheckError(MusicEventIteratorHasNextEvent(iterator, &hasCurrentEvent), "MusicEventIteratorHasCurrentEvent");
+        CheckError(MusicEventIteratorNextEvent(iterator), "MusicEventIteratorNextEvent");
+    }
+}
+
+/* 根据轨道名称获取单个轨道的编号*/
+- (NSInteger)getSingleTrackNumByName:(NSString *)trackName {
+    
+    for (NSInteger i = 0; i < self.instrumentTrackNameArray.count; i++) {
+        NSString *instrumentTrackName = self.instrumentTrackNameArray[i];
+        if ([trackName isEqualToString:instrumentTrackName]) {
+            return i;
+        }
+    }
+    return -1;
+}
+
+/* 通过轨道名称获取对应的轨道*/
+- (NSMutableArray *)getTrackNumerFromName:(NSArray *)trackNameArray {
+    NSMutableArray *trackArray = [NSMutableArray array];
+    NSLog(@"%@",self.instrumentTrackNameArray);
+    for (NSString *name in trackNameArray) {
+        for (NSInteger i = 0; i < self.instrumentTrackNameArray.count; i++) {
+            NSString *instrumentTrackName = self.instrumentTrackNameArray[i];
+            // 包含name就添加对应的轨道
+            if ([name isEqualToString:instrumentTrackName]) {
+                [trackArray addObject:@(i)];
+            }
+        }
+    }
+    return trackArray;
+}
+
+- (void)muteTrackWithTrackNameExclude:(NSMutableArray *)trackNameArray {
+    NSMutableArray *unMuteArray = [self getTrackNumerFromName:trackNameArray];
+    for (UInt32 i = 0; i < self.trackCount; i++) {
+        Boolean mutedBoolean = true;
+        MusicTrack track;
+        MusicSequenceGetIndTrack(self.musicSequence, i, &track);
+        MusicTrackSetProperty(track, kSequenceTrackProperty_MuteStatus, &mutedBoolean, sizeof(mutedBoolean));
+    }
+    
+    Boolean mutedBoolean = FALSE;
+    for (NSNumber *trackNo in unMuteArray) {
+        MusicTrack track;
+        MusicSequenceGetIndTrack(self.musicSequence, trackNo.intValue, &track);
+        MusicTrackSetProperty(track, kSequenceTrackProperty_MuteStatus, &mutedBoolean, sizeof(mutedBoolean));
+    }
+}
+
+// 播放mid文件
+- (void)playMIDIFile {
+
+    
+    CheckError(MusicPlayerPreroll(self.musicPlayer), "MusicPlayerPreroll");
+    
+    CheckError(MusicPlayerSetPlayRateScalar(self.musicPlayer, self.timeRatio), "MusicPlayerSetPlayRateScalar");
+    
+    CheckError(MusicPlayerStart(self.musicPlayer), "MusicPlayerStart");
+    self.playing = YES;
+    
+    __weak typeof(self) weakSelf = self;
+    dispatch_queue_t queue = dispatch_queue_create("Timer", DISPATCH_QUEUE_SERIAL);
+    // 定时器回调
+    [[GCDTimer shared] scheduleGCDTimerWithName:@"MusicPlayerTimer" Interval:self.reportTime Queue:queue Repeats:YES Action:^{
+        
+        MusicTimeStamp currentStamp;
+        CheckError(MusicPlayerGetTime(weakSelf.musicPlayer, &currentStamp), "MusicPlayerGetTime_CurrentTimeStamp");
+        double currentPlayTime;
+        CheckError(MusicSequenceGetSecondsForBeats(weakSelf.musicSequence, currentStamp, &currentPlayTime), "MusicSequencself->GetSecondsForBeats_CurrentTime");
+        weakSelf.currentTime = currentPlayTime;
+        
+        if (weakSelf.currentTime >= weakSelf.totalTime) {
+            weakSelf.currentTime = weakSelf.totalTime;
+            dispatch_async(dispatch_get_main_queue(), ^{
+                if ([weakSelf.delegate respondsToSelector:@selector(playEnd)]) {
+                    [weakSelf.delegate playEnd];
+                }
+                // 进度上报100%
+                if([weakSelf.delegate respondsToSelector:@selector(ProgressUpdated:currentTime:)]) {
+                    [weakSelf.delegate ProgressUpdated:weakSelf.currentTime/weakSelf.totalTime currentTime:weakSelf.currentTime];
+                }
+            });
+            
+            [[GCDTimer shared] cancelAllTimer];
+            CheckError(MusicPlayerStop(weakSelf.musicPlayer), "MusicPlayerStop");
+            
+            weakSelf.playing = NO;
+            weakSelf.currentTime = 0;
+            CheckError(MusicSequenceGetBeatsForSeconds(weakSelf.musicSequence, weakSelf.currentTime, &currentStamp), "MusicSequenceGetBeatsForSeconds_SetCurrentStamp");
+            CheckError(MusicPlayerSetTime(weakSelf.musicPlayer, currentStamp), "MusicPlayerSetTime_SetZero");
+        }
+        else {
+            // 如果是选段播放 到结束
+            if (weakSelf.endTime != 0 && weakSelf.currentTime >= weakSelf.endTime) {
+                dispatch_async(dispatch_get_main_queue(), ^{
+                    if ([weakSelf.delegate respondsToSelector:@selector(playEnd)]) {
+                        [weakSelf.delegate playEnd];
+                    }
+                });
+                
+                [[GCDTimer shared] cancelAllTimer];
+                CheckError(MusicPlayerStop(weakSelf.musicPlayer), "MusicPlayerStop");
+                
+                weakSelf.playing = NO;
+                weakSelf.currentTime = 0;
+                CheckError(MusicSequenceGetBeatsForSeconds(weakSelf.musicSequence, weakSelf.currentTime, &currentStamp), "MusicSequenceGetBeatsForSeconds_SetCurrentStamp");
+                CheckError(MusicPlayerSetTime(weakSelf.musicPlayer, currentStamp), "MusicPlayerSetTime_SetZero");
+            }
+            else {
+                dispatch_async(dispatch_get_main_queue(), ^{
+                    if ([weakSelf.delegate respondsToSelector:@selector(ProgressUpdated:currentTime:)]){
+                        [weakSelf.delegate ProgressUpdated:weakSelf.currentTime/weakSelf.totalTime currentTime:weakSelf.currentTime];
+                    }
+                });
+            }
+        }
+    }];
+}
+
+- (void)stopPlayingMIDIFile {
+    CheckError(MusicPlayerStop(self.musicPlayer), "MusicPlayerStop");
+    self.playing = NO;
+    [[GCDTimer shared] cancelAllTimer];
+}
+
+- (BOOL)setMusicPlayerSpeed:(double)speed {
+    if(speed<0) return NO;
+    else {
+        self.timeRatio = speed;
+        if (self.musicPlayer){
+            CheckError(MusicPlayerSetPlayRateScalar(self.musicPlayer, self.timeRatio), "MusicPlayerSetPlayRateScalar");
+        }
+        return YES;
+    }
+}
+
+- (void)setProgress:(double)progress {
+    if(progress>1||progress<0) return;
+    
+    self.currentTime = self.totalTime * progress;
+    MusicTimeStamp currentStamp;
+    CheckError(MusicSequenceGetBeatsForSeconds(self.musicSequence, self.currentTime, &currentStamp), "MusicSequenceGetBeatsForSeconds_SetCurrentStamp");
+    CheckError(MusicPlayerSetTime(self.musicPlayer, currentStamp), "MusicPlayerSetTime_SetZero");
+    
+    [self stopPlayingMIDIFile];
+}
+
+- (void)setProgressTime:(MusicTimeStamp)startTime {
+    if (startTime < 0 || startTime > self.totalTime) {
+        return;
+    }
+    self.currentTime = startTime;
+    MusicTimeStamp currentStamp;
+    CheckError(MusicSequenceGetBeatsForSeconds(self.musicSequence, self.currentTime, &currentStamp), "MusicSequenceGetBeatsForSeconds_SetCurrentStamp");
+    CheckError(MusicPlayerSetTime(self.musicPlayer, currentStamp), "MusicPlayerSetTime_SetZero");
+    [self stopPlayingMIDIFile];
+}
+
+- (void)muteWithInstrument:(UInt8)instrument {
+    if(instrument<0||instrument>127) return;
+    
+    NSNumber* numIns = [NSNumber numberWithInt:instrument];
+    [self.instrumentArray addObject:numIns];
+    
+    self.isMute = YES;
+}
+
+-(void)muteWithDrum {
+    self.muteDrum = YES;
+}
+
+-(BOOL)isPlayingFile {
+    return self.playing;
+}
+
+-(double)musicTotalTime {
+    return self.totalTime;
+}
+
+-(double)musicCurrentTime {
+    return self.currentTime;
+}
+
+- (void)volume:(float)volume WithTrack:(UInt32)trackNum {
+    
+    if(volume<0||volume>1) return;
+    if(trackNum<0||trackNum>self.trackCount) return;
+    CheckError(AudioUnitSetParameter(_mixerUnit,
+                                     kMultiChannelMixerParam_Volume,
+                                     kAudioUnitScope_Input,
+                                     trackNum,
+                                     volume,
+                                     0),
+                                "AudioUnitSetMixerInputVolume");
+}
+
+/* 调整除了 trackNum 之外的 其他轨道的音量 范围[0,1]*/
+- (void)volumeOtherTrackExcept:(UInt32)trackNum withVolume:(float)volume {
+    if(volume<0||volume>1) return;
+
+    if(trackNum<0||trackNum>self.trackCount) return;
+    
+    CheckError(AudioUnitSetParameter(_mixerUnit, kMultiChannelMixerParam_Volume, kAudioUnitScope_Input, trackNum, 1, 0), "AudioUnitSetMixerInputVolume");
+}
+
+- (void)getAllTrackVolume {
+    for (int i = 0; i < self.trackCount; i++) {
+        float volume = 0.0f;
+        AudioUnitGetParameter(_mixerUnit, kMultiChannelMixerParam_Volume, kAudioUnitScope_Input, i, &volume);
+        NSLog(@"------ track %d --- volume :%.2f", i, volume);
+    }
+}
+
+
+- (void)muteTrack:(BOOL)isMute WithTrack:(NSMutableArray *)trackArray {
+    
+    Boolean mutedBoolean = isMute ? TRUE : FALSE;
+    for (NSNumber *trackNo in trackArray) {
+        MusicTrack track;
+        MusicSequenceGetIndTrack(self.musicSequence, trackNo.intValue, &track);
+        MusicTrackSetProperty(track, kSequenceTrackProperty_MuteStatus, &mutedBoolean, sizeof(mutedBoolean));
+    }
+}
+
+- (void)cleanup {
+    if (self.musicPlayer) {
+        //当前在播放,先暂停
+        if ([self isPlayingFile]) {
+
+            [self stopPlayingMIDIFile];
+        }
+
+        CheckError(MusicSequenceGetTrackCount(self.musicSequence, &_trackCount), "MusicSequenceGetTrackCount");
+        MusicTrack track;
+        for (int i = 0;i < self.trackCount; i++) {
+            CheckError(MusicSequenceGetIndTrack (self.musicSequence,0,&track), "MusicSequenceGetIndTrack");
+            CheckError(MusicSequenceDisposeTrack(self.musicSequence, track), "MusicSequenceDisposeTrack");
+        }
+        
+        CheckError(DisposeMusicPlayer(self.musicPlayer), "DisposeMusicPlayer");
+        CheckError(DisposeMusicSequence(self.musicSequence), "DisposeMusicSequence");
+        CheckError(DisposeAUGraph(self.processingGraph), "DisposeAUGraph");
+        
+        self.playing=NO;
+        
+        [self.samplerNodeList removeAllObjects];
+        [self.samplerUnitList removeAllObjects];
+        [self.instrumentArray removeAllObjects];
+        [self.instrumentTrackNameArray removeAllObjects];
+        
+        self.isMute = NO;
+        self.muteDrum = NO;
+        NSLog(@"CleanUp!");
+    }
+}
+
+#pragma mark ------ 变调 调整hz
+- (void)transformTo442Hz {
+    [self adjustPitchByHZ:442.0f];
+}
+
+
+// hz调整 440 - 442。。。。。。。
+- (void)adjustPitchByHZ:(float)newHZ {
+    // 415 ~ 466
+    if (newHZ < 415 || newHZ > 466) {
+        return;
+    }
+    /**
+     1200音分等于一个八度音程,频率比为2:1,等程半音(相当于相邻钢琴键间的音程)等于100音分。这意味着1音分正好等于21/1200,即
+
+     如果知道两个音a和b的频率,两个音相距的音分值n可用下列公式计算(类似分贝定义式的形式,目的是为了使指数形式的物理单位线性化,使其化为对数):
+
+      n=1200 *log2(a/b) == 3986 *log10(a/b)
+     */
+    Float32 censt = log2(newHZ/440.0) * 1200;
+    AudioUnitSetParameter(self.pitchUnit, kNewTimePitchParam_Pitch, kAudioUnitScope_Global, 0, censt, 0);
+}
+
+#pragma mark ---- 自动循环功能和播放时同步节拍器暂未实现
+
+- (void)startPlaybackLoop {
+    self.isLooping = YES;
+    [self startPlaybackFromPosition:0];
+}
+
+- (void)startPlaybackFromPosition:(MusicTimeStamp)position {
+    if (self.playing) {
+        [self stopPlayback];
+    }
+    [self loopTrackWhenNeed];
+    [self addClickTrackWhenNeededFromTimeStamp:position];
+    
+//    CheckError(<#OSStatus error#>, <#const char *operation#>)
+}
+
+#pragma mark ---- Looping
+- (void)loopTrackWhenNeed {
+    if (self.isLooping) {
+        MusicTrackLoopInfo loopInfo;
+        loopInfo.numberOfLoops = 0;
+        loopInfo.loopDuration = self.trackLength;
+        MusicTrack track;
+        // 设置循环播放属性
+        for (int i = 0; i < self.trackCount; i++) {
+            CheckError(MusicSequenceGetIndTrack (self.musicSequence, i, &track), "MusicSequenceGetIndTrack");
+            CheckError(MusicTrackSetProperty(track, kSequenceTrackProperty_LoopInfo, &loopInfo, sizeof(loopInfo)), "MusicTrackSetProperty loop");
+        }
+    }
+}
+
+
+
+#pragma mark ----- click tracks
+- (void)addClickTrackWhenNeededFromTimeStamp:(MusicTimeStamp)fromTimeStamp {
+    if (self.isClickPlayer) {
+        return;
+    }
+    if (self.isClickTrackEnabled == NO) {
+        return;
+    }
+    
+    self.clickPlayer = [[MidiPlayerEngine alloc] init];
+    self.clickPlayer->_isClickPlayer = YES;
+    
+    // 添加节拍器mid声轨
+    
+}
+
+
+#pragma mark --- Properties
+- (void)setIsLooping:(BOOL)isLooping {
+    if (isLooping != _isLooping) {
+        _isLooping = isLooping;
+        if (self.playing) {  // 如果正在播放 停止之后从新开启播放
+            
+        }
+    }
+}
+
+
+
+#pragma mark --- Lazyload
+- (NSMutableArray*)samplerNodeList {
+    if (!_samplerNodeList) {
+        _samplerNodeList = [[NSMutableArray alloc] init];
+    }
+    return _samplerNodeList;
+}
+
+- (NSMutableArray*)samplerUnitList {
+    if (!_samplerUnitList) {
+        _samplerUnitList = [[NSMutableArray alloc] init];
+    }
+    return _samplerUnitList;
+}
+
+-(NSMutableArray*)instrumentArray {
+    if (!_instrumentArray) {
+        _instrumentArray = [[NSMutableArray alloc] init];
+    }
+    return _instrumentArray;
+}
+
+- (NSMutableArray *)muteTrackArray {
+    if (!_muteTrackArray) {
+        _muteTrackArray = [NSMutableArray array];
+    }
+    return _muteTrackArray;
+}
+
+- (void)dealloc {
+    NSLog(@"************ player engine dealloc!");
+}
+
+
+/*
+ 物理连接
+  ____________
+ |            |
+ |  Sampler   |-------
+ |            |       |
+  ------------        |
+                      |
+  ____________        |       ----------------------                ------------
+ |            |        ----->|                      |              |            |
+ |  Sampler   |------------->|  MultiChannel Mixer  |------------->|    I/O     |
+ |            |        ----->|                      |              |            |
+  ------------        |       ----------------------                ------------
+                      |
+  ____________        |
+ |            |       |
+ |  Sampler   |-------
+ |            |
+  ------------
+ 
+ 
+ 
+ 
+ 
+ 
+  ---------------------          -------------------------------------------------
+ |    MusicSequence    |        |                    AUGraph                      |
+ |                     |        |                                                 |
+ |     MusicTrack1  ---|--------| ->MIDISynth1                                    |
+ |                     |        |               --->MixerNode ------->  OutputNode|
+ |     MusicTrack2  ---|--------| ->MIDISynth2                                    |
+ |                     |        |                                                 |
+  ---------------------          -------------------------------------------------
+ 
+ 
+ */
+
+
+@end

+ 33 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/Model/CloudSongMessageModel.h

@@ -0,0 +1,33 @@
+//
+//  CloudSongMessageModel.h
+//  StudentDaya
+//
+//  Created by Kyle on 2021/12/9.
+//  Copyright © 2021 DayaMusic. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface CloudSongMessageModel : NSObject
+
+@property (nonatomic, strong) NSString *detailId;  // 返回的id
+
+@property (nonatomic, strong) NSString *subjectId;     // 声部id
+
+@property (nonatomic, strong) NSString *examSongId;    // 曲目id
+
+@property (nonatomic, strong) NSString *examSongName;  // 曲目名称
+
+@property (nonatomic, strong) NSString *zipUrl;
+
+@property (nonatomic, strong) NSString *xmlUrl;
+
+@property (nonatomic, strong) NSString *behaviorId; // 统计批次ID
+
++ (NSString *)getSaveSpeedPath;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 35 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/Model/CloudSongMessageModel.m

@@ -0,0 +1,35 @@
+//
+//  CloudSongMessageModel.m
+//  StudentDaya
+//
+//  Created by Kyle on 2021/12/9.
+//  Copyright © 2021 DayaMusic. All rights reserved.
+//
+
+#import "CloudSongMessageModel.h"
+
+@implementation CloudSongMessageModel
+
++ (NSString *)getSaveSpeedPath {
+    //  在Documents目录下创建一个名为AudioSpeedFile的文件夹
+    NSString *path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)lastObject] stringByAppendingPathComponent:@"AudioSpeedFile"];
+    NSLog(@"%@",path);
+    
+    NSFileManager *fileManager = [NSFileManager defaultManager];
+    BOOL isDir = FALSE;
+    BOOL isDirExist = [fileManager fileExistsAtPath:path isDirectory:&isDir];
+    if(!(isDirExist && isDir)) {
+        BOOL bCreateDir = [fileManager createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
+        if(!bCreateDir){
+            NSLog(@"创建文件夹失败!");
+        }
+        NSLog(@"创建文件夹成功,文件路径%@",path);
+    }
+    
+    path = [path stringByAppendingPathComponent:@"songSpeed.plist"];
+    NSLog(@"file path:%@",path);
+    return path;
+}
+
+
+@end

+ 1206 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/Model/FingerList.plist

@@ -0,0 +1,1206 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>flute</key>
+	<dict>
+		<key>60</key>
+		<array>
+			<string>flute13,flute12,flute11,flute1,flute10,flute8,flute3,flute6</string>
+		</array>
+		<key>61</key>
+		<array>
+			<string>flute13,flute12,flute11,flute1,flute10,flute8,flute3,flute7</string>
+		</array>
+		<key>62</key>
+		<array>
+			<string>flute13,flute12,flute11,flute1,flute10,flute8,flute3</string>
+		</array>
+		<key>63</key>
+		<array>
+			<string>flute13,flute12,flute11,flute1,flute10,flute8,flute3,flute5</string>
+		</array>
+		<key>64</key>
+		<array>
+			<string>flute13,flute12,flute11,flute1,flute10,flute8,flute5</string>
+		</array>
+		<key>65</key>
+		<array>
+			<string>flute13,flute12,flute11,flute1,flute10,flute5</string>
+		</array>
+		<key>66</key>
+		<array>
+			<string>flute13,flute12,flute11,flute1,flute3,flute5</string>
+		</array>
+		<key>67</key>
+		<array>
+			<string>flute13,flute12,flute11,flute1,flute5</string>
+		</array>
+		<key>68</key>
+		<array>
+			<string>flute13,flute12,flute11,flute1,flute4,flute5</string>
+		</array>
+		<key>69</key>
+		<array>
+			<string>flute13,flute12,flute1,flute5</string>
+		</array>
+		<key>70</key>
+		<array>
+			<string>flute13,flute0,flute5</string>
+			<string>flute13,flute10,flute1,flute5</string>
+		</array>
+		<key>71</key>
+		<array>
+			<string>flute13,flute1,flute5</string>
+		</array>
+		<key>72</key>
+		<array>
+			<string>flute13,flute5</string>
+		</array>
+		<key>73</key>
+		<array>
+			<string>flute5</string>
+		</array>
+		<key>74</key>
+		<array>
+			<string>flute12,flute11,flute10,flute8,flute3,flute1</string>
+		</array>
+		<key>75</key>
+		<array>
+			<string>flute12,flute11,flute10,flute8,flute3,flute5,flute1</string>
+		</array>
+		<key>76</key>
+		<array>
+			<string>flute13,flute12,flute11,flute1,flute10,flute8,flute5</string>
+		</array>
+		<key>77</key>
+		<array>
+			<string>flute13,flute12,flute11,flute1,flute10,flute5</string>
+		</array>
+		<key>78</key>
+		<array>
+			<string>flute13,flute12,flute11,flute1,flute3,flute5</string>
+		</array>
+		<key>79</key>
+		<array>
+			<string>flute13,flute12,flute11,flute1,flute5</string>
+		</array>
+		<key>80</key>
+		<array>
+			<string>flute13,flute12,flute11,flute1,flute4,flute5</string>
+		</array>
+		<key>81</key>
+		<array>
+			<string>flute13,flute12,flute1,flute5</string>
+		</array>
+		<key>82</key>
+		<array>
+			<string>flute13,flute0,flute5</string>
+			<string>flute13,flute10,flute1,flute5</string>
+		</array>
+		<key>83</key>
+		<array>
+			<string>flute13,flute1,flute5</string>
+		</array>
+		<key>84</key>
+		<array>
+			<string>flute13,flute5</string>
+		</array>
+		<key>85</key>
+		<array>
+			<string>flute5</string>
+		</array>
+		<key>86</key>
+		<array>
+			<string>flute12,flute11,flute1,flute5</string>
+		</array>
+		<key>87</key>
+		<array>
+			<string>flute13,flute12,flute11,flute1,flute10,flute8,flute3,flute5</string>
+		</array>
+		<key>88</key>
+		<array>
+			<string>flute13,flute12,flute1,flute10,flute8,flute5</string>
+		</array>
+		<key>89</key>
+		<array>
+			<string>flute13,flute11,flute1,flute5</string>
+		</array>
+	</dict>
+	<key>clarinet</key>
+	<dict>
+		<key>52</key>
+		<array>
+			<string>clarinet2,clarinet3,clarinet5,clarinet7,clarinet8,clarinet10,clarinet13,clarinet17,clarinet19</string>
+			<string>clarinet2,clarinet3,clarinet5,clarinet7,clarinet8,clarinet10,clarinet13,clarinet19</string>
+		</array>
+		<key>53</key>
+		<array>
+			<string>clarinet2,clarinet3,clarinet5,clarinet7,clarinet8,clarinet10,clarinet11,clarinet19</string>
+			<string>clarinet2,clarinet3,clarinet5,clarinet7,clarinet8,clarinet10,clarinet17,clarinet19</string>
+		</array>
+		<key>54</key>
+		<array>
+			<string>clarinet2,clarinet3,clarinet5,clarinet7,clarinet8,clarinet10,clarinet12,clarinet17,clarinet19</string>
+			<string>clarinet2,clarinet3,clarinet5,clarinet7,clarinet8,clarinet10,clarinet12,clarinet19</string>
+			<string>clarinet2,clarinet3,clarinet5,clarinet7,clarinet8,clarinet10,clarinet14</string>
+		</array>
+		<key>55</key>
+		<array>
+			<string>clarinet2,clarinet3,clarinet5,clarinet7,clarinet8,clarinet10,clarinet19</string>
+		</array>
+		<key>56</key>
+		<array>
+			<string>clarinet2,clarinet3,clarinet5,clarinet7,clarinet8,clarinet10,clarinet15,clarinet19</string>
+		</array>
+		<key>57</key>
+		<array>
+			<string>clarinet2,clarinet3,clarinet5,clarinet7,clarinet8,clarinet19</string>
+		</array>
+		<key>58</key>
+		<array>
+			<string>clarinet2,clarinet3,clarinet5,clarinet7,clarinet19</string>
+		</array>
+		<key>59</key>
+		<array>
+			<string>clarinet2,clarinet3,clarinet5,clarinet8,clarinet19</string>
+			<string>clarinet2,clarinet3,clarinet5,clarinet7,clarinet9,clarinet19</string>
+		</array>
+		<key>60</key>
+		<array>
+			<string>clarinet2,clarinet3,clarinet5,clarinet9</string>
+		</array>
+		<key>61</key>
+		<array>
+			<string>clarinet2,clarinet3,clarinet5,clarinet6,clarinet19</string>
+		</array>
+		<key>62</key>
+		<array>
+			<string>clarinet2,clarinet3,clarinet19</string>
+		</array>
+		<key>63</key>
+		<array>
+			<string>clarinet2,clarinet3,clarinet19,clarinet23</string>
+			<string>clarinet2,clarinet3,clarinet4,clarinet19</string>
+			<string>clarinet2,clarinet7,clarinet19</string>
+		</array>
+		<key>64</key>
+		<array>
+			<string>clarinet2,clarinet19</string>
+		</array>
+		<key>65</key>
+		<array>
+			<string>clarinet19</string>
+		</array>
+		<key>66</key>
+		<array>
+			<string>clarinet2</string>
+			<string>clarinet19,clarinet22,clarinet23</string>
+		</array>
+		<key>67</key>
+		<array>
+			<string></string>
+		</array>
+		<key>68</key>
+		<array>
+			<string>clarinet1</string>
+		</array>
+		<key>69</key>
+		<array>
+			<string>clarinet0</string>
+		</array>
+		<key>70</key>
+		<array>
+			<string>clarinet0,clarinet18</string>
+		</array>
+		<key>71</key>
+		<array>
+			<string>clarinet2,clarinet3,clarinet5,clarinet7,clarinet8,clarinet10,clarinet13,clarinet17,clarinet18,clarinet19</string>
+			<string>clarinet2,clarinet3,clarinet5,clarinet7,clarinet8,clarinet10,clarinet13,clarinet18,clarinet19</string>
+		</array>
+		<key>72</key>
+		<array>
+			<string>clarinet2,clarinet3,clarinet5,clarinet7,clarinet8,clarinet10,clarinet12,clarinet18,clarinet19</string>
+			<string>clarinet2,clarinet3,clarinet5,clarinet7,clarinet8,clarinet10,clarinet17,clarinet18,clarinet19</string>
+		</array>
+		<key>73</key>
+		<array>
+			<string>clarinet2,clarinet3,clarinet5,clarinet7,clarinet8,clarinet10,clarinet12,clarinet18,clarinet19</string>
+			<string>clarinet2,clarinet3,clarinet5,clarinet7,clarinet8,clarinet10,clarinet12,clarinet17,clarinet18,clarinet19</string>
+			<string>clarinet2,clarinet3,clarinet5,clarinet7,clarinet8,clarinet10,clarinet14,clarinet18,clarinet19</string>
+		</array>
+		<key>74</key>
+		<array>
+			<string>clarinet2,clarinet3,clarinet5,clarinet7,clarinet8,clarinet10,clarinet18,clarinet19</string>
+		</array>
+		<key>75</key>
+		<array>
+			<string>clarinet2,clarinet3,clarinet5,clarinet7,clarinet8,clarinet10,clarinet15,clarinet18,clarinet19</string>
+		</array>
+		<key>76</key>
+		<array>
+			<string>clarinet2,clarinet3,clarinet5,clarinet7,clarinet8,clarinet18,clarinet19</string>
+		</array>
+		<key>77</key>
+		<array>
+			<string>clarinet2,clarinet3,clarinet5,clarinet7,clarinet18,clarinet19</string>
+		</array>
+		<key>78</key>
+		<array>
+			<string>clarinet2,clarinet3,clarinet5,clarinet8,clarinet18,clarinet19</string>
+			<string>clarinet2,clarinet3,clarinet5,clarinet7,clarinet9,clarinet18,clarinet19</string>
+		</array>
+		<key>79</key>
+		<array>
+			<string>clarinet2,clarinet3,clarinet5,clarinet18,clarinet19</string>
+		</array>
+		<key>80</key>
+		<array>
+			<string>clarinet2,clarinet3,clarinet5,clarinet6,clarinet18,clarinet19</string>
+		</array>
+		<key>81</key>
+		<array>
+			<string>clarinet2,clarinet3,clarinet18,clarinet19</string>
+		</array>
+		<key>82</key>
+		<array>
+			<string>clarinet2,clarinet3,clarinet18,clarinet19,clarinet23</string>
+			<string>clarinet2,clarinet3,clarinet4,clarinet18,clarinet19</string>
+			<string>clarinet2,clarinet3,clarinet7,clarinet18,clarinet19</string>
+		</array>
+		<key>83</key>
+		<array>
+			<string>clarinet2,clarinet18,clarinet19</string>
+		</array>
+		<key>84</key>
+		<array>
+			<string>clarinet18,clarinet19</string>
+		</array>
+		<key>85</key>
+		<array>
+			<string>clarinet3,clarinet5,clarinet7,clarinet8,clarinet18,clarinet19</string>
+		</array>
+		<key>86</key>
+		<array>
+			<string>clarinet3,clarinet5,clarinet7,clarinet15,clarinet18,clarinet19</string>
+		</array>
+		<key>87</key>
+		<array>
+			<string>clarinet3,clarinet5,clarinet7,clarinet9,clarinet15,clarinet18,clarinet19</string>
+			<string>clarinet3,clarinet5,clarinet8,clarinet15,clarinet18,clarinet19</string>
+		</array>
+		<key>88</key>
+		<array>
+			<string>clarinet3,clarinet5,clarinet15,clarinet18,clarinet19</string>
+		</array>
+		<key>89</key>
+		<array>
+			<string>clarinet3,clarinet5,clarinet6,clarinet15,clarinet18,clarinet19</string>
+		</array>
+	</dict>
+	<key>trumpet</key>
+	<dict>
+		<key>0</key>
+		<array>
+			<string>trumpet0,trumpet1,trumpet2</string>
+		</array>
+		<key>54</key>
+		<array>
+			<string>trumpet3,trumpet4,trumpet5</string>
+		</array>
+		<key>55</key>
+		<array>
+			<string>trumpet3,trumpet1,trumpet5</string>
+		</array>
+		<key>56</key>
+		<array>
+			<string>trumpet0,trumpet4,trumpet5</string>
+		</array>
+		<key>57</key>
+		<array>
+			<string>trumpet3,trumpet4,trumpet2</string>
+		</array>
+		<key>58</key>
+		<array>
+			<string>trumpet3,trumpet1,trumpet2</string>
+		</array>
+		<key>59</key>
+		<array>
+			<string>trumpet0,trumpet4,trumpet2</string>
+		</array>
+		<key>60</key>
+		<array>
+			<string>trumpet0,trumpet1,trumpet2</string>
+		</array>
+		<key>61</key>
+		<array>
+			<string>trumpet3,trumpet4,trumpet5</string>
+		</array>
+		<key>62</key>
+		<array>
+			<string>trumpet3,trumpet1,trumpet5</string>
+		</array>
+		<key>63</key>
+		<array>
+			<string>trumpet0,trumpet4,trumpet5</string>
+		</array>
+		<key>64</key>
+		<array>
+			<string>trumpet3,trumpet4,trumpet2</string>
+		</array>
+		<key>65</key>
+		<array>
+			<string>trumpet3,trumpet1,trumpet2</string>
+		</array>
+		<key>66</key>
+		<array>
+			<string>trumpet0,trumpet4,trumpet2</string>
+		</array>
+		<key>67</key>
+		<array>
+			<string>trumpet0,trumpet1,trumpet2</string>
+		</array>
+		<key>68</key>
+		<array>
+			<string>trumpet0,trumpet4,trumpet5</string>
+		</array>
+		<key>69</key>
+		<array>
+			<string>trumpet3,trumpet4,trumpet2</string>
+		</array>
+		<key>70</key>
+		<array>
+			<string>trumpet3,trumpet1,trumpet2</string>
+		</array>
+		<key>71</key>
+		<array>
+			<string>trumpet0,trumpet4,trumpet2</string>
+		</array>
+		<key>72</key>
+		<array>
+			<string>trumpet0,trumpet1,trumpet2</string>
+		</array>
+		<key>73</key>
+		<array>
+			<string>trumpet3,trumpet4,trumpet2</string>
+		</array>
+		<key>74</key>
+		<array>
+			<string>trumpet3,trumpet1,trumpet2</string>
+		</array>
+		<key>75</key>
+		<array>
+			<string>trumpet0,trumpet4,trumpet2</string>
+		</array>
+		<key>76</key>
+		<array>
+			<string>trumpet0,trumpet1,trumpet2</string>
+		</array>
+		<key>77</key>
+		<array>
+			<string>trumpet3,trumpet1,trumpet2</string>
+		</array>
+		<key>78</key>
+		<array>
+			<string>trumpet0,trumpet3,trumpet2</string>
+		</array>
+		<key>79</key>
+		<array>
+			<string>trumpet0,trumpet1,trumpet2</string>
+		</array>
+		<key>80</key>
+		<array>
+			<string>trumpet0,trumpet4,trumpet5</string>
+		</array>
+		<key>81</key>
+		<array>
+			<string>trumpet3,trumpet4,trumpet2</string>
+		</array>
+		<key>82</key>
+		<array>
+			<string>trumpet3,trumpet1,trumpet2</string>
+		</array>
+		<key>83</key>
+		<array>
+			<string>trumpet0,trumpet4,trumpet2</string>
+		</array>
+		<key>84</key>
+		<array>
+			<string>trumpet0,trumpet1,trumpet2</string>
+		</array>
+	</dict>
+	<key>horn</key>
+	<dict>
+		<key>53</key>
+		<array>
+			<string>horn1</string>
+		</array>
+		<key>54</key>
+		<array>
+			<string>horn2</string>
+		</array>
+		<key>55</key>
+		<array>
+			<string></string>
+		</array>
+		<key>56</key>
+		<array>
+			<string>horn2,horn3</string>
+		</array>
+		<key>57</key>
+		<array>
+			<string>horn1,horn2</string>
+		</array>
+		<key>58</key>
+		<array>
+			<string>horn1</string>
+		</array>
+		<key>59</key>
+		<array>
+			<string>horn2</string>
+		</array>
+		<key>60</key>
+		<array>
+			<string></string>
+		</array>
+		<key>61</key>
+		<array>
+			<string>horn1,horn2</string>
+		</array>
+		<key>62</key>
+		<array>
+			<string>horn1</string>
+		</array>
+		<key>63</key>
+		<array>
+			<string></string>
+		</array>
+		<key>65</key>
+		<array>
+			<string>horn1</string>
+		</array>
+		<key>66</key>
+		<array>
+			<string>horn2</string>
+		</array>
+		<key>67</key>
+		<array>
+			<string></string>
+		</array>
+		<key>68</key>
+		<array>
+			<string>horn0,horn2,horn3</string>
+		</array>
+		<key>69</key>
+		<array>
+			<string>horn0,horn1,horn2</string>
+		</array>
+		<key>70</key>
+		<array>
+			<string>horn0,horn1</string>
+		</array>
+		<key>71</key>
+		<array>
+			<string>horn0,horn2</string>
+		</array>
+		<key>72</key>
+		<array>
+			<string>horn0</string>
+		</array>
+		<key>73</key>
+		<array>
+			<string>horn0,horn2,horn3</string>
+		</array>
+		<key>74</key>
+		<array>
+			<string>horn0,horn1,horn2</string>
+		</array>
+		<key>75</key>
+		<array>
+			<string>horn0,horn1</string>
+		</array>
+		<key>76</key>
+		<array>
+			<string>horn0,horn2</string>
+		</array>
+		<key>77</key>
+		<array>
+			<string>horn0</string>
+		</array>
+		<key>78</key>
+		<array>
+			<string>horn0,horn2</string>
+		</array>
+		<key>79</key>
+		<array>
+			<string>horn0</string>
+		</array>
+		<key>80</key>
+		<array>
+			<string>horn0,horn2,horn3</string>
+		</array>
+		<key>81</key>
+		<array>
+			<string>horn0,horn1,horn3</string>
+		</array>
+		<key>82</key>
+		<array>
+			<string>horn0,horn1</string>
+		</array>
+		<key>83</key>
+		<array>
+			<string>horn0,horn2</string>
+		</array>
+		<key>84</key>
+		<array>
+			<string>horn0</string>
+		</array>
+	</dict>
+	<key>up-bass-horn</key>
+	<dict>
+		<key>0</key>
+		<array>
+			<string>up-bass-horn0,up-bass-horn1,up-bass-horn2</string>
+		</array>
+		<key>40</key>
+		<array>
+			<string>up-bass-horn3,up-bass-horn4,up-bass-horn5</string>
+		</array>
+		<key>41</key>
+		<array>
+			<string>up-bass-horn3,up-bass-horn1,up-bass-horn5</string>
+		</array>
+		<key>42</key>
+		<array>
+			<string>up-bass-horn0,up-bass-horn4,up-bass-horn5</string>
+		</array>
+		<key>43</key>
+		<array>
+			<string>up-bass-horn3,up-bass-horn4,up-bass-horn2</string>
+		</array>
+		<key>44</key>
+		<array>
+			<string>up-bass-horn3,up-bass-horn1,up-bass-horn2</string>
+		</array>
+		<key>45</key>
+		<array>
+			<string>up-bass-horn0,up-bass-horn4,up-bass-horn2</string>
+		</array>
+		<key>46</key>
+		<array>
+			<string>up-bass-horn0,up-bass-horn1,up-bass-horn2</string>
+		</array>
+		<key>47</key>
+		<array>
+			<string>up-bass-horn3,up-bass-horn4,up-bass-horn5</string>
+		</array>
+		<key>48</key>
+		<array>
+			<string>up-bass-horn3,up-bass-horn1,up-bass-horn5</string>
+		</array>
+		<key>49</key>
+		<array>
+			<string>up-bass-horn0,up-bass-horn4,up-bass-horn5</string>
+		</array>
+		<key>50</key>
+		<array>
+			<string>up-bass-horn3,up-bass-horn4,up-bass-horn2</string>
+		</array>
+		<key>51</key>
+		<array>
+			<string>up-bass-horn3,up-bass-horn1,up-bass-horn2</string>
+		</array>
+		<key>52</key>
+		<array>
+			<string>up-bass-horn0,up-bass-horn4,up-bass-horn2</string>
+		</array>
+		<key>53</key>
+		<array>
+			<string>up-bass-horn0,up-bass-horn1,up-bass-horn2</string>
+		</array>
+		<key>54</key>
+		<array>
+			<string>up-bass-horn0,up-bass-horn4,up-bass-horn5</string>
+		</array>
+		<key>55</key>
+		<array>
+			<string>up-bass-horn3,up-bass-horn4,up-bass-horn2</string>
+		</array>
+		<key>56</key>
+		<array>
+			<string>up-bass-horn3,up-bass-horn1,up-bass-horn2</string>
+		</array>
+		<key>57</key>
+		<array>
+			<string>up-bass-horn0,up-bass-horn4,up-bass-horn2</string>
+		</array>
+		<key>58</key>
+		<array>
+			<string>up-bass-horn0,up-bass-horn1,up-bass-horn2</string>
+		</array>
+		<key>59</key>
+		<array>
+			<string>up-bass-horn3,up-bass-horn4,up-bass-horn2</string>
+		</array>
+		<key>60</key>
+		<array>
+			<string>up-bass-horn3,up-bass-horn1,up-bass-horn2</string>
+		</array>
+		<key>61</key>
+		<array>
+			<string>up-bass-horn0,up-bass-horn4,up-bass-horn2</string>
+		</array>
+		<key>62</key>
+		<array>
+			<string>up-bass-horn0,up-bass-horn1,up-bass-horn2</string>
+		</array>
+		<key>63</key>
+		<array>
+			<string>up-bass-horn3,up-bass-horn1,up-bass-horn2</string>
+		</array>
+		<key>64</key>
+		<array>
+			<string>up-bass-horn0,up-bass-horn4,up-bass-horn2</string>
+		</array>
+		<key>65</key>
+		<array>
+			<string>up-bass-horn0,up-bass-horn1,up-bass-horn2</string>
+		</array>
+		<key>66</key>
+		<array>
+			<string>up-bass-horn0,up-bass-horn4,up-bass-horn5</string>
+		</array>
+		<key>67</key>
+		<array>
+			<string>up-bass-horn3,up-bass-horn4,up-bass-horn2</string>
+		</array>
+		<key>68</key>
+		<array>
+			<string>up-bass-horn3,up-bass-horn1,up-bass-horn2</string>
+		</array>
+		<key>69</key>
+		<array>
+			<string>up-bass-horn0,up-bass-horn4,up-bass-horn2</string>
+		</array>
+		<key>70</key>
+		<array>
+			<string>up-bass-horn0,up-bass-horn1,up-bass-horn2</string>
+		</array>
+	</dict>
+	<key>trombone</key>
+	<dict>
+		<key>0</key>
+		<array>
+			<string>trombone0</string>
+		</array>
+        <key>40</key>
+        <array>
+            <string>trombone6</string>
+        </array>
+        <key>41</key>
+        <array>
+            <string>trombone5</string>
+        </array>
+        <key>42</key>
+        <array>
+            <string>trombone4</string>
+        </array>
+        <key>43</key>
+        <array>
+            <string>trombone3</string>
+        </array>
+        <key>44</key>
+        <array>
+            <string>trombone2</string>
+        </array>
+        <key>45</key>
+        <array>
+            <string>trombone1</string>
+        </array>
+        <key>46</key>
+        <array>
+            <string>trombone0</string>
+        </array>
+        <key>47</key>
+        <array>
+            <string>trombone6</string>
+        </array>
+        <key>48</key>
+        <array>
+            <string>trombone5</string>
+        </array>
+        <key>49</key>
+        <array>
+            <string>trombone4</string>
+        </array>
+        <key>50</key>
+        <array>
+            <string>trombone3</string>
+        </array>
+        <key>51</key>
+        <array>
+            <string>trombone2</string>
+        </array>
+        <key>52</key>
+        <array>
+            <string>trombone1</string>
+        </array>
+        <key>53</key>
+        <array>
+            <string>trombone0</string>
+        </array>
+        <key>54</key>
+        <array>
+            <string>trombone4</string>
+        </array>
+        <key>55</key>
+        <array>
+            <string>trombone3</string>
+        </array>
+        <key>56</key>
+        <array>
+            <string>trombone2</string>
+        </array>
+        <key>57</key>
+        <array>
+            <string>trombone1</string>
+        </array>
+        <key>58</key>
+        <array>
+            <string>trombone0</string>
+        </array>
+        <key>59</key>
+        <array>
+            <string>trombone3</string>
+        </array>
+        <key>60</key>
+        <array>
+            <string>trombone2</string>
+        </array>
+        <key>61</key>
+        <array>
+            <string>trombone1</string>
+        </array>
+        <key>62</key>
+        <array>
+            <string>trombone0</string>
+        </array>
+        <key>63</key>
+        <array>
+            <string>trombone2</string>
+        </array>
+        <key>64</key>
+        <array>
+            <string>trombone1</string>
+        </array>
+        <key>65</key>
+        <array>
+            <string>trombone0</string>
+        </array>
+        <key>66</key>
+        <array>
+            <string>trombone2</string>
+        </array>
+        <key>67</key>
+        <array>
+            <string>trombone1</string>
+        </array>
+        <key>68</key>
+        <array>
+            <string>trombone2</string>
+        </array>
+        <key>69</key>
+        <array>
+            <string>trombone1</string>
+        </array>
+        <key>70</key>
+        <array>
+            <string>trombone0</string>
+        </array>
+	</dict>
+    <key>saxophone</key>
+    <dict>
+        <key>58</key>
+        <array>
+            <string>saxophone1,saxophone3,saxophone4,saxophone12,saxophone16,saxophone17,saxophone18,saxophone22</string>
+        </array>
+        <key>59</key>
+        <array>
+            <string>saxophone1,saxophone3,saxophone4,saxophone10,saxophone16,saxophone17,saxophone18,saxophone22</string>
+        </array>
+        <key>60</key>
+        <array>
+            <string>saxophone1,saxophone3,saxophone4,saxophone16,saxophone17,saxophone18,saxophone22</string>
+        </array>
+        <key>61</key>
+        <array>
+            <string>saxophone1,saxophone3,saxophone4,saxophone16,saxophone17,saxophone18,saxophone22</string>
+        </array>
+        <key>62</key>
+        <array>
+            <string>saxophone1,saxophone3,saxophone4,saxophone16,saxophone17,saxophone18</string>
+        </array>
+        <key>63</key>
+        <array>
+            <string>saxophone1,saxophone3,saxophone4,saxophone16,saxophone17,saxophone18,saxophone21</string>
+        </array>
+        <key>64</key>
+        <array>
+            <string>saxophone1,saxophone3,saxophone4,saxophone16,saxophone17</string>
+        </array>
+        <key>65</key>
+        <array>
+            <string>saxophone1,saxophone3,saxophone4,saxophone16</string>
+        </array>
+        <key>66</key>
+        <array>
+            <string>saxophone1,saxophone3,saxophone4,saxophone17</string>
+            <string>saxophone1,saxophone3,saxophone4,saxophone16,saxophone20</string>
+        </array>
+        <key>67</key>
+        <array>
+            <string>saxophone1,saxophone3,saxophone4</string>
+        </array>
+        <key>68</key>
+        <array>
+            <string>saxophone1,saxophone3,saxophone4,saxophone9</string>
+        </array>
+        <key>69</key>
+        <array>
+            <string>saxophone1,saxophone3</string>
+        </array>
+        <key>70</key>
+        <array>
+            <string>saxophone1,saxophone3,saxophone15</string>
+            <string>saxophone1,saxophone16</string>
+            <string>saxophone1,saxophone2</string>
+        </array>
+        <key>71</key>
+        <array>
+            <string>saxophone1</string>
+        </array>
+        <key>72</key>
+        <array>
+            <string>saxophone3</string>
+        </array>
+        <key>73</key>
+        <array>
+            <string></string>
+        </array>
+        <key>74</key>
+        <array>
+            <string>saxophone1,saxophone3,saxophone4,saxophone5,saxophone16,saxophone17,saxophone18</string>
+        </array>
+        <key>75</key>
+        <array>
+            <string>saxophone1,saxophone3,saxophone4,saxophone5,saxophone16,saxophone17,saxophone18,saxophone21</string>
+        </array>
+        <key>76</key>
+        <array>
+            <string>saxophone1,saxophone3,saxophone4,saxophone5,saxophone16,saxophone17</string>
+        </array>
+        <key>77</key>
+        <array>
+            <string>saxophone1,saxophone3,saxophone4,saxophone5,saxophone16</string>
+        </array>
+        <key>78</key>
+        <array>
+            <string>saxophone1,saxophone3,saxophone4,saxophone5,saxophone17</string>
+            <string>saxophone1,saxophone3,saxophone4,saxophone5,saxophone16,saxophone20</string>
+        </array>
+        <key>79</key>
+        <array>
+            <string>saxophone1,saxophone3,saxophone4,saxophone5</string>
+        </array>
+        <key>80</key>
+        <array>
+            <string>saxophone1,saxophone3,saxophone4,saxophone5,saxophone9</string>
+        </array>
+        <key>81</key>
+        <array>
+            <string>saxophone1,saxophone3,saxophone5</string>
+        </array>
+        <key>82</key>
+        <array>
+            <string>saxophone1,saxophone3,saxophone5,saxophone15</string>
+            <string>saxophone1,saxophone5,saxophone16</string>
+            <string>saxophone1,saxophone2,saxophone5</string>
+        </array>
+        <key>83</key>
+        <array>
+            <string>saxophone1,saxophone5</string>
+        </array>
+        <key>84</key>
+        <array>
+            <string>saxophone3,saxophone5</string>
+            <string>saxophone1,saxophone5,saxophone14</string>
+        </array>
+        <key>85</key>
+        <array>
+            <string>saxophone5</string>
+        </array>
+        <key>86</key>
+        <array>
+            <string>saxophone5,saxophone8</string>
+        </array>
+        <key>87</key>
+        <array>
+            <string>saxophone5,saxophone6,saxophone7</string>
+        </array>
+        <key>88</key>
+        <array>
+            <string>saxophone5,saxophone7,saxophone8,saxophone13</string>
+        </array>
+        <key>89</key>
+        <array>
+            <string>saxophone5,saxophone6,saxophone7,saxophone8,saxophone13</string>
+            <string>saxophone0,saxophone3,saxophone5</string>
+        </array>
+    </dict>
+    <key>tuba</key>
+    <dict>
+        <key>0</key>
+        <array>
+            <string>tuba0,tuba1,tuba2</string>
+        </array>
+        <key>28</key>
+        <array>
+            <string>tuba3,tuba4,tuba5</string>
+        </array>
+        <key>29</key>
+        <array>
+            <string>tuba3,tuba1,tuba5</string>
+        </array>
+        <key>30</key>
+        <array>
+            <string>tuba0,tuba4,tuba5</string>
+        </array>
+        <key>31</key>
+        <array>
+            <string>tuba3,tuba4,tuba2</string>
+        </array>
+        <key>32</key>
+        <array>
+            <string>tuba3,tuba1,tuba2</string>
+        </array>
+        <key>33</key>
+        <array>
+            <string>tuba3,tuba1,tuba5</string>
+        </array>
+        <key>34</key>
+        <array>
+            <string>tuba0,tuba1,tuba2</string>
+        </array>
+        <key>35</key>
+        <array>
+            <string>tuba3,tuba4,tuba5</string>
+        </array>
+        <key>36</key>
+        <array>
+            <string>tuba3,tuba1,tuba5</string>
+        </array>
+        <key>37</key>
+        <array>
+            <string>tuba0,tuba4,tuba5</string>
+        </array>
+        <key>38</key>
+        <array>
+            <string>tuba3,tuba4,tuba2</string>
+        </array>
+        <key>39</key>
+        <array>
+            <string>tuba3,tuba1,tuba2</string>
+        </array>
+        <key>40</key>
+        <array>
+            <string>tuba0,tuba4,tuba2</string>
+        </array>
+        <key>41</key>
+        <array>
+            <string>tuba0,tuba1,tuba2</string>
+        </array>
+        <key>42</key>
+        <array>
+            <string>tuba0,tuba4,tuba5</string>
+        </array>
+        <key>43</key>
+        <array>
+            <string>tuba3,tuba4,tuba2</string>
+        </array>
+        <key>44</key>
+        <array>
+            <string>tuba3,tuba1,tuba2</string>
+        </array>
+        <key>45</key>
+        <array>
+            <string>tuba0,tuba4,tuba2</string>
+        </array>
+        <key>46</key>
+        <array>
+            <string>tuba0,tuba1,tuba2</string>
+        </array>
+        <key>47</key>
+        <array>
+            <string>tuba3,tuba4,tuba2</string>
+        </array>
+        <key>48</key>
+        <array>
+            <string>tuba3,tuba1,tuba2</string>
+        </array>
+        <key>49</key>
+        <array>
+            <string>tuba0,tuba4,tuba2</string>
+        </array>
+        <key>50</key>
+        <array>
+            <string>tuba0,tuba1,tuba2</string>
+        </array>
+        <key>51</key>
+        <array>
+            <string>tuba3,tuba1,tuba2</string>
+        </array>
+        <key>52</key>
+        <array>
+            <string>tuba0,tuba4,tuba2</string>
+        </array>
+        <key>53</key>
+        <array>
+            <string>tuba0,tuba1,tuba2</string>
+        </array>
+        <key>54</key>
+        <array>
+            <string>tuba0,tuba4,tuba5</string>
+        </array>
+        <key>55</key>
+        <array>
+            <string>tuba3,tuba4,tuba2</string>
+        </array>
+        <key>56</key>
+        <array>
+            <string>tuba3,tuba1,tuba2</string>
+        </array>
+        <key>57</key>
+        <array>
+            <string>tuba0,tuba4,tuba2</string>
+        </array>
+        <key>58</key>
+        <array>
+            <string>tuba0,tuba1,tuba2</string>
+        </array>
+    </dict>
+    <key>piccolo</key>
+    <dict>
+        <key>72</key>
+        <array>
+            <string>piccolo8,piccolo1,piccolo9,piccolo3,piccolo4,piccolo5,piccolo6,piccolo7</string>
+        </array>
+        <key>73</key>
+        <array>
+            <string>piccolo8,piccolo1,piccolo9,piccolo3,piccolo4,piccolo5,piccolo6,piccolo11</string>
+        </array>
+        <key>74</key>
+        <array>
+            <string>piccolo8,piccolo1,piccolo9,piccolo3,piccolo4,piccolo5,piccolo10</string>
+        </array>
+        <key>75</key>
+        <array>
+            <string>piccolo8,piccolo1,piccolo9,piccolo3,piccolo4,piccolo5,piccolo6</string>
+        </array>
+        <key>76</key>
+        <array>
+            <string>piccolo8,piccolo1,piccolo9,piccolo3,piccolo4,piccolo5</string>
+        </array>
+        <key>77</key>
+        <array>
+            <string>piccolo8,piccolo1,piccolo9,piccolo3,piccolo4,piccolo6,piccolo7</string>
+        </array>
+        <key>78</key>
+        <array>
+            <string>piccolo8,piccolo1,piccolo9,piccolo3,piccolo5,piccolo6</string>
+        </array>
+        <key>79</key>
+        <array>
+            <string>piccolo8,piccolo1,piccolo9,piccolo3</string>
+        </array>
+        <key>80</key>
+        <array>
+            <string>piccolo8,piccolo1,piccolo9,piccolo4,piccolo5,piccolo10</string>
+        </array>
+        <key>81</key>
+        <array>
+            <string>piccolo8,piccolo1,piccolo9</string>
+        </array>
+        <key>82</key>
+        <array>
+            <string>piccolo8,piccolo1,piccolo3,piccolo4</string>
+            <string>piccolo8,piccolo2,piccolo3,piccolo4</string>
+        </array>
+        <key>83</key>
+        <array>
+            <string>piccolo8,piccolo1</string>
+            <string>piccolo8,piccolo9,piccolo3</string>
+        </array>
+        <key>84</key>
+        <array>
+            <string>piccolo8,piccolo9</string>
+        </array>
+        <key>85</key>
+        <array>
+            <string>piccolo1,piccolo9</string>
+            <string>piccolo8</string>
+        </array>
+        <key>86</key>
+        <array>
+            <string>piccolo9</string>
+        </array>
+        <key>87</key>
+        <array>
+            <string>piccolo9,piccolo3,piccolo4,piccolo5,piccolo10</string>
+        </array>
+        <key>88</key>
+        <array>
+            <string>piccolo0,piccolo1,piccolo9,piccolo3,piccolo4,piccolo5</string>
+        </array>
+        <key>89</key>
+        <array>
+            <string>piccolo0,piccolo1,piccolo9,piccolo3,piccolo4,piccolo10</string>
+        </array>
+        <key>90</key>
+        <array>
+            <string>piccolo0,piccolo1,piccolo9,piccolo3,piccolo5</string>
+        </array>
+        <key>91</key>
+        <array>
+            <string>piccolo0,piccolo1,piccolo9,piccolo3</string>
+        </array>
+        <key>92</key>
+        <array>
+            <string>piccolo0,piccolo1,piccolo9,piccolo4</string>
+        </array>
+        <key>93</key>
+        <array>
+            <string>piccolo0,piccolo1,piccolo9</string>
+        </array>
+        <key>94</key>
+        <array>
+            <string>piccolo0,piccolo1,piccolo9,piccolo4,piccolo5,piccolo10</string>
+        </array>
+        <key>95</key>
+        <array>
+            <string>piccolo0,piccolo1,piccolo9,piccolo4,piccolo5</string>
+        </array>
+        <key>96</key>
+        <array>
+            <string>piccolo0,piccolo1,piccolo4,piccolo5</string>
+        </array>
+        <key>97</key>
+        <array>
+            <string>piccolo0,piccolo1,piccolo2,piccolo3,piccolo4,piccolo10,piccolo11</string>
+        </array>
+        <key>98</key>
+        <array>
+            <string>piccolo0,piccolo1,piccolo3,piccolo4,piccolo10,piccolo7</string>
+        </array>        
+    </dict>
+</dict>
+</plist>

+ 30 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/Model/KSParseMessageModel.h

@@ -0,0 +1,30 @@
+//
+//  KSParseMessageModel.h
+//  MidiPlayer
+//
+//  Created by Kyle on 2021/12/2.
+//
+
+#import <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface KSParseMessageModel : NSObject
+
+@property (nonatomic, assign) NSInteger elementIndex;
+
+@property (nonatomic, assign) NSInteger pageIndex;
+
+@property (nonatomic, assign) double elementTimePosition;
+
+@property (nonatomic, assign) double positionX;
+
+@property (nonatomic, assign) double positionY;
+
+@property (nonatomic, assign) double elementWidth;
+
+@property (nonatomic, assign) double elementHeight;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 13 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/Model/KSParseMessageModel.m

@@ -0,0 +1,13 @@
+//
+//  KSParseMessageModel.m
+//  MidiPlayer
+//
+//  Created by Kyle on 2021/12/2.
+//
+
+#import "KSParseMessageModel.h"
+
+@implementation KSParseMessageModel
+
+
+@end

+ 23 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/Model/KSXMLInfoParse.h

@@ -0,0 +1,23 @@
+//
+//  KSXMLInfoParse.h
+//  StudentDaya
+//
+//  Created by Kyle on 2021/12/8.
+//  Copyright © 2021 DayaMusic. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface KSXMLInfoParse : NSObject
+
++ (NSMutableDictionary *)getInfoFromXMLMusicFile:(NSString *)filePath;
+
++ (double)parsePitchToHZ:(double)pitch;
+
++ (int)ParseHZToPitch:(double)hz;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 324 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/Model/KSXMLInfoParse.m

@@ -0,0 +1,324 @@
+//
+//  KSXMLInfoParse.m
+//  StudentDaya
+//
+//  Created by Kyle on 2021/12/8.
+//  Copyright © 2021 DayaMusic. All rights reserved.
+//
+
+#import "KSXMLInfoParse.h"
+#import "TBXML.h"
+
+#define GET_SUB_INT(var, sub, parent_elem)  \
+{                               \
+if(parent_elem){    \
+TBXMLElement *divisions_elem = [TBXML childElementNamed:sub parentElement:parent_elem]; \
+if (divisions_elem!=nil) {      \
+var = [[TBXML textForElement:divisions_elem] intValue];   \
+}                             \
+}   \
+}
+
+#define GET_SUB_STR(var, sub, parent_elem)  \
+{                           \
+if(parent_elem){        \
+TBXMLElement *temp_elem = [TBXML childElementNamed:sub parentElement:parent_elem]; \
+if (temp_elem!=nil) {  \
+var = [TBXML textForElement:temp_elem];   \
+}else{var=nil;}                    \
+}   \
+}
+
+#define DEFAULT_TEMPO (120)
+#define QUARTER_TICK (480) // 一个四分音符tick时钟数
+@implementation KSXMLInfoParse
+
++ (NSMutableDictionary *)getInfoFromXMLMusicFile:(NSString *)filePath {
+    NSMutableDictionary *parm = [NSMutableDictionary dictionary];
+    
+    NSMutableArray *infoArray = [NSMutableArray array];
+    
+    NSFileManager *fileManager = [NSFileManager defaultManager];
+    BOOL isDirExist = [fileManager fileExistsAtPath:filePath];
+    if (isDirExist == NO) {
+        // 当前文件不存在
+        return nil;
+    }
+    NSData *xml_data = [NSData dataWithContentsOfFile:filePath];
+    TBXMLElement *element;
+    NSError *error;
+    TBXML *tbxml = [[TBXML alloc] initWithXMLData:xml_data error:&error];
+    element = [tbxml rootXMLElement];
+    if (element == NULL) {
+        NSLog(@"Fail to parse xml file. error=%@", error);
+        return nil;
+    }
+    
+    NSString *name = [TBXML elementName:element];
+    if (![name isEqualToString:@"score-partwise"]) {
+        NSLog(@"this is not a MusicXML %@",name);
+        return nil;
+    }
+//    NSMutableArray *parts = [[NSMutableArray alloc] init];
+    int max_measures = 0;
+    element = element->firstChild;
+    NSInteger partIndex = 0;
+    do {
+        name = [TBXML elementName:element];
+        if ([name isEqualToString:@"part"]) {
+            NSString *part_id = [TBXML valueOfAttributeNamed:@"id" forElement:element];
+            if ([part_id isEqualToString:@"1"]) {
+                
+            }
+            int measure_index = 0;
+            int note_index = 0;
+            TBXMLElement *measure_elem = element->firstChild;
+            NSMutableArray *partNodeArray = [NSMutableArray array];
+            float tick = 0;
+            do {
+                // 解析小节信息
+               NSMutableArray *nodeArray =  [self parseMeasure:measure_elem measureIndex:&measure_index nodeIndex:&note_index tick:&tick];
+                if (nodeArray.count) {
+                    [partNodeArray addObjectsFromArray:nodeArray];
+                }
+                measure_elem = measure_elem->nextSibling;
+                measure_index++;
+                if (measure_elem==nil) {
+                    break;
+                }
+            } while (true);
+            
+            // 获取最大小节数
+            if (max_measures < measure_index) {
+                max_measures = measure_index;
+            }
+            if (partIndex == 0) {
+                infoArray = [NSMutableArray arrayWithArray:partNodeArray];
+            }
+            partIndex++;
+        }
+        element = element->nextSibling;
+        if (element==nil) {
+            break;
+        }
+    } while (true);
+    
+    [parm setValue:infoArray forKey:@"noteInfo"];
+    NSString *beat = [NSString stringWithFormat:@"%d/%d", last_numerator, last_denominator];
+    [parm setValue:beat forKey:@"beatType"];
+    return parm;
+}
+
++ (NSMutableArray *)parseMeasure:(TBXMLElement *)measure_elem measureIndex:(int *)measureIndex nodeIndex:(int *)nodeIndex tick:(float *)tick {
+    NSMutableArray *array = [NSMutableArray array];
+    NSString *name = [TBXML elementName:measure_elem];
+    if ([name isEqualToString:@"measure"]) {
+        //notes
+        TBXMLElement *note_elem = measure_elem->firstChild;
+        do {
+            name = [TBXML elementName:note_elem];
+            if ([name isEqualToString:@"note"]) {
+               NSMutableDictionary *parm = [self parseNote:note_elem tick:tick measureIndex:measureIndex nodeIndex:nodeIndex];
+                if (parm != nil) {
+                    *nodeIndex = *nodeIndex + 1;
+                    [array addObject:parm];
+                }
+            }
+            else if ([name isEqualToString:@"attributes"]) {
+                //divisions
+                TBXMLElement *divisions_elem = [TBXML childElementNamed:@"divisions" parentElement:note_elem];
+                if (divisions_elem != nil) {
+                    last_divisions = [[TBXML textForElement:divisions_elem] intValue];
+                }
+                
+                //time
+                TBXMLElement *time_elem = [TBXML childElementNamed:@"time" parentElement:note_elem];
+                if(time_elem != nil)
+                {
+                    //beats
+                    GET_SUB_INT(last_numerator, @"beats", time_elem);
+                    //beat-type
+                    GET_SUB_INT(last_denominator, @"beat-type", time_elem);
+                }
+            }
+            note_elem = note_elem->nextSibling;
+            
+            if (note_elem==nil) {
+                break;
+            }
+        } while (true);
+    }
+    else {
+        NSLog(@"error unknow element:%@", name);
+    }
+    return array;
+}
+
++ (NSMutableDictionary *)parseNote:(TBXMLElement *)note_elem tick:(float *)tick measureIndex:(int *)measureIndex nodeIndex:(int *)nodeIndex {
+    NSMutableDictionary *parm = [NSMutableDictionary dictionary];
+    NSString *name = [TBXML elementName:note_elem];
+    if (note_elem==nil) {
+        return nil;
+    }
+    if ([name isEqualToString:@"note"]) {
+        BOOL isChord = NO, isRest = NO;
+        BOOL isListen = NO;
+        int duration=0;
+        int pitch_step = 0,pitch_octave = 0,pitch_alter = 0,note_value = 0;
+        NSInteger noteDenominator = 1;
+        TBXMLElement *elem = note_elem->firstChild;
+        while (elem) {
+            name = [TBXML elementName:elem];
+            if ([name isEqualToString:@"duration"]) { // 时值
+                int nodeDuration = [[TBXML textForElement:elem] intValue];
+                duration = nodeDuration * 1.0 / last_divisions * QUARTER_TICK;
+            }
+            else if ([name isEqualToString:@"type"]) { // 音符类型
+                NSString* type = nil;           //256th, 128th, 64th, 32nd, 16th, eighth, quarter, half, whole, breve, and long
+                GET_SUB_STR(type, @"type", note_elem);
+                noteDenominator = [self noteType:type];
+            }
+            else if ([name isEqualToString:@"chord"]) {
+                isChord = YES;
+            }
+            else if ([name isEqualToString:@"rest"]) { // 休息
+                isRest = YES;
+            }
+            else if ([name isEqualToString:@"pitch"]) { // 音阶
+                NSString *step=nil;
+                GET_SUB_STR(step, @"step", elem);
+                if (step) {
+                    if ([step isEqualToString:@"C"])pitch_step=1;
+                    else if ([step isEqualToString:@"D"])pitch_step=2;
+                    else if ([step isEqualToString:@"E"])pitch_step=3;
+                    else if ([step isEqualToString:@"F"])pitch_step=4;
+                    else if ([step isEqualToString:@"G"])pitch_step=5;
+                    else if ([step isEqualToString:@"A"])pitch_step=6;
+                    else if ([step isEqualToString:@"B"])pitch_step=7;
+                    else NSLog(@"Error: unknow pitch step=%@",step);
+                }
+                GET_SUB_INT(pitch_alter, @"alter", elem);
+                GET_SUB_INT(pitch_octave, @"octave", elem);
+                note_value = [self noteValueForStep:pitch_step octave:pitch_octave alter:pitch_alter];
+            }
+            else if ([name isEqualToString:@"unpitched"]) { // 休息
+//                isRest = YES;
+                NSString *step=nil;
+                GET_SUB_STR(step, @"step", elem);
+                if (step) {
+                    if ([step isEqualToString:@"C"])pitch_step=1;
+                    else if ([step isEqualToString:@"D"])pitch_step=2;
+                    else if ([step isEqualToString:@"E"])pitch_step=3;
+                    else if ([step isEqualToString:@"F"])pitch_step=4;
+                    else if ([step isEqualToString:@"G"])pitch_step=5;
+                    else if ([step isEqualToString:@"A"])pitch_step=6;
+                    else if ([step isEqualToString:@"B"])pitch_step=7;
+                    else NSLog(@"Error: unknow pitch step=%@",step);
+                }
+                GET_SUB_INT(pitch_alter, @"alter", elem);
+                GET_SUB_INT(pitch_octave, @"octave", elem);
+                note_value = [self noteValueForStep:pitch_step octave:pitch_octave alter:pitch_alter];
+            }
+            else if ([name isEqualToString:@"lyric"]) { // 歌词  获取listen play
+                NSString *text = @"";
+                GET_SUB_STR(text, @"text", elem);
+                if ([text containsString:@"listen"] || [text containsString:@"Listen"]) {
+                    isListen = YES;
+                }
+            }
+            elem = elem->nextSibling;
+        }
+        // 解析
+        if (isRest) {
+            *tick = *tick + duration;
+            return nil;
+        }
+        else if (isChord) {
+            return nil;
+        }
+        else {
+            
+            // timeStamp duration frequency nextFrequency measureIndex dontEvaluating musicalNotesIndex
+            // tick 转 时间
+            // 一拍时常 beatDuration
+            double beatDuration = 60.0f / DEFAULT_TEMPO;
+            // tick 时常
+            // 获取一个四分音符时常
+            double quaterDuartion = beatDuration * last_denominator / 4;
+            double tickDuration =  quaterDuartion / QUARTER_TICK * 1000;
+            // 计算频率
+            float timeStamp = *tick * tickDuration;
+            float durationStamp = duration * tickDuration;
+            float frequency =  [self parsePitchToHZ:note_value];
+            float nextFrequency =  [self parsePitchToHZ:note_value+1];
+            
+            [parm setValue:@(timeStamp) forKey:@"timeStamp"];
+            [parm setValue:@(durationStamp) forKey:@"duration"];
+            [parm setValue:@(frequency) forKey:@"frequency"];
+            [parm setValue:@(nextFrequency) forKey:@"nextFrequency"];
+            [parm setValue:@(isListen) forKey:@"dontEvaluating"];
+            [parm setValue:@(*measureIndex) forKey:@"measureIndex"];
+            [parm setValue:@(*nodeIndex) forKey:@"musicalNotesIndex"];
+            [parm setValue:@(noteDenominator) forKey:@"denominator"];
+            [parm setValue:@(note_value) forKey:@"note_value"];
+            *tick = *tick + duration;
+        }
+    }
+    
+    return parm;
+}
+
++ (double)parsePitchToHZ:(double)pitch {
+    return pow(2, (pitch - 69)/12)*442;
+}
+
++ (int)ParseHZToPitch:(double)hz {
+    return log2(hz);
+}
+
+
+//pitch_step:1..7
+//pitch_alter:-1,0,+1
++ (int)noteValueForStep:(int)pitch_step octave:(int)pitch_octave alter:(int)pitch_alter {
+    int note_value=(1+pitch_octave)*12+pitch_alter;
+    if (pitch_step==2) { //D
+        note_value+=2;
+    }else if (pitch_step==3) { //E
+        note_value+=4;
+    }else if (pitch_step==4) { //F
+        note_value+=5;
+    }else if (pitch_step==5) { //G
+        note_value+=7;
+    }else if (pitch_step==6) { //A
+        note_value+=9;
+    }else if (pitch_step==7) { //B
+        note_value+=11;
+    }
+    return note_value;
+}
+
+
++ (NSInteger)noteType:(NSString *)type{
+    NSInteger noteDenominator = 0;
+    if ([NSString isEmptyString:type]) {
+        noteDenominator = 1;
+    }
+    else if ([type isEqualToString:@"whole"]) {
+        noteDenominator = 1;
+    }else if ([type isEqualToString:@"half"]) {
+        noteDenominator = 2;
+    }else if ([type isEqualToString:@"quarter"]) {
+        noteDenominator = 4;
+    }else if ([type isEqualToString:@"eighth"]) {
+        noteDenominator = 8;
+    }else if ([type isEqualToString:@"16th"]) {
+        noteDenominator = 16;
+    }else {
+        noteDenominator = 32;
+    }
+    return noteDenominator;
+}
+
+static int last_divisions = 0; // 四分音符标准时常
+static int last_numerator = 4, last_denominator = 4;
+@end

+ 24 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/Model/KYSourceParseManager.h

@@ -0,0 +1,24 @@
+//
+//  KYSourceParseManager.h
+//  MidiPlayer
+//
+//  Created by Kyle on 2021/12/2.
+//
+
+#import <Foundation/Foundation.h>
+#import "KSParseMessageModel.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface KYSourceParseManager : NSObject
+
+@property (nonatomic, strong) NSMutableArray *measureMessageArray;
+
+@property (nonatomic, strong) NSMutableArray *nodeMessageArray;
+
+
+- (BOOL)parseMessageSource:(NSString *)measureSourcePath nodeSource:(NSString *)nodeSourcePath;
+ 
+@end
+
+NS_ASSUME_NONNULL_END

+ 145 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/Model/KYSourceParseManager.m

@@ -0,0 +1,145 @@
+//
+//  KYSourceParseManager.m
+//  MidiPlayer
+//
+//  Created by Kyle on 2021/12/2.
+//
+
+#import "KYSourceParseManager.h"
+#import "TBXML.h"
+
+
+@implementation KYSourceParseManager
+
+- (BOOL)parseMessageSource:(NSString *)measureSourcePath nodeSource:(NSString *)nodeSourcePath {
+    NSFileManager *fileManager = [NSFileManager defaultManager];
+    BOOL measureFileExist = [fileManager fileExistsAtPath:measureSourcePath];
+    BOOL nodeFileExist = [fileManager fileExistsAtPath:nodeSourcePath];
+    if (measureFileExist == NO || nodeFileExist == NO) {
+        NSLog(@"文件不存在");
+        return NO;
+    }
+    TBXMLElement *element;
+    NSError *error;
+    NSData *measureData = [NSData dataWithContentsOfFile:measureSourcePath];
+    TBXML *tbxml = [[TBXML alloc] initWithXMLData:measureData error:&error];
+    if (error) {
+        NSLog(@"%@", error.description);
+        return NO;
+    }
+    element = [tbxml rootXMLElement];
+    if (element == nil) {
+        NSLog(@"Fail to parse xml file. error=%@", error);
+        return NO;
+    }
+    NSString *name = [TBXML elementName:element];
+    
+    if ([name isEqualToString:@"score"]) {
+        self.measureMessageArray = [self parseScoreWithSource:element];
+    }
+    
+    NSData *nodeData = [NSData dataWithContentsOfFile:nodeSourcePath];
+    TBXML *nodeTbxml = [[TBXML alloc] initWithXMLData:nodeData error:&error];
+    if (error) {
+        NSLog(@"%@", error.description);
+        return NO;
+    }
+    TBXMLElement *nodeElement = [nodeTbxml rootXMLElement];
+    if (nodeElement == nil) {
+        NSLog(@"Fail to parse xml file. error=%@", error);
+        return NO;
+    }
+    NSString *nodeName = [TBXML elementName:nodeElement];
+    if ([nodeName isEqualToString:@"score"]) {
+        self.nodeMessageArray = [self parseScoreWithSource:nodeElement];
+    }
+    return YES;
+}
+
+- (NSMutableArray *)parseScoreWithSource:(TBXMLElement *)element {
+    NSMutableArray *sourceArray = [NSMutableArray array];
+    NSMutableArray *positionArray = [NSMutableArray array];
+    NSMutableArray *timeArray = [NSMutableArray array];
+    TBXMLElement *temp_elem = element->firstChild;
+    do {
+        NSString *name = [TBXML elementName:temp_elem];
+        if ([name isEqualToString:@"elements"]) { // 位置信息
+            positionArray = [self getDetailSourceWithElement:temp_elem];
+        }
+        else if ([name isEqualToString:@"events"]) { // 时间信息
+            timeArray = [self getDetailSourceWithElement:temp_elem];
+        }
+        
+        temp_elem = temp_elem->nextSibling;
+        if (temp_elem == nil) {
+            break;
+        }
+    } while (YES);
+    
+    // 赋值
+    if (positionArray.count != timeArray.count) {
+        return nil;
+    }
+    for (NSInteger i = 0; i < positionArray.count; i++) {
+        KSParseMessageModel *model = [[KSParseMessageModel alloc] init];
+        NSDictionary *positionDic = [positionArray objectAtIndex:i];
+        model.elementIndex = [[positionDic valueForKey:@"id"] integerValue];
+        model.positionX = [[positionDic valueForKey:@"x"] doubleValue];
+        model.positionY = [[positionDic valueForKey:@"y"] doubleValue];
+        double width = [[positionDic valueForKey:@"sx"] doubleValue];
+        if (width == 0) {
+            width = 700.0f;
+        }
+        model.elementWidth = width;
+        model.elementHeight = [[positionDic valueForKey:@"sy"] doubleValue];
+        model.pageIndex = [[positionDic valueForKey:@"page"] integerValue];
+        NSDictionary *timeDic = [timeArray objectAtIndex:i];
+        model.elementTimePosition = [[timeDic valueForKey:@"position"] integerValue];
+        [sourceArray addObject:model];
+    }
+    return sourceArray;
+}
+
+- (NSMutableArray *)getDetailSourceWithElement:(TBXMLElement *)elements {
+    NSMutableArray *messageArray = [NSMutableArray array];
+    TBXMLElement *sub_elem = elements->firstChild;
+    NSString *name = [TBXML elementName:sub_elem];
+    BOOL isElement = [name isEqualToString:@"element"];
+    
+    do {
+        
+        NSMutableDictionary *valueDic = [NSMutableDictionary dictionary];
+        if (isElement) {
+            NSString *elemId = [TBXML valueOfAttributeNamed:@"id" forElement:sub_elem];
+            [valueDic setValue:elemId forKey:@"id"];
+            
+            double positionX = [[TBXML valueOfAttributeNamed:@"x" forElement:sub_elem] doubleValue];
+            [valueDic setValue:@(positionX) forKey:@"x"];
+            double positionY = [[TBXML valueOfAttributeNamed:@"y" forElement:sub_elem] doubleValue];
+            [valueDic setValue:@(positionY) forKey:@"y"];
+            
+            double width = [[TBXML valueOfAttributeNamed:@"sx" forElement:sub_elem] doubleValue];
+            [valueDic setValue:@(width) forKey:@"sx"];
+            double height = [[TBXML valueOfAttributeNamed:@"sy" forElement:sub_elem] doubleValue];
+            [valueDic setValue:@(height) forKey:@"sy"];
+            
+            NSInteger pageIndex = [[TBXML valueOfAttributeNamed:@"page" forElement:sub_elem] doubleValue];
+            [valueDic setValue:@(pageIndex) forKey:@"page"];
+        }
+        else {
+            NSInteger position = [[TBXML valueOfAttributeNamed:@"position" forElement:sub_elem] integerValue];
+            [valueDic setValue:@(position) forKey:@"position"];
+        }
+        [messageArray addObject:valueDic];
+        
+        sub_elem = sub_elem->nextSibling;
+        if (sub_elem == nil) {
+            break;
+        }
+    } while (YES);
+    
+    return messageArray;
+}
+
+
+@end

+ 76 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/Model/SubjectFinger.plist

@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>2</key>
+	<dict>
+		<key>vertical</key>
+		<false/>
+		<key>name</key>
+		<string>flute</string>
+	</dict>
+	<key>4</key>
+	<dict>
+		<key>vertical</key>
+		<true/>
+		<key>name</key>
+		<string>clarinet</string>
+	</dict>
+	<key>5</key>
+	<dict>
+		<key>vertical</key>
+		<true/>
+		<key>name</key>
+		<string>saxophone</string>
+	</dict>
+	<key>6</key>
+	<dict>
+		<key>vertical</key>
+		<true/>
+		<key>name</key>
+		<string>saxophone</string>
+	</dict>
+	<key>12</key>
+	<dict>
+		<key>vertical</key>
+		<false/>
+		<key>name</key>
+		<string>trumpet</string>
+	</dict>
+	<key>13</key>
+	<dict>
+		<key>vertical</key>
+		<true/>
+		<key>name</key>
+		<string>horn</string>
+	</dict>
+	<key>14</key>
+	<dict>
+		<key>vertical</key>
+		<false/>
+		<key>name</key>
+		<string>trombone</string>
+	</dict>
+	<key>15</key>
+	<dict>
+		<key>vertical</key>
+		<true/>
+		<key>name</key>
+		<string>up-bass-horn</string>
+	</dict>
+	<key>17</key>
+	<dict>
+		<key>vertical</key>
+		<true/>
+		<key>name</key>
+		<string>tuba</string>
+	</dict>
+	<key>120</key>
+	<dict>
+		<key>vertical</key>
+		<true/>
+		<key>name</key>
+		<string>piccolo</string>
+	</dict>
+</dict>
+</plist>

+ 211 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/Model/TBXML.h

@@ -0,0 +1,211 @@
+// ================================================================================================
+//  TBXML.h
+//  Fast processing of XML files
+//
+// ================================================================================================
+//  Created by Tom Bradley on 21/10/2009.
+//  Version 1.5
+//  
+//  Copyright 2012 71Squared All rights reserved.b
+//  
+//  Permission is hereby granted, free of charge, to any person obtaining a copy
+//  of this software and associated documentation files (the "Software"), to deal
+//  in the Software without restriction, including without limitation the rights
+//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+//  copies of the Software, and to permit persons to whom the Software is
+//  furnished to do so, subject to the following conditions:
+//  
+//  The above copyright notice and this permission notice shall be included in
+//  all copies or substantial portions of the Software.
+//  
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+//  THE SOFTWARE.
+// ================================================================================================
+#import <Foundation/Foundation.h>
+
+@class TBXML;
+
+
+// ================================================================================================
+//  Error Codes
+// ================================================================================================
+enum TBXMLErrorCodes {
+    D_TBXML_SUCCESS = 0,
+
+    D_TBXML_DATA_NIL,
+    D_TBXML_DECODE_FAILURE,
+    D_TBXML_MEMORY_ALLOC_FAILURE,
+    D_TBXML_FILE_NOT_FOUND_IN_BUNDLE,
+    
+    D_TBXML_ELEMENT_IS_NIL,
+    D_TBXML_ELEMENT_NAME_IS_NIL,
+    D_TBXML_ELEMENT_NOT_FOUND,
+    D_TBXML_ELEMENT_TEXT_IS_NIL,
+    D_TBXML_ATTRIBUTE_IS_NIL,
+    D_TBXML_ATTRIBUTE_NAME_IS_NIL,
+    D_TBXML_ATTRIBUTE_NOT_FOUND,
+    D_TBXML_PARAM_NAME_IS_NIL
+};
+
+
+// ================================================================================================
+//  Defines
+// ================================================================================================
+#define D_TBXML_DOMAIN @"com.71squared.tbxml"
+
+#define MAX_ELEMENTS 100
+#define MAX_ATTRIBUTES 100
+
+#define TBXML_ATTRIBUTE_NAME_START 0
+#define TBXML_ATTRIBUTE_NAME_END 1
+#define TBXML_ATTRIBUTE_VALUE_START 2
+#define TBXML_ATTRIBUTE_VALUE_END 3
+#define TBXML_ATTRIBUTE_CDATA_END 4
+
+// ================================================================================================
+//  Structures
+// ================================================================================================
+
+/** The TBXMLAttribute structure holds information about a single XML attribute. The structure holds the attribute name, value and next sibling attribute. This structure allows us to create a linked list of attributes belonging to a specific element.
+ */
+typedef struct _TBXMLAttribute {
+	char * name;
+	char * value;
+	struct _TBXMLAttribute * next;
+} TBXMLAttribute;
+
+
+
+/** The TBXMLElement structure holds information about a single XML element. The structure holds the element name & text along with pointers to the first attribute, parent element, first child element and first sibling element. Using this structure, we can create a linked list of TBXMLElements to map out an entire XML file.
+ */
+typedef struct _TBXMLElement {
+	char * name;
+	char * text;
+	
+	TBXMLAttribute * firstAttribute;
+	
+	struct _TBXMLElement * parentElement;
+	
+	struct _TBXMLElement * firstChild;
+	struct _TBXMLElement * currentChild;
+	
+	struct _TBXMLElement * nextSibling;
+	struct _TBXMLElement * previousSibling;
+	
+} TBXMLElement;
+
+/** The TBXMLElementBuffer is a structure that holds a buffer of TBXMLElements. When the buffer of elements is used, an additional buffer is created and linked to the previous one. This allows for efficient memory allocation/deallocation elements.
+ */
+typedef struct _TBXMLElementBuffer {
+	TBXMLElement * elements;
+	struct _TBXMLElementBuffer * next;
+	struct _TBXMLElementBuffer * previous;
+} TBXMLElementBuffer;
+
+
+
+/** The TBXMLAttributeBuffer is a structure that holds a buffer of TBXMLAttributes. When the buffer of attributes is used, an additional buffer is created and linked to the previous one. This allows for efficient memeory allocation/deallocation of attributes.
+ */
+typedef struct _TBXMLAttributeBuffer {
+	TBXMLAttribute * attributes;
+	struct _TBXMLAttributeBuffer * next;
+	struct _TBXMLAttributeBuffer * previous;
+} TBXMLAttributeBuffer;
+
+
+// ================================================================================================
+//  Block Callbacks
+// ================================================================================================
+typedef void (^TBXMLSuccessBlock)(TBXML *tbxml);
+typedef void (^TBXMLFailureBlock)(TBXML *tbxml, NSError *error);
+typedef void (^TBXMLIterateBlock)(TBXMLElement *element);
+typedef void (^TBXMLIterateAttributeBlock)(TBXMLAttribute *attribute, NSString *attributeName, NSString *attributeValue);
+
+
+// ================================================================================================
+//  TBXML Public Interface
+// ================================================================================================
+
+@interface TBXML : NSObject {
+	
+@private
+	TBXMLElement * rootXMLElement;
+	
+	TBXMLElementBuffer * currentElementBuffer;
+	TBXMLAttributeBuffer * currentAttributeBuffer;
+	
+	long currentElement;
+	long currentAttribute;
+	
+	char * bytes;
+	long bytesLength;
+}
+
+
+@property (nonatomic, readonly) TBXMLElement * rootXMLElement;
+
++ (id)newTBXMLWithXMLString:(NSString*)aXMLString error:(NSError **)error;
++ (id)newTBXMLWithXMLData:(NSData*)aData error:(NSError **)error;
++ (id)newTBXMLWithXMLFile:(NSString*)aXMLFile error:(NSError **)error;
++ (id)newTBXMLWithXMLFile:(NSString*)aXMLFile fileExtension:(NSString*)aFileExtension error:(NSError **)error;
+
++ (id)newTBXMLWithXMLString:(NSString*)aXMLString __attribute__((deprecated));
++ (id)newTBXMLWithXMLData:(NSData*)aData __attribute__((deprecated));
++ (id)newTBXMLWithXMLFile:(NSString*)aXMLFile __attribute__((deprecated));
++ (id)newTBXMLWithXMLFile:(NSString*)aXMLFile fileExtension:(NSString*)aFileExtension __attribute__((deprecated));
+
+
+- (id)initWithXMLString:(NSString*)aXMLString error:(NSError **)error;
+- (id)initWithXMLData:(NSData*)aData error:(NSError **)error;
+- (id)initWithXMLFile:(NSString*)aXMLFile error:(NSError **)error;
+- (id)initWithXMLFile:(NSString*)aXMLFile fileExtension:(NSString*)aFileExtension error:(NSError **)error;
+
+- (id)initWithXMLString:(NSString*)aXMLString __attribute__((deprecated));
+- (id)initWithXMLData:(NSData*)aData __attribute__((deprecated));
+- (id)initWithXMLFile:(NSString*)aXMLFile __attribute__((deprecated));
+- (id)initWithXMLFile:(NSString*)aXMLFile fileExtension:(NSString*)aFileExtension __attribute__((deprecated));
+
+
+- (int) decodeData:(NSData*)data;
+- (int) decodeData:(NSData*)data withError:(NSError **)error;
+
+@end
+
+// ================================================================================================
+//  TBXML Static Functions Interface
+// ================================================================================================
+
+@interface TBXML (StaticFunctions)
+
++ (NSString*) elementName:(TBXMLElement*)aXMLElement;
++ (NSString*) elementName:(TBXMLElement*)aXMLElement error:(NSError **)error;
++ (NSString*) textForElement:(TBXMLElement*)aXMLElement;
++ (NSString*) textForElement:(TBXMLElement*)aXMLElement error:(NSError **)error;
++ (NSString*) valueOfAttributeNamed:(NSString *)aName forElement:(TBXMLElement*)aXMLElement;
++ (NSString*) valueOfAttributeNamed:(NSString *)aName forElement:(TBXMLElement*)aXMLElement error:(NSError **)error;
+
++ (NSString*) attributeName:(TBXMLAttribute*)aXMLAttribute;
++ (NSString*) attributeName:(TBXMLAttribute*)aXMLAttribute error:(NSError **)error;
++ (NSString*) attributeValue:(TBXMLAttribute*)aXMLAttribute;
++ (NSString*) attributeValue:(TBXMLAttribute*)aXMLAttribute error:(NSError **)error;
+
++ (TBXMLElement*) nextSiblingNamed:(NSString*)aName searchFromElement:(TBXMLElement*)aXMLElement;
++ (TBXMLElement*) childElementNamed:(NSString*)aName parentElement:(TBXMLElement*)aParentXMLElement;
+
++ (TBXMLElement*) nextSiblingNamed:(NSString*)aName searchFromElement:(TBXMLElement*)aXMLElement error:(NSError **)error;
++ (TBXMLElement*) childElementNamed:(NSString*)aName parentElement:(TBXMLElement*)aParentXMLElement error:(NSError **)error;
+
+/** Iterate through all elements found using query.
+ 
+ Inspiration taken from John Blanco's RaptureXML https://github.com/ZaBlanc/RaptureXML
+ */
++ (void)iterateElementsForQuery:(NSString *)query fromElement:(TBXMLElement *)anElement withBlock:(TBXMLIterateBlock)iterateBlock;
++ (void)iterateAttributesOfElement:(TBXMLElement *)anElement withBlock:(TBXMLIterateAttributeBlock)iterateBlock;
+
+
+@end

+ 952 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/Model/TBXML.m

@@ -0,0 +1,952 @@
+// ================================================================================================
+//  TBXML.m
+//  Fast processing of XML files
+//
+// ================================================================================================
+//  Created by Tom Bradley on 21/10/2009.
+//  Version 1.5
+//  
+//  Copyright 2012 71Squared All rights reserved.
+//  
+//  Permission is hereby granted, free of charge, to any person obtaining a copy
+//  of this software and associated documentation files (the "Software"), to deal
+//  in the Software without restriction, including without limitation the rights
+//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+//  copies of the Software, and to permit persons to whom the Software is
+//  furnished to do so, subject to the following conditions:
+//  
+//  The above copyright notice and this permission notice shall be included in
+//  all copies or substantial portions of the Software.
+//  
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+//  THE SOFTWARE.
+// ================================================================================================
+#import "TBXML.h"
+
+// ================================================================================================
+// Private methods
+// ================================================================================================
+@interface TBXML (Private)
++ (NSString *) errorTextForCode:(int)code;
++ (NSError *) errorWithCode:(int)code;
++ (NSError *) errorWithCode:(int)code userInfo:(NSDictionary *)userInfo;
+- (void) decodeBytes;
+- (int) allocateBytesOfLength:(long)length error:(NSError **)error;
+- (TBXMLElement*) nextAvailableElement;
+- (TBXMLAttribute*) nextAvailableAttribute;
+@end
+
+// ================================================================================================
+// Public Implementation
+// ================================================================================================
+@implementation TBXML
+
+@synthesize rootXMLElement;
+
++ (id)newTBXMLWithXMLString:(NSString*)aXMLString {
+	return [[TBXML alloc] initWithXMLString:aXMLString];
+}
+
++ (id)newTBXMLWithXMLString:(NSString*)aXMLString error:(NSError *__autoreleasing *)error {
+	return [[TBXML alloc] initWithXMLString:aXMLString error:error];
+}
+
++ (id)newTBXMLWithXMLData:(NSData*)aData {
+	return [[TBXML alloc] initWithXMLData:aData];
+}
+
++ (id)newTBXMLWithXMLData:(NSData*)aData error:(NSError *__autoreleasing *)error {
+	return [[TBXML alloc] initWithXMLData:aData error:error];
+}
+
++ (id)newTBXMLWithXMLFile:(NSString*)aXMLFile {
+	return [[TBXML alloc] initWithXMLFile:aXMLFile];
+}
+
++ (id)newTBXMLWithXMLFile:(NSString*)aXMLFile error:(NSError *__autoreleasing *)error {
+	return [[TBXML alloc] initWithXMLFile:aXMLFile error:error];
+}
+
++ (id)newTBXMLWithXMLFile:(NSString*)aXMLFile fileExtension:(NSString*)aFileExtension {
+	return [[TBXML alloc] initWithXMLFile:aXMLFile fileExtension:aFileExtension];
+}
+
++ (id)newTBXMLWithXMLFile:(NSString*)aXMLFile fileExtension:(NSString*)aFileExtension error:(NSError *__autoreleasing *)error {
+	return [[TBXML alloc] initWithXMLFile:aXMLFile fileExtension:aFileExtension error:error];
+}
+
+- (id)init {
+	self = [super init];
+	if (self != nil) {
+		rootXMLElement = nil;
+		
+		currentElementBuffer = 0;
+		currentAttributeBuffer = 0;
+		
+		currentElement = 0;
+		currentAttribute = 0;		
+		
+		bytes = 0;
+		bytesLength = 0;
+	}
+	return self;
+}
+- (id)initWithXMLString:(NSString*)aXMLString {
+    NSError *error = nil;
+    return [self initWithXMLString:aXMLString error:&error];
+}
+
+- (id)initWithXMLString:(NSString*)aXMLString error:(NSError *__autoreleasing *)error {
+	self = [self init];
+	if (self != nil) {
+		
+        
+        // allocate memory for byte array
+        int result = [self allocateBytesOfLength:[aXMLString lengthOfBytesUsingEncoding:NSUTF8StringEncoding] error:error];
+        
+        // if an error occured, return
+        if (result != D_TBXML_SUCCESS) 
+            return self;
+        
+		// copy string to byte array
+		[aXMLString getBytes:bytes maxLength:bytesLength usedLength:0 encoding:NSUTF8StringEncoding options:NSStringEncodingConversionAllowLossy range:NSMakeRange(0, bytesLength) remainingRange:nil];
+		
+		// set null terminator at end of byte array
+		bytes[bytesLength] = 0;
+		
+		// decode xml data
+		[self decodeBytes];
+        
+        // Check for root element
+        if (error && !*error && !self.rootXMLElement) {
+            *error = [TBXML errorWithCode:D_TBXML_DECODE_FAILURE];
+        }
+	}
+	return self;
+}
+
+- (id)initWithXMLData:(NSData*)aData {
+    NSError *error = nil;
+    return [self initWithXMLData:aData error:&error];
+}
+
+- (id)initWithXMLData:(NSData*)aData error:(NSError **)error {
+    self = [self init];
+    if (self != nil) {
+		// decode aData
+		[self decodeData:aData withError:error];
+    }
+    
+    return self;
+}
+
+- (id)initWithXMLFile:(NSString*)aXMLFile {
+    NSError *error = nil;
+    return [self initWithXMLFile:aXMLFile error:&error];
+}
+
+- (id)initWithXMLFile:(NSString*)aXMLFile error:(NSError **)error {
+    NSString * filename = [aXMLFile stringByDeletingPathExtension];
+    NSString * extension = [aXMLFile pathExtension];
+    
+    self = [self initWithXMLFile:filename fileExtension:extension error:error];
+	if (self != nil) {
+        
+	}
+	return self;
+}
+
+- (id)initWithXMLFile:(NSString*)aXMLFile fileExtension:(NSString*)aFileExtension {
+    NSError *error = nil;
+    return [self initWithXMLFile:aXMLFile fileExtension:aFileExtension error:&error];
+}
+
+- (id)initWithXMLFile:(NSString*)aXMLFile fileExtension:(NSString*)aFileExtension error:(NSError **)error {
+	self = [self init];
+	if (self != nil) {
+        
+        NSData * data;
+        
+        // Get the bundle that this class resides in. This allows to load resources from the app bundle when running unit tests.
+        NSString * bundlePath = [[NSBundle bundleForClass:[self class]] pathForResource:aXMLFile ofType:aFileExtension];
+
+        if (!bundlePath) {
+            if (error) {
+                NSDictionary * userInfo = [NSDictionary dictionaryWithObjectsAndKeys:[aXMLFile stringByAppendingPathExtension:aFileExtension], NSFilePathErrorKey, nil];
+                *error = [TBXML errorWithCode:D_TBXML_FILE_NOT_FOUND_IN_BUNDLE userInfo:userInfo];
+            }
+        } else {
+            SEL dataWithUncompressedContentsOfFile = NSSelectorFromString(@"dataWithUncompressedContentsOfFile:");
+            
+            // Get uncompressed file contents if TBXML+Compression has been included
+            if ([[NSData class] respondsToSelector:dataWithUncompressedContentsOfFile]) {
+                
+                #pragma clang diagnostic push
+                #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
+                data = [[NSData class] performSelector:dataWithUncompressedContentsOfFile withObject:bundlePath];
+                #pragma clang diagnostic pop   
+
+            } else {
+                data = [NSData dataWithContentsOfFile:bundlePath];
+            }
+            
+            // decode data
+            [self decodeData:data withError:error];
+            
+            // Check for root element
+            if (error && !*error && !self.rootXMLElement) {
+                *error = [TBXML errorWithCode:D_TBXML_DECODE_FAILURE];
+            }
+        }
+	}
+	return self;
+}
+
+- (int) decodeData:(NSData*)data {
+    NSError *error = nil;
+    return [self decodeData:data withError:&error];
+}
+
+- (int) decodeData:(NSData*)data withError:(NSError **)error {
+    
+    NSError *localError = nil;
+    
+    // allocate memory for byte array
+    int result = [self allocateBytesOfLength:[data length] error:&localError];
+
+    // ensure no errors during allocation
+    if (result == D_TBXML_SUCCESS) {
+        
+        // copy data to byte array
+        [data getBytes:bytes length:bytesLength];
+        
+        // set null terminator at end of byte array
+        bytes[bytesLength] = 0;
+        
+        // decode xml data
+        [self decodeBytes];
+        
+        if (!self.rootXMLElement) {
+            localError = [TBXML errorWithCode:D_TBXML_DECODE_FAILURE];
+        }
+    }
+
+    // assign local error to pointer
+    if (error) *error = localError;
+    
+    // return success or error code
+    return localError == nil ? D_TBXML_SUCCESS : [localError code];
+}
+
+@end
+
+
+// ================================================================================================
+// Static Functions Implementation
+// ================================================================================================
+
+#pragma mark -
+#pragma mark Static Functions implementation
+
+@implementation TBXML (StaticFunctions)
+
++ (NSString*) elementName:(TBXMLElement*)aXMLElement {
+	if (nil == aXMLElement->name) return @"";
+	return [NSString stringWithCString:&aXMLElement->name[0] encoding:NSUTF8StringEncoding];
+}
+
++ (NSString*) elementName:(TBXMLElement*)aXMLElement error:(NSError **)error {
+    // check for nil element
+    if (nil == aXMLElement) {
+        if (error) *error = [TBXML errorWithCode:D_TBXML_ELEMENT_IS_NIL];
+        return @"";
+    }
+    
+    // check for nil element name
+    if (nil == aXMLElement->name || strlen(aXMLElement->name) == 0) {
+        if (error) *error = [TBXML errorWithCode:D_TBXML_ELEMENT_NAME_IS_NIL];
+        return @"";
+    }
+    
+	return [NSString stringWithCString:&aXMLElement->name[0] encoding:NSUTF8StringEncoding];
+}
+
++ (NSString*) attributeName:(TBXMLAttribute*)aXMLAttribute {
+	if (nil == aXMLAttribute->name) return @"";
+	return [NSString stringWithCString:&aXMLAttribute->name[0] encoding:NSUTF8StringEncoding];
+}
+
++ (NSString*) attributeName:(TBXMLAttribute*)aXMLAttribute error:(NSError **)error {
+    // check for nil attribute
+    if (nil == aXMLAttribute) {
+        if (error) *error = [TBXML errorWithCode:D_TBXML_ATTRIBUTE_IS_NIL];
+        return @"";
+    }
+    
+    // check for nil attribute name
+    if (nil == aXMLAttribute->name) {
+        if (error) *error = [TBXML errorWithCode:D_TBXML_ATTRIBUTE_NAME_IS_NIL];
+        return @"";
+    }
+    
+	return [NSString stringWithCString:&aXMLAttribute->name[0] encoding:NSUTF8StringEncoding];
+}
+
+
++ (NSString*) attributeValue:(TBXMLAttribute*)aXMLAttribute {
+	if (nil == aXMLAttribute->value) return @"";
+	return [NSString stringWithCString:&aXMLAttribute->value[0] encoding:NSUTF8StringEncoding];
+}
+
++ (NSString*) attributeValue:(TBXMLAttribute*)aXMLAttribute error:(NSError **)error {
+    // check for nil attribute
+    if (nil == aXMLAttribute) {
+        if (error) *error = [TBXML errorWithCode:D_TBXML_ATTRIBUTE_IS_NIL];
+        return @"";
+    }
+    
+	return [NSString stringWithCString:&aXMLAttribute->value[0] encoding:NSUTF8StringEncoding];
+}
+
++ (NSString*) textForElement:(TBXMLElement*)aXMLElement {
+	if (nil == aXMLElement->text) return @"";
+	return [NSString stringWithCString:&aXMLElement->text[0] encoding:NSUTF8StringEncoding];
+}
+
++ (NSString*) textForElement:(TBXMLElement*)aXMLElement error:(NSError **)error {
+    // check for nil element
+    if (nil == aXMLElement) {
+        if (error) *error = [TBXML errorWithCode:D_TBXML_ELEMENT_IS_NIL];
+        return @"";
+    }
+    
+    // check for nil text value
+    if (nil == aXMLElement->text || strlen(aXMLElement->text) == 0) {
+        if (error) *error = [TBXML errorWithCode:D_TBXML_ELEMENT_TEXT_IS_NIL];
+        return @"";
+    }
+    
+	return [NSString stringWithCString:&aXMLElement->text[0] encoding:NSUTF8StringEncoding];
+}
+
++ (NSString*) valueOfAttributeNamed:(NSString *)aName forElement:(TBXMLElement*)aXMLElement {
+	const char * name = [aName cStringUsingEncoding:NSUTF8StringEncoding];
+	NSString * value = nil;
+	TBXMLAttribute * attribute = aXMLElement->firstAttribute;
+	while (attribute) {
+		if (strlen(attribute->name) == strlen(name) && memcmp(attribute->name,name,strlen(name)) == 0) {
+			value = [NSString stringWithCString:&attribute->value[0] encoding:NSUTF8StringEncoding];
+			break;
+		}
+		attribute = attribute->next;
+	}
+	return value;
+}
+
++ (NSString*) valueOfAttributeNamed:(NSString *)aName forElement:(TBXMLElement*)aXMLElement error:(NSError **)error {
+    // check for nil element
+    if (nil == aXMLElement) {
+        if (error) *error = [TBXML errorWithCode:D_TBXML_ELEMENT_IS_NIL];
+        return @"";
+    }
+    
+    // check for nil name parameter
+    if (nil == aName) {
+        if (error) *error = [TBXML errorWithCode:D_TBXML_ATTRIBUTE_NAME_IS_NIL];
+        return @"";
+    }
+    
+	const char * name = [aName cStringUsingEncoding:NSUTF8StringEncoding];
+	NSString * value = nil;
+    
+	TBXMLAttribute * attribute = aXMLElement->firstAttribute;
+	while (attribute) {
+		if (strlen(attribute->name) == strlen(name) && memcmp(attribute->name,name,strlen(name)) == 0) {
+            if (attribute->value[0])
+                value = [NSString stringWithCString:&attribute->value[0] encoding:NSUTF8StringEncoding];
+            else
+                value = [NSString stringWithString:@""];
+            
+			break;
+		}
+		attribute = attribute->next;
+	}
+    
+    // check for attribute not found
+    if (!value) {
+        if (error) *error = [TBXML errorWithCode:D_TBXML_ATTRIBUTE_NOT_FOUND];
+        return @"";
+    }
+    
+	return value;
+}
+
++ (TBXMLElement*) childElementNamed:(NSString*)aName parentElement:(TBXMLElement*)aParentXMLElement{
+    
+	TBXMLElement * xmlElement = aParentXMLElement->firstChild;
+	const char * name = [aName cStringUsingEncoding:NSUTF8StringEncoding];
+	while (xmlElement) {
+		if (strlen(xmlElement->name) == strlen(name) && memcmp(xmlElement->name,name,strlen(name)) == 0) {
+			return xmlElement;
+		}
+		xmlElement = xmlElement->nextSibling;
+	}
+	return nil;
+}
+
++ (TBXMLElement*) childElementNamed:(NSString*)aName parentElement:(TBXMLElement*)aParentXMLElement error:(NSError **)error {
+    // check for nil element
+    if (nil == aParentXMLElement) {
+        if (error) *error = [TBXML errorWithCode:D_TBXML_ELEMENT_IS_NIL];
+        return nil;
+    }
+    
+    // check for nil name parameter
+    if (nil == aName) {
+        if (error) *error = [TBXML errorWithCode:D_TBXML_PARAM_NAME_IS_NIL];
+        return nil;
+    }
+    
+	TBXMLElement * xmlElement = aParentXMLElement->firstChild;
+	const char * name = [aName cStringUsingEncoding:NSUTF8StringEncoding];
+	while (xmlElement) {
+		if (strlen(xmlElement->name) == strlen(name) && memcmp(xmlElement->name,name,strlen(name)) == 0) {
+			return xmlElement;
+		}
+		xmlElement = xmlElement->nextSibling;
+	}
+    
+    if (error) *error = [TBXML errorWithCode:D_TBXML_ELEMENT_NOT_FOUND];
+    
+	return nil;
+}
+
++ (TBXMLElement*) nextSiblingNamed:(NSString*)aName searchFromElement:(TBXMLElement*)aXMLElement{
+	TBXMLElement * xmlElement = aXMLElement->nextSibling;
+	const char * name = [aName cStringUsingEncoding:NSUTF8StringEncoding];
+	while (xmlElement) {
+		if (strlen(xmlElement->name) == strlen(name) && memcmp(xmlElement->name,name,strlen(name)) == 0) {
+			return xmlElement;
+		}
+		xmlElement = xmlElement->nextSibling;
+	}
+	return nil;
+}
+
++ (TBXMLElement*) nextSiblingNamed:(NSString*)aName searchFromElement:(TBXMLElement*)aXMLElement error:(NSError **)error {
+    // check for nil element
+    if (nil == aXMLElement) {
+        if (error) *error = [TBXML errorWithCode:D_TBXML_ELEMENT_IS_NIL];
+        return nil;
+    }
+    
+    // check for nil name parameter
+    if (nil == aName) {
+        if (error) *error = [TBXML errorWithCode:D_TBXML_PARAM_NAME_IS_NIL];
+        return nil;
+    }
+    
+	TBXMLElement * xmlElement = aXMLElement->nextSibling;
+	const char * name = [aName cStringUsingEncoding:NSUTF8StringEncoding];
+	while (xmlElement) {
+		if (strlen(xmlElement->name) == strlen(name) && memcmp(xmlElement->name,name,strlen(name)) == 0) {
+			return xmlElement;
+		}
+		xmlElement = xmlElement->nextSibling;
+	}
+    
+    if (error) *error = [TBXML errorWithCode:D_TBXML_ELEMENT_NOT_FOUND];
+    
+	return nil;
+}
+
++ (void)iterateElementsForQuery:(NSString *)query fromElement:(TBXMLElement *)anElement withBlock:(TBXMLIterateBlock)iterateBlock {
+    
+    NSArray *components = [query componentsSeparatedByString:@"."];
+    TBXMLElement *currTBXMLElement = anElement;
+    
+    // navigate down
+    for (NSInteger i=0; i < components.count; ++i) {
+        NSString *iTagName = [components objectAtIndex:i];
+        
+        if ([iTagName isEqualToString:@"*"]) {
+            currTBXMLElement = currTBXMLElement->firstChild;
+            
+            // different behavior depending on if this is the end of the query or midstream
+            if (i < (components.count - 1)) {
+                // midstream
+                do {
+                    NSString *restOfQuery = [[components subarrayWithRange:NSMakeRange(i + 1, components.count - i - 1)] componentsJoinedByString:@"."];
+                    [TBXML iterateElementsForQuery:restOfQuery fromElement:currTBXMLElement withBlock:iterateBlock];
+                } while ((currTBXMLElement = currTBXMLElement->nextSibling));
+                
+            }
+        } else {
+            currTBXMLElement = [TBXML childElementNamed:iTagName parentElement:currTBXMLElement];            
+        }
+        
+        if (!currTBXMLElement) {
+            break;
+        }
+    }
+    
+    if (currTBXMLElement) {
+        // enumerate
+        NSString *childTagName = [components lastObject];
+        
+        if ([childTagName isEqualToString:@"*"]) {
+            childTagName = nil;
+        }
+        
+        do {
+            iterateBlock(currTBXMLElement);
+        } while (childTagName ? (currTBXMLElement = [TBXML nextSiblingNamed:childTagName searchFromElement:currTBXMLElement]) : (currTBXMLElement = currTBXMLElement->nextSibling));
+    }
+}
+
++ (void)iterateAttributesOfElement:(TBXMLElement *)anElement withBlock:(TBXMLIterateAttributeBlock)iterateAttributeBlock {
+
+    // Obtain first attribute from element
+    TBXMLAttribute * attribute = anElement->firstAttribute;
+    
+    // if attribute is valid
+    
+    while (attribute) {
+        // Call the iterateAttributeBlock with the attribute, it's name and value
+        iterateAttributeBlock(attribute, [TBXML attributeName:attribute], [TBXML attributeValue:attribute]);
+        
+        // Obtain the next attribute
+        attribute = attribute->next;
+    }
+}
+
+@end
+
+
+// ================================================================================================
+// Private Implementation
+// ================================================================================================
+
+#pragma mark -
+#pragma mark Private implementation
+
+@implementation TBXML (Private)
+
++ (NSString *) errorTextForCode:(int)code {
+    NSString * codeText = @"";
+    
+    switch (code) {
+        case D_TBXML_DATA_NIL:                  codeText = @"Data is nil";                          break;
+        case D_TBXML_DECODE_FAILURE:            codeText = @"Decode failure";                       break;
+        case D_TBXML_MEMORY_ALLOC_FAILURE:      codeText = @"Unable to allocate memory";            break;
+        case D_TBXML_FILE_NOT_FOUND_IN_BUNDLE:  codeText = @"File not found in bundle";             break;
+            
+        case D_TBXML_ELEMENT_IS_NIL:            codeText = @"Element is nil";                       break;
+        case D_TBXML_ELEMENT_NAME_IS_NIL:       codeText = @"Element name is nil";                  break;
+        case D_TBXML_ATTRIBUTE_IS_NIL:          codeText = @"Attribute is nil";                     break;
+        case D_TBXML_ATTRIBUTE_NAME_IS_NIL:     codeText = @"Attribute name is nil";                break;
+        case D_TBXML_ELEMENT_TEXT_IS_NIL:       codeText = @"Element text is nil";                  break;
+        case D_TBXML_PARAM_NAME_IS_NIL:         codeText = @"Parameter name is nil";                break;
+        case D_TBXML_ATTRIBUTE_NOT_FOUND:       codeText = @"Attribute not found";                  break;
+        case D_TBXML_ELEMENT_NOT_FOUND:         codeText = @"Element not found";                    break;
+            
+        default: codeText = @"No Error Description!"; break;
+    }
+    
+    return codeText;
+}
+
++ (NSError *) errorWithCode:(int)code {
+    
+    NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:[TBXML errorTextForCode:code], NSLocalizedDescriptionKey, nil];
+    
+    return [NSError errorWithDomain:D_TBXML_DOMAIN 
+                               code:code 
+                           userInfo:userInfo];
+}
+
++ (NSError *) errorWithCode:(int)code userInfo:(NSMutableDictionary *)someUserInfo {
+    
+    NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:someUserInfo];
+    [userInfo setValue:[TBXML errorTextForCode:code] forKey:NSLocalizedDescriptionKey];
+    
+    return [NSError errorWithDomain:D_TBXML_DOMAIN 
+                               code:code 
+                           userInfo:userInfo];
+}
+
+- (int) allocateBytesOfLength:(long)length error:(NSError **)error {
+    bytesLength = length;
+    
+    NSError *localError = nil;
+    
+    if(!length) {
+        localError = [TBXML errorWithCode:D_TBXML_DATA_NIL];
+    }
+    
+	bytes = malloc(bytesLength+1);
+    
+    if(!bytes) {
+        localError = [TBXML errorWithCode:D_TBXML_MEMORY_ALLOC_FAILURE];
+    }
+    
+    if (error) *error = localError;
+        
+    return localError == nil ? D_TBXML_SUCCESS : [localError code];
+}
+
+- (void) decodeBytes {
+	
+	// -----------------------------------------------------------------------------
+	// Process xml
+	// -----------------------------------------------------------------------------
+	
+	// set elementStart pointer to the start of our xml
+	char * elementStart=bytes;
+	
+	// set parent element to nil
+	TBXMLElement * parentXMLElement = nil;
+	
+	// find next element start
+	while ((elementStart = strstr(elementStart,"<"))) {
+		
+		// detect comment section
+		if (strncmp(elementStart,"<!--",4) == 0) {
+			elementStart = strstr(elementStart,"-->") + 3;
+			continue;
+		}
+
+		// detect cdata section within element text
+		int isCDATA = strncmp(elementStart,"<![CDATA[",9);
+		
+		// if cdata section found, skip data within cdata section and remove cdata tags
+		if (isCDATA==0) {
+			
+			// find end of cdata section
+			char * CDATAEnd = strstr(elementStart,"]]>");
+			
+			// find start of next element skipping any cdata sections within text
+			char * elementEnd = CDATAEnd;
+			
+			// find next open tag
+			elementEnd = strstr(elementEnd,"<");
+			// if open tag is a cdata section
+			while (strncmp(elementEnd,"<![CDATA[",9) == 0) {
+				// find end of cdata section
+				elementEnd = strstr(elementEnd,"]]>");
+				// find next open tag
+				elementEnd = strstr(elementEnd,"<");
+			}
+			
+			// calculate length of cdata content
+			long CDATALength = CDATAEnd-elementStart;
+			
+			// calculate total length of text
+			long textLength = elementEnd-elementStart;
+			
+			// remove begining cdata section tag
+			memcpy(elementStart, elementStart+9, CDATAEnd-elementStart-9);
+
+			// remove ending cdata section tag
+			memcpy(CDATAEnd-9, CDATAEnd+3, textLength-CDATALength-3);
+			
+			// blank out end of text
+			memset(elementStart+textLength-12,' ',12);
+			
+			// set new search start position 
+			elementStart = CDATAEnd-9;
+			continue;
+		}
+		
+		
+		// find element end, skipping any cdata sections within attributes
+		char * elementEnd = elementStart+1;		
+		while ((elementEnd = strpbrk(elementEnd, "<>"))) {
+			if (strncmp(elementEnd,"<![CDATA[",9) == 0) {
+				elementEnd = strstr(elementEnd,"]]>")+3;
+			} else {
+				break;
+			}
+		}
+		
+        if (!elementEnd) break;
+		
+		// null terminate element end
+		if (elementEnd) *elementEnd = 0;
+		
+		// null terminate element start so previous element text doesnt overrun
+		*elementStart = 0;
+		
+		// get element name start
+		char * elementNameStart = elementStart+1;
+		
+		// ignore tags that start with ? or ! unless cdata "<![CDATA"
+		if (*elementNameStart == '?' || (*elementNameStart == '!' && isCDATA != 0)) {
+			elementStart = elementEnd+1;
+			continue;
+		}
+		
+		// ignore attributes/text if this is a closing element
+		if (*elementNameStart == '/') {
+			elementStart = elementEnd+1;
+			if (parentXMLElement) {
+
+				if (parentXMLElement->text) {
+					// trim whitespace from start of text
+					while (isspace(*parentXMLElement->text)) 
+						parentXMLElement->text++;
+					
+					// trim whitespace from end of text
+					char * end = parentXMLElement->text + strlen(parentXMLElement->text)-1;
+					while (end > parentXMLElement->text && isspace(*end)) 
+						*end--=0;
+				}
+				
+				parentXMLElement = parentXMLElement->parentElement;
+				
+				// if parent element has children clear text
+				if (parentXMLElement && parentXMLElement->firstChild)
+					parentXMLElement->text = 0;
+				
+			}
+			continue;
+		}
+		
+		
+		// is this element opening and closing
+		BOOL selfClosingElement = NO;
+		if (*(elementEnd-1) == '/') {
+			selfClosingElement = YES;
+		}
+		
+		
+		// create new xmlElement struct
+		TBXMLElement * xmlElement = [self nextAvailableElement];
+		
+		// set element name
+		xmlElement->name = elementNameStart;
+		
+		// if there is a parent element
+		if (parentXMLElement) {
+			
+			// if this is first child of parent element
+			if (parentXMLElement->currentChild) {
+				// set next child element in list
+				parentXMLElement->currentChild->nextSibling = xmlElement;
+				xmlElement->previousSibling = parentXMLElement->currentChild;
+				
+				parentXMLElement->currentChild = xmlElement;
+				
+				
+			} else {
+				// set first child element
+				parentXMLElement->currentChild = xmlElement;
+				parentXMLElement->firstChild = xmlElement;
+			}
+			
+			xmlElement->parentElement = parentXMLElement;
+		}
+		
+		
+		// in the following xml the ">" is replaced with \0 by elementEnd. 
+		// element may contain no atributes and would return nil while looking for element name end
+		// <tile> 
+		// find end of element name
+		char * elementNameEnd = strpbrk(xmlElement->name," /\n");
+		
+		
+		// if end was found check for attributes
+		if (elementNameEnd) {
+			
+			// null terminate end of elemenet name
+			*elementNameEnd = 0;
+			
+			char * chr = elementNameEnd;
+			char * name = nil;
+			char * value = nil;
+			char * CDATAStart = nil;
+			char * CDATAEnd = nil;
+			TBXMLAttribute * lastXMLAttribute = nil;
+			TBXMLAttribute * xmlAttribute = nil;
+			BOOL singleQuote = NO;
+			
+			int mode = TBXML_ATTRIBUTE_NAME_START;
+			
+			// loop through all characters within element
+			while (chr++ < elementEnd) {
+				
+				switch (mode) {
+					// look for start of attribute name
+					case TBXML_ATTRIBUTE_NAME_START:
+						if (isspace(*chr)) continue;
+						name = chr;
+						mode = TBXML_ATTRIBUTE_NAME_END;
+						break;
+					// look for end of attribute name
+					case TBXML_ATTRIBUTE_NAME_END:
+						if (isspace(*chr) || *chr == '=') {
+							*chr = 0;
+							mode = TBXML_ATTRIBUTE_VALUE_START;
+						}
+						break;
+					// look for start of attribute value
+					case TBXML_ATTRIBUTE_VALUE_START:
+						if (isspace(*chr)) continue;
+						if (*chr == '"' || *chr == '\'') {
+							value = chr+1;
+							mode = TBXML_ATTRIBUTE_VALUE_END;
+							if (*chr == '\'') 
+								singleQuote = YES;
+							else
+								singleQuote = NO;
+						}
+						break;
+					// look for end of attribute value
+					case TBXML_ATTRIBUTE_VALUE_END:
+						if (*chr == '<' && strncmp(chr, "<![CDATA[", 9) == 0) {
+							mode = TBXML_ATTRIBUTE_CDATA_END;
+						}else if ((*chr == '"' && singleQuote == NO) || (*chr == '\'' && singleQuote == YES)) {
+							*chr = 0;
+							
+							// remove cdata section tags
+							while ((CDATAStart = strstr(value, "<![CDATA["))) {
+								
+								// remove begin cdata tag
+								memcpy(CDATAStart, CDATAStart+9, strlen(CDATAStart)-8);
+								
+								// search for end cdata
+								CDATAEnd = strstr(CDATAStart,"]]>");
+								
+								// remove end cdata tag
+								memcpy(CDATAEnd, CDATAEnd+3, strlen(CDATAEnd)-2);
+							}
+							
+							
+							// create new attribute
+							xmlAttribute = [self nextAvailableAttribute];
+							
+							// if this is the first attribute found, set pointer to this attribute on element
+							if (!xmlElement->firstAttribute) xmlElement->firstAttribute = xmlAttribute;
+							// if previous attribute found, link this attribute to previous one
+							if (lastXMLAttribute) lastXMLAttribute->next = xmlAttribute;
+							// set last attribute to this attribute
+							lastXMLAttribute = xmlAttribute;
+
+							// set attribute name & value
+							xmlAttribute->name = name;
+							xmlAttribute->value = value;
+							
+							// clear name and value pointers
+							name = nil;
+							value = nil;
+							
+							// start looking for next attribute
+							mode = TBXML_ATTRIBUTE_NAME_START;
+						}
+						break;
+						// look for end of cdata
+					case TBXML_ATTRIBUTE_CDATA_END:
+						if (*chr == ']') {
+							if (strncmp(chr, "]]>", 3) == 0) {
+								mode = TBXML_ATTRIBUTE_VALUE_END;
+							}
+						}
+						break;						
+					default:
+						break;
+				}
+			}
+		}
+		
+		// if tag is not self closing, set parent to current element
+		if (!selfClosingElement) {
+			// set text on element to element end+1
+			if (*(elementEnd+1) != '>')
+				xmlElement->text = elementEnd+1;
+			
+			parentXMLElement = xmlElement;
+		}
+		
+		// start looking for next element after end of current element
+		elementStart = elementEnd+1;
+	}
+}
+
+// Deallocate used memory
+- (void) dealloc {
+    
+	if (bytes) {
+		free(bytes);
+		bytes = nil;
+	}
+	
+	while (currentElementBuffer) {
+		if (currentElementBuffer->elements)
+			free(currentElementBuffer->elements);
+		
+		if (currentElementBuffer->previous) {
+			currentElementBuffer = currentElementBuffer->previous;
+			free(currentElementBuffer->next);
+		} else {
+			free(currentElementBuffer);
+			currentElementBuffer = 0;
+		}
+	}
+	
+	while (currentAttributeBuffer) {
+		if (currentAttributeBuffer->attributes)
+			free(currentAttributeBuffer->attributes);
+		
+		if (currentAttributeBuffer->previous) {
+			currentAttributeBuffer = currentAttributeBuffer->previous;
+			free(currentAttributeBuffer->next);
+		} else {
+			free(currentAttributeBuffer);
+			currentAttributeBuffer = 0;
+		}
+	}
+}
+
+- (TBXMLElement*) nextAvailableElement {
+	currentElement++;
+	
+	if (!currentElementBuffer) {
+		currentElementBuffer = calloc(1, sizeof(TBXMLElementBuffer));
+		currentElementBuffer->elements = (TBXMLElement*)calloc(1,sizeof(TBXMLElement)*MAX_ELEMENTS);
+		currentElement = 0;
+		rootXMLElement = &currentElementBuffer->elements[currentElement];
+	} else if (currentElement >= MAX_ELEMENTS) {
+		currentElementBuffer->next = calloc(1, sizeof(TBXMLElementBuffer));
+		currentElementBuffer->next->previous = currentElementBuffer;
+		currentElementBuffer = currentElementBuffer->next;
+		currentElementBuffer->elements = (TBXMLElement*)calloc(1,sizeof(TBXMLElement)*MAX_ELEMENTS);
+		currentElement = 0;
+	}
+	
+	return &currentElementBuffer->elements[currentElement];
+}
+
+- (TBXMLAttribute*) nextAvailableAttribute {
+	currentAttribute++;
+	
+	if (!currentAttributeBuffer) {
+		currentAttributeBuffer = calloc(1, sizeof(TBXMLAttributeBuffer));
+		currentAttributeBuffer->attributes = (TBXMLAttribute*)calloc(MAX_ATTRIBUTES,sizeof(TBXMLAttribute));
+		currentAttribute = 0;
+	} else if (currentAttribute >= MAX_ATTRIBUTES) {
+		currentAttributeBuffer->next = calloc(1, sizeof(TBXMLAttributeBuffer));
+		currentAttributeBuffer->next->previous = currentAttributeBuffer;
+		currentAttributeBuffer = currentAttributeBuffer->next;
+		currentAttributeBuffer->attributes = (TBXMLAttribute*)calloc(MAX_ATTRIBUTES,sizeof(TBXMLAttribute));
+		currentAttribute = 0;
+	}
+	
+	return &currentAttributeBuffer->attributes[currentAttribute];
+}
+
+@end

+ 25 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/Channel/TrackChooseView.h

@@ -0,0 +1,25 @@
+//
+//  TrackChooseView.h
+//  StudentDaya
+//
+//  Created by Kyle on 2022/1/21.
+//  Copyright © 2022 DayaMusic. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+typedef void(^ChooseTrackCallback)(NSMutableArray * _Nullable trackNameArray);
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface TrackChooseView : UIView
+
++ (instancetype)shareInstanceWithFullTrackArray:(NSMutableArray *)trackNameArray;
+
+- (void)chooseTrackCallback:(ChooseTrackCallback)callback;
+
+- (void)showInView:(UIView *)displayView;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 142 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/Channel/TrackChooseView.m

@@ -0,0 +1,142 @@
+//
+//  TrackChooseView.m
+//  StudentDaya
+//
+//  Created by Kyle on 2022/1/21.
+//  Copyright © 2022 DayaMusic. All rights reserved.
+//
+
+#import "TrackChooseView.h"
+
+@interface TrackChooseView ()
+
+@property (nonatomic, strong) NSMutableArray *trackNameArray;
+
+@property (nonatomic, strong) NSMutableArray *chooseTrackArray;
+
+@property (nonatomic, strong) NSMutableArray *tempoArray;
+
+@property (nonatomic, strong) UIScrollView *baseScroll;
+
+@property (weak, nonatomic) IBOutlet UIView *contentView;
+
+@property (nonatomic, copy) ChooseTrackCallback callback;
+
+@end
+
+
+@implementation TrackChooseView
+
++ (instancetype)shareInstanceWithFullTrackArray:(NSMutableArray *)trackNameArray {
+    TrackChooseView *view = [[[NSBundle mainBundle] loadNibNamed:@"TrackChooseView" owner:nil options:nil] firstObject];
+    view.trackNameArray = trackNameArray;
+    view.frame = CGRectMake(0, 0, KLandscapeWidth, KLandscapeHeight);
+    view.backgroundColor = HexRGBAlpha(0x000000, 0.5f);
+    [view displayView];
+    return view;
+}
+
+
+- (void)displayView {
+    self.tempoArray = [NSMutableArray array];
+    self.baseScroll = [[UIScrollView alloc] init];
+    [self addSubview:self.baseScroll];
+    [self.baseScroll mas_makeConstraints:^(MASConstraintMaker *make) {
+        make.left.right.bottom.top.mas_equalTo(self.contentView);
+    }];
+    CGFloat width = 300.0f;
+    CGFloat height = KLandscapeHeight - 64 - 50 - 40;
+    CGFloat space = 10;
+    CGFloat buttonHeight = 30;
+    CGFloat buttonWidth = (width - space * 4) / 3.0f;
+    CGFloat maxHeight = 0;
+    for (NSInteger i = 0; i < self.trackNameArray.count; i++) {
+        CGRect frame = CGRectMake(space + (i % 3) * (buttonWidth + space), (i / 3) * (buttonHeight + space), buttonWidth, buttonHeight);
+        UIButton *button =  [self createButtonWithName:self.trackNameArray[i] frame:frame tag:i+1000];
+        if (i == 0) {
+            button.selected = YES;
+            button.layer.borderColor = HexRGB(0x01c1b5).CGColor;
+        }
+        [self.baseScroll addSubview:button];
+        maxHeight = (i / 3) * (buttonHeight + space) + buttonHeight + space;
+    }
+    if (height < maxHeight) {
+        height = maxHeight;
+    }
+    self.baseScroll.contentSize = CGSizeMake(width, height);
+    // 默认选择第一个
+    [self.chooseTrackArray addObject:self.trackNameArray[0]];
+    [self.tempoArray addObject:self.trackNameArray[0]];
+}
+
+
+- (UIButton *)createButtonWithName:(NSString *)name frame:(CGRect)frame tag:(NSInteger)tag {
+    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
+    button.frame = frame;
+    button.tag = 1000 + tag;
+    [button setTitle:name forState:UIControlStateNormal];
+    [button addTarget:self action:@selector(buttonClickAction:) forControlEvents:UIControlEventTouchUpInside];
+    button.layer.cornerRadius = 5.0f;
+    button.layer.borderColor = HexRGB(0xf5f5f5).CGColor;
+    button.layer.borderWidth = 1.0f;
+    [button.titleLabel setFont:[UIFont systemFontOfSize:12.0f]];
+    [button setTitleColor:HexRGB(0x333333) forState:UIControlStateNormal];
+    [button setTitleColor:HexRGB(0x01c1b5) forState:UIControlStateSelected];
+    return button;
+}
+
+- (void)buttonClickAction:(UIButton *)sender {
+    sender.selected = !sender.selected;
+    BOOL isSelected = sender.isSelected;
+    NSString *trackName = sender.titleLabel.text;
+    if (isSelected) {
+        sender.layer.borderColor = HexRGB(0x01c1b5).CGColor;
+        [self chooseTrackWithName:trackName];
+    }
+    else {
+        sender.layer.borderColor = HexRGB(0xf5f5f5).CGColor;
+        [self removeChooseTrackWithName:trackName];
+    }
+}
+- (IBAction)sureAction:(id)sender {
+    if (self.callback) {
+        self.chooseTrackArray = [NSMutableArray arrayWithArray:self.tempoArray];
+        self.callback(self.chooseTrackArray);
+    }
+    [self removeFromSuperview];
+}
+
+- (IBAction)cancleAction:(id)sender {
+    [self hideView];
+}
+
+- (void)hideView {
+    [self removeFromSuperview];
+}
+- (void)chooseTrackWithName:(NSString *)trackName {
+    [self.tempoArray addObject:trackName];
+}
+
+- (void)removeChooseTrackWithName:(NSString *)trackName {
+    [self.tempoArray removeObject:trackName];
+}
+- (void)showInView:(UIView *)displayView {
+    [displayView addSubview:self];
+}
+
+
+- (void)chooseTrackCallback:(ChooseTrackCallback)callback {
+    if (callback) {
+        self.callback = callback;
+    }
+}
+
+/*
+// Only override drawRect: if you perform custom drawing.
+// An empty implementation adversely affects performance during animation.
+- (void)drawRect:(CGRect)rect {
+    // Drawing code
+}
+*/
+
+@end

+ 113 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/Channel/TrackChooseView.xib

@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="19529" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
+    <device id="retina6_1" orientation="portrait" appearance="light"/>
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19519"/>
+        <capability name="System colors in document resources" minToolsVersion="11.0"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <objects>
+        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
+        <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
+        <view contentMode="scaleToFill" id="iN0-l3-epB" customClass="TrackChooseView">
+            <rect key="frame" x="0.0" y="0.0" width="750" height="375"/>
+            <autoresizingMask key="autoresizingMask"/>
+            <subviews>
+                <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="eNR-CP-hMc">
+                    <rect key="frame" x="215" y="32" width="320" height="311"/>
+                    <subviews>
+                        <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="image_alertTips" translatesAutoresizingMaskIntoConstraints="NO" id="GeC-WM-XZa">
+                            <rect key="frame" x="106" y="-4" width="108" height="31"/>
+                            <constraints>
+                                <constraint firstAttribute="height" constant="31" id="Vqd-ny-i28"/>
+                                <constraint firstAttribute="width" constant="108" id="fr5-5C-Fjc"/>
+                            </constraints>
+                        </imageView>
+                        <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="选择乐器" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="jDn-Lm-9Nb">
+                            <rect key="frame" x="127" y="1.5" width="66" height="20"/>
+                            <fontDescription key="fontDescription" type="system" pointSize="16"/>
+                            <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                            <nil key="highlightedColor"/>
+                        </label>
+                        <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="HLa-gh-ykH">
+                            <rect key="frame" x="10" y="40" width="300" height="221"/>
+                            <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+                        </view>
+                        <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="TuA-cX-WJb">
+                            <rect key="frame" x="110" y="271" width="100" height="30"/>
+                            <color key="backgroundColor" red="0.0039215686269999999" green="0.75686274509999996" blue="0.70980392160000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                            <constraints>
+                                <constraint firstAttribute="width" constant="100" id="04l-xF-B07"/>
+                                <constraint firstAttribute="height" constant="30" id="Fpt-41-dXV"/>
+                            </constraints>
+                            <fontDescription key="fontDescription" type="system" pointSize="16"/>
+                            <inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
+                            <state key="normal" title="确定"/>
+                            <userDefinedRuntimeAttributes>
+                                <userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
+                                    <real key="value" value="15"/>
+                                </userDefinedRuntimeAttribute>
+                            </userDefinedRuntimeAttributes>
+                            <connections>
+                                <action selector="sureAction:" destination="iN0-l3-epB" eventType="touchUpInside" id="Jax-ON-cOH"/>
+                            </connections>
+                        </button>
+                    </subviews>
+                    <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+                    <constraints>
+                        <constraint firstItem="GeC-WM-XZa" firstAttribute="centerX" secondItem="eNR-CP-hMc" secondAttribute="centerX" id="Ac7-xw-CrE"/>
+                        <constraint firstAttribute="width" constant="320" id="FfP-sW-0Os"/>
+                        <constraint firstAttribute="bottom" secondItem="TuA-cX-WJb" secondAttribute="bottom" constant="10" id="MlH-oo-9cl"/>
+                        <constraint firstItem="HLa-gh-ykH" firstAttribute="top" secondItem="eNR-CP-hMc" secondAttribute="top" constant="40" id="ObX-yh-RXp"/>
+                        <constraint firstItem="jDn-Lm-9Nb" firstAttribute="centerX" secondItem="GeC-WM-XZa" secondAttribute="centerX" id="Qqc-ww-Ctv"/>
+                        <constraint firstItem="TuA-cX-WJb" firstAttribute="top" secondItem="HLa-gh-ykH" secondAttribute="bottom" constant="10" id="UfX-Vl-d1X"/>
+                        <constraint firstAttribute="trailing" secondItem="HLa-gh-ykH" secondAttribute="trailing" constant="10" id="YLK-oy-QnV"/>
+                        <constraint firstItem="jDn-Lm-9Nb" firstAttribute="centerY" secondItem="GeC-WM-XZa" secondAttribute="centerY" id="kEM-Ki-Iwi"/>
+                        <constraint firstItem="GeC-WM-XZa" firstAttribute="top" secondItem="eNR-CP-hMc" secondAttribute="top" constant="-4" id="kas-ox-JDL"/>
+                        <constraint firstItem="HLa-gh-ykH" firstAttribute="leading" secondItem="eNR-CP-hMc" secondAttribute="leading" constant="10" id="oob-L8-Acp"/>
+                        <constraint firstItem="TuA-cX-WJb" firstAttribute="centerX" secondItem="eNR-CP-hMc" secondAttribute="centerX" id="vjk-rT-eyj"/>
+                    </constraints>
+                    <userDefinedRuntimeAttributes>
+                        <userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
+                            <real key="value" value="12"/>
+                        </userDefinedRuntimeAttribute>
+                    </userDefinedRuntimeAttributes>
+                </view>
+                <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="91q-38-ktZ">
+                    <rect key="frame" x="535" y="10" width="44" height="44"/>
+                    <constraints>
+                        <constraint firstAttribute="height" constant="44" id="cnq-7T-YJl"/>
+                        <constraint firstAttribute="width" constant="44" id="d4t-p7-m3i"/>
+                    </constraints>
+                    <state key="normal" image="cloud_cancle"/>
+                    <connections>
+                        <action selector="cancleAction:" destination="iN0-l3-epB" eventType="touchUpInside" id="ar1-Sk-D9Q"/>
+                    </connections>
+                </button>
+            </subviews>
+            <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+            <constraints>
+                <constraint firstItem="eNR-CP-hMc" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" constant="32" id="NJh-Th-HQW"/>
+                <constraint firstItem="91q-38-ktZ" firstAttribute="centerY" secondItem="eNR-CP-hMc" secondAttribute="top" id="Tqw-hy-1Uc"/>
+                <constraint firstAttribute="bottom" secondItem="eNR-CP-hMc" secondAttribute="bottom" constant="32" id="UxN-d4-lNH"/>
+                <constraint firstItem="91q-38-ktZ" firstAttribute="leading" secondItem="eNR-CP-hMc" secondAttribute="trailing" id="f9i-84-h7Y"/>
+                <constraint firstItem="eNR-CP-hMc" firstAttribute="centerX" secondItem="iN0-l3-epB" secondAttribute="centerX" id="lVN-0h-cpf"/>
+            </constraints>
+            <nil key="simulatedTopBarMetrics"/>
+            <nil key="simulatedBottomBarMetrics"/>
+            <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
+            <connections>
+                <outlet property="contentView" destination="HLa-gh-ykH" id="1ag-Ko-i22"/>
+            </connections>
+            <point key="canvasLocation" x="195.6521739130435" y="24.107142857142858"/>
+        </view>
+    </objects>
+    <resources>
+        <image name="cloud_cancle" width="26" height="26"/>
+        <image name="image_alertTips" width="108" height="31"/>
+        <systemColor name="systemBackgroundColor">
+            <color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+        </systemColor>
+    </resources>
+</document>

+ 23 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/Help/CloudFeedbackView.h

@@ -0,0 +1,23 @@
+//
+//  CloudFeedbackView.h
+//  StudentDaya
+//
+//  Created by Kyle on 2021/12/15.
+//  Copyright © 2021 DayaMusic. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+typedef void(^CloudFeedbackCallback)(NSString *content, NSString *type);
+
+@interface CloudFeedbackView : UIView
+
++ (instancetype)shareInstance;
+
+- (void)submitCallback:(CloudFeedbackCallback)callback;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 126 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/Help/CloudFeedbackView.m

@@ -0,0 +1,126 @@
+//
+//  CloudFeedbackView.m
+//  StudentDaya
+//
+//  Created by Kyle on 2021/12/15.
+//  Copyright © 2021 DayaMusic. All rights reserved.
+//
+
+#import "CloudFeedbackView.h"
+
+@interface CloudFeedbackView ()<UITextViewDelegate>
+@property (weak, nonatomic) IBOutlet UILabel *tipsLabel;
+
+@property (weak, nonatomic) IBOutlet UILabel *countLabel;
+
+@property (weak, nonatomic) IBOutlet UITextView *textView;
+
+@property (nonatomic, copy) CloudFeedbackCallback callback;
+
+@property (nonatomic, strong) NSString *typeString;
+
+@property (nonatomic, assign) NSInteger lastChooseIndex;
+
+@end
+
+
+
+@implementation CloudFeedbackView
+
+- (void)awakeFromNib {
+    [super awakeFromNib];
+    self.textView.delegate = self;
+}
+
++ (instancetype)shareInstance {
+    CloudFeedbackView *view = [[[NSBundle mainBundle] loadNibNamed:@"CloudFeedbackView" owner:nil options:nil] firstObject];
+    return view;
+}
+
+- (void)submitCallback:(CloudFeedbackCallback)callback {
+    if (callback) {
+        self.callback = callback;
+    }
+}
+
+- (IBAction)TypeButonAction:(UIButton *)sender {
+    [sender setBackgroundColor:HexRGB(0xe2fff9)];
+    sender.layer.borderColor = HexRGB(0x01c1b5).CGColor;
+    self.typeString = sender.titleLabel.text;
+    NSInteger index = sender.tag;
+    
+    if (index == self.lastChooseIndex || self.lastChooseIndex == 0) {
+        self.lastChooseIndex = index;
+        return;
+    }
+    else {
+        UIButton *preButton = (UIButton *)[self viewWithTag:self.lastChooseIndex];
+        [preButton setBackgroundColor:HexRGB(0x999999)];
+        preButton.layer.borderColor = HexRGB(0xf8f8f8).CGColor;
+        self.lastChooseIndex = index;
+    }
+}
+
+- (IBAction)sureAction:(id)sender {
+    [self endEditing:YES];
+    if (self.callback) {
+        if ([NSString isEmptyString:self.typeString]) {
+            [self MBPShow:@"请选择问题类型"];
+            return;
+        }
+        else if ([NSString isEmptyString:self.textView.text]) {
+            [self MBPShow:@"请描述您遇到的问题"];
+            return;
+        }
+        self.callback(self.textView.text, self.typeString);
+    }
+}
+
+#pragma mark   ---- delegate
+
+- (void)textViewDidBeginEditing:(UITextView *)textView {
+    self.tipsLabel.hidden = YES;
+}
+- (void)textViewDidEndEditing:(UITextView *)textView {
+    if ([NSString isEmptyString:textView.text]) {
+        self.tipsLabel.hidden = NO;
+    }
+}
+
+- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
+    if ([text isEqualToString:@"\n"]) {
+        [self endEditing:YES];
+        return YES;
+    }
+    if ([text isEqualToString:@""]) {
+        return YES;
+    }
+    // 输入控制
+    NSString *newString = [textView.text stringByReplacingCharactersInRange:range withString:text];
+    if (newString.length > 200) {
+        return NO;
+    }
+    self.countLabel.text = [NSString stringWithFormat:@"%zd/200", newString.length];
+    return YES;
+}
+
+
+- (BOOL)textViewShouldEndEditing:(UITextView *)textView {
+    [self endEditing:YES];
+    return YES;
+}
+
+- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
+    [self endEditing:YES];
+}
+
+
+/*
+// Only override drawRect: if you perform custom drawing.
+// An empty implementation adversely affects performance during animation.
+- (void)drawRect:(CGRect)rect {
+    // Drawing code
+}
+*/
+
+@end

+ 325 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/Help/CloudFeedbackView.xib

@@ -0,0 +1,325 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
+    <device id="retina6_1" orientation="portrait" appearance="light"/>
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/>
+        <capability name="System colors in document resources" minToolsVersion="11.0"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <objects>
+        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
+        <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
+        <view contentMode="scaleToFill" id="iN0-l3-epB" customClass="CloudFeedbackView">
+            <rect key="frame" x="0.0" y="0.0" width="1162" height="515"/>
+            <autoresizingMask key="autoresizingMask"/>
+            <subviews>
+                <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="0Jt-Nx-mxp">
+                    <rect key="frame" x="421" y="97.5" width="320" height="320"/>
+                    <subviews>
+                        <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="意见反馈" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="39T-t6-0Wv">
+                            <rect key="frame" x="127.5" y="12" width="65.5" height="22"/>
+                            <constraints>
+                                <constraint firstAttribute="height" constant="22" id="Ma2-5f-LS5"/>
+                            </constraints>
+                            <fontDescription key="fontDescription" type="system" weight="medium" pointSize="16"/>
+                            <color key="textColor" red="0.10196078431372549" green="0.10196078431372549" blue="0.10196078431372549" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                            <nil key="highlightedColor"/>
+                        </label>
+                        <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Gq5-UG-UPH">
+                            <rect key="frame" x="0.0" y="45" width="320" height="1"/>
+                            <color key="backgroundColor" red="0.94117647058823528" green="0.94117647058823528" blue="0.94117647058823528" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                            <constraints>
+                                <constraint firstAttribute="height" constant="1" id="bXb-3D-Mpu"/>
+                            </constraints>
+                        </view>
+                        <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="请选择问题类型" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="TEQ-Mu-woj">
+                            <rect key="frame" x="18" y="70" width="100" height="20"/>
+                            <constraints>
+                                <constraint firstAttribute="height" constant="20" id="jjb-5v-wg7"/>
+                            </constraints>
+                            <fontDescription key="fontDescription" type="system" pointSize="14"/>
+                            <color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                            <nil key="highlightedColor"/>
+                        </label>
+                        <button opaque="NO" tag="1001" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="7U7-1u-Czi">
+                            <rect key="frame" x="18" y="98" width="89.5" height="27"/>
+                            <color key="backgroundColor" red="0.97254901960784312" green="0.97254901960784312" blue="0.97254901960784312" alpha="1" colorSpace="calibratedRGB"/>
+                            <constraints>
+                                <constraint firstAttribute="height" constant="27" id="mkM-2a-PEa"/>
+                            </constraints>
+                            <fontDescription key="fontDescription" type="system" pointSize="12"/>
+                            <state key="normal" title="识别不准">
+                                <color key="titleColor" red="0.59999999999999998" green="0.59999999999999998" blue="0.59999999999999998" alpha="1" colorSpace="calibratedRGB"/>
+                            </state>
+                            <userDefinedRuntimeAttributes>
+                                <userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
+                                    <real key="value" value="6"/>
+                                </userDefinedRuntimeAttribute>
+                                <userDefinedRuntimeAttribute type="number" keyPath="borderWidth">
+                                    <real key="value" value="1"/>
+                                </userDefinedRuntimeAttribute>
+                                <userDefinedRuntimeAttribute type="color" keyPath="borderColor">
+                                    <color key="value" red="0.97254901960784312" green="0.97254901960784312" blue="0.97254901960784312" alpha="1" colorSpace="calibratedRGB"/>
+                                </userDefinedRuntimeAttribute>
+                            </userDefinedRuntimeAttributes>
+                            <connections>
+                                <action selector="TypeButonAction:" destination="iN0-l3-epB" eventType="touchUpInside" id="zAI-Ki-2V5"/>
+                            </connections>
+                        </button>
+                        <button opaque="NO" tag="1002" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="J1s-eN-oKW">
+                            <rect key="frame" x="115.5" y="98" width="89" height="27"/>
+                            <color key="backgroundColor" red="0.97254901959999995" green="0.97254901959999995" blue="0.97254901959999995" alpha="1" colorSpace="calibratedRGB"/>
+                            <fontDescription key="fontDescription" type="system" pointSize="12"/>
+                            <state key="normal" title="无法评测">
+                                <color key="titleColor" red="0.59999999999999998" green="0.59999999999999998" blue="0.59999999999999998" alpha="1" colorSpace="calibratedRGB"/>
+                            </state>
+                            <userDefinedRuntimeAttributes>
+                                <userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
+                                    <real key="value" value="6"/>
+                                </userDefinedRuntimeAttribute>
+                                <userDefinedRuntimeAttribute type="number" keyPath="borderWidth">
+                                    <real key="value" value="1"/>
+                                </userDefinedRuntimeAttribute>
+                                <userDefinedRuntimeAttribute type="color" keyPath="borderColor">
+                                    <color key="value" red="0.97254901960784312" green="0.97254901960784312" blue="0.97254901960784312" alpha="1" colorSpace="calibratedRGB"/>
+                                </userDefinedRuntimeAttribute>
+                            </userDefinedRuntimeAttributes>
+                            <connections>
+                                <action selector="TypeButonAction:" destination="iN0-l3-epB" eventType="touchUpInside" id="U77-aX-4ha"/>
+                            </connections>
+                        </button>
+                        <button opaque="NO" tag="1003" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="x52-Ag-qNB">
+                            <rect key="frame" x="212.5" y="98" width="89.5" height="27"/>
+                            <color key="backgroundColor" red="0.97254901959999995" green="0.97254901959999995" blue="0.97254901959999995" alpha="1" colorSpace="calibratedRGB"/>
+                            <fontDescription key="fontDescription" type="system" pointSize="12"/>
+                            <state key="normal" title="不出评测结果">
+                                <color key="titleColor" red="0.59999999999999998" green="0.59999999999999998" blue="0.59999999999999998" alpha="1" colorSpace="calibratedRGB"/>
+                            </state>
+                            <userDefinedRuntimeAttributes>
+                                <userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
+                                    <real key="value" value="6"/>
+                                </userDefinedRuntimeAttribute>
+                                <userDefinedRuntimeAttribute type="number" keyPath="borderWidth">
+                                    <real key="value" value="1"/>
+                                </userDefinedRuntimeAttribute>
+                                <userDefinedRuntimeAttribute type="color" keyPath="borderColor">
+                                    <color key="value" red="0.97254901960784312" green="0.97254901960784312" blue="0.97254901960784312" alpha="1" colorSpace="calibratedRGB"/>
+                                </userDefinedRuntimeAttribute>
+                            </userDefinedRuntimeAttributes>
+                            <connections>
+                                <action selector="TypeButonAction:" destination="iN0-l3-epB" eventType="touchUpInside" id="pQC-pc-EWq"/>
+                            </connections>
+                        </button>
+                        <button opaque="NO" tag="1004" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="1Mw-7R-dLz">
+                            <rect key="frame" x="18" y="134" width="89.5" height="27"/>
+                            <color key="backgroundColor" red="0.97254901959999995" green="0.97254901959999995" blue="0.97254901959999995" alpha="1" colorSpace="calibratedRGB"/>
+                            <constraints>
+                                <constraint firstAttribute="height" constant="27" id="db0-C7-Pdy"/>
+                            </constraints>
+                            <fontDescription key="fontDescription" type="system" pointSize="12"/>
+                            <state key="normal" title="曲谱不一致">
+                                <color key="titleColor" red="0.59999999999999998" green="0.59999999999999998" blue="0.59999999999999998" alpha="1" colorSpace="calibratedRGB"/>
+                            </state>
+                            <userDefinedRuntimeAttributes>
+                                <userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
+                                    <real key="value" value="6"/>
+                                </userDefinedRuntimeAttribute>
+                                <userDefinedRuntimeAttribute type="number" keyPath="borderWidth">
+                                    <real key="value" value="1"/>
+                                </userDefinedRuntimeAttribute>
+                                <userDefinedRuntimeAttribute type="color" keyPath="borderColor">
+                                    <color key="value" red="0.97254901960784312" green="0.97254901960784312" blue="0.97254901960784312" alpha="1" colorSpace="calibratedRGB"/>
+                                </userDefinedRuntimeAttribute>
+                            </userDefinedRuntimeAttributes>
+                            <connections>
+                                <action selector="TypeButonAction:" destination="iN0-l3-epB" eventType="touchUpInside" id="u1y-qp-4qe"/>
+                            </connections>
+                        </button>
+                        <button opaque="NO" tag="1005" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="9DQ-zy-Jq5">
+                            <rect key="frame" x="115.5" y="134" width="89" height="27"/>
+                            <color key="backgroundColor" red="0.97254901959999995" green="0.97254901959999995" blue="0.97254901959999995" alpha="1" colorSpace="calibratedRGB"/>
+                            <fontDescription key="fontDescription" type="system" pointSize="12"/>
+                            <state key="normal" title="指法错误">
+                                <color key="titleColor" red="0.59999999999999998" green="0.59999999999999998" blue="0.59999999999999998" alpha="1" colorSpace="calibratedRGB"/>
+                            </state>
+                            <userDefinedRuntimeAttributes>
+                                <userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
+                                    <real key="value" value="6"/>
+                                </userDefinedRuntimeAttribute>
+                                <userDefinedRuntimeAttribute type="number" keyPath="borderWidth">
+                                    <real key="value" value="1"/>
+                                </userDefinedRuntimeAttribute>
+                                <userDefinedRuntimeAttribute type="color" keyPath="borderColor">
+                                    <color key="value" red="0.97254901960784312" green="0.97254901960784312" blue="0.97254901960784312" alpha="1" colorSpace="calibratedRGB"/>
+                                </userDefinedRuntimeAttribute>
+                            </userDefinedRuntimeAttributes>
+                            <connections>
+                                <action selector="TypeButonAction:" destination="iN0-l3-epB" eventType="touchUpInside" id="fMv-k4-1rs"/>
+                            </connections>
+                        </button>
+                        <button opaque="NO" tag="1006" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="JzE-X8-lLe">
+                            <rect key="frame" x="212.5" y="134" width="89.5" height="27"/>
+                            <color key="backgroundColor" red="0.97254901959999995" green="0.97254901959999995" blue="0.97254901959999995" alpha="1" colorSpace="calibratedRGB"/>
+                            <fontDescription key="fontDescription" type="system" pointSize="12"/>
+                            <state key="normal" title="其他">
+                                <color key="titleColor" red="0.59999999999999998" green="0.59999999999999998" blue="0.59999999999999998" alpha="1" colorSpace="calibratedRGB"/>
+                            </state>
+                            <userDefinedRuntimeAttributes>
+                                <userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
+                                    <real key="value" value="6"/>
+                                </userDefinedRuntimeAttribute>
+                                <userDefinedRuntimeAttribute type="number" keyPath="borderWidth">
+                                    <real key="value" value="1"/>
+                                </userDefinedRuntimeAttribute>
+                                <userDefinedRuntimeAttribute type="color" keyPath="borderColor">
+                                    <color key="value" red="0.97254901960784312" green="0.97254901960784312" blue="0.97254901960784312" alpha="1" colorSpace="calibratedRGB"/>
+                                </userDefinedRuntimeAttribute>
+                            </userDefinedRuntimeAttributes>
+                            <connections>
+                                <action selector="TypeButonAction:" destination="iN0-l3-epB" eventType="touchUpInside" id="qB6-4P-wfV"/>
+                            </connections>
+                        </button>
+                        <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="fDw-RL-YZQ">
+                            <rect key="frame" x="20" y="171" width="284" height="71"/>
+                            <subviews>
+                                <textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="l1z-4Y-3eT">
+                                    <rect key="frame" x="12" y="10" width="260" height="51"/>
+                                    <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                                    <color key="textColor" systemColor="labelColor"/>
+                                    <fontDescription key="fontDescription" type="system" pointSize="14"/>
+                                    <textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
+                                </textView>
+                                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="0/200" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="9Jw-La-2Hn">
+                                    <rect key="frame" x="244" y="47" width="34" height="15"/>
+                                    <fontDescription key="fontDescription" type="system" pointSize="12"/>
+                                    <color key="textColor" red="0.80000000000000004" green="0.80000000000000004" blue="0.80000000000000004" alpha="1" colorSpace="calibratedRGB"/>
+                                    <nil key="highlightedColor"/>
+                                </label>
+                                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="请详细描述您遇到的问题,以便我们尽快为您解决!" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="nvM-32-Z6W">
+                                    <rect key="frame" x="9" y="15" width="243" height="29"/>
+                                    <fontDescription key="fontDescription" type="system" pointSize="12"/>
+                                    <color key="textColor" red="0.50196078431372548" green="0.50196078431372548" blue="0.50196078431372548" alpha="1" colorSpace="calibratedRGB"/>
+                                    <nil key="highlightedColor"/>
+                                </label>
+                            </subviews>
+                            <color key="backgroundColor" red="0.97254901959999995" green="0.97254901959999995" blue="0.97254901959999995" alpha="1" colorSpace="calibratedRGB"/>
+                            <constraints>
+                                <constraint firstAttribute="trailing" secondItem="l1z-4Y-3eT" secondAttribute="trailing" constant="12" id="DrW-kV-bFE"/>
+                                <constraint firstItem="l1z-4Y-3eT" firstAttribute="top" secondItem="fDw-RL-YZQ" secondAttribute="top" constant="10" id="KKe-Sd-KsV"/>
+                                <constraint firstItem="nvM-32-Z6W" firstAttribute="top" secondItem="fDw-RL-YZQ" secondAttribute="top" constant="15" id="WnD-Kz-gA6"/>
+                                <constraint firstAttribute="bottom" secondItem="9Jw-La-2Hn" secondAttribute="bottom" constant="9" id="Zz7-nb-trk"/>
+                                <constraint firstItem="nvM-32-Z6W" firstAttribute="leading" secondItem="fDw-RL-YZQ" secondAttribute="leading" constant="9" id="aIL-ME-Sir"/>
+                                <constraint firstItem="l1z-4Y-3eT" firstAttribute="leading" secondItem="fDw-RL-YZQ" secondAttribute="leading" constant="12" id="aX5-em-K7L"/>
+                                <constraint firstAttribute="bottom" secondItem="l1z-4Y-3eT" secondAttribute="bottom" constant="10" id="cRb-ik-WSo"/>
+                                <constraint firstAttribute="height" constant="71" id="kLV-hO-8zU"/>
+                                <constraint firstAttribute="trailing" secondItem="nvM-32-Z6W" secondAttribute="trailing" constant="32" id="lNb-ym-wwt"/>
+                                <constraint firstAttribute="trailing" secondItem="9Jw-La-2Hn" secondAttribute="trailing" constant="6" id="y7h-9W-aTe"/>
+                            </constraints>
+                            <userDefinedRuntimeAttributes>
+                                <userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
+                                    <real key="value" value="6"/>
+                                </userDefinedRuntimeAttribute>
+                            </userDefinedRuntimeAttributes>
+                        </view>
+                        <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="d27-7V-Wg8">
+                            <rect key="frame" x="65" y="274" width="190" height="34"/>
+                            <color key="backgroundColor" red="0.0039215686269999999" green="0.75686274509999996" blue="0.70980392160000005" alpha="1" colorSpace="calibratedRGB"/>
+                            <constraints>
+                                <constraint firstAttribute="height" constant="34" id="N3v-Xl-2MW"/>
+                                <constraint firstAttribute="width" constant="190" id="uxK-Vz-uCB"/>
+                            </constraints>
+                            <fontDescription key="fontDescription" type="system" weight="medium" pointSize="14"/>
+                            <state key="normal" title="提交反馈"/>
+                            <userDefinedRuntimeAttributes>
+                                <userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
+                                    <real key="value" value="17"/>
+                                </userDefinedRuntimeAttribute>
+                            </userDefinedRuntimeAttributes>
+                            <connections>
+                                <action selector="sureAction:" destination="iN0-l3-epB" eventType="touchUpInside" id="jAn-FI-sqc"/>
+                            </connections>
+                        </button>
+                    </subviews>
+                    <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+                    <constraints>
+                        <constraint firstItem="9DQ-zy-Jq5" firstAttribute="leading" secondItem="1Mw-7R-dLz" secondAttribute="trailing" constant="8" id="06O-4g-wue"/>
+                        <constraint firstItem="1Mw-7R-dLz" firstAttribute="leading" secondItem="0Jt-Nx-mxp" secondAttribute="leading" constant="18" id="2WH-8E-XvK"/>
+                        <constraint firstItem="J1s-eN-oKW" firstAttribute="width" secondItem="7U7-1u-Czi" secondAttribute="width" id="32b-GC-Ka1"/>
+                        <constraint firstItem="J1s-eN-oKW" firstAttribute="height" secondItem="7U7-1u-Czi" secondAttribute="height" id="3Pw-Ri-nap"/>
+                        <constraint firstItem="7U7-1u-Czi" firstAttribute="top" secondItem="TEQ-Mu-woj" secondAttribute="bottom" constant="8" id="3R2-EC-nqd"/>
+                        <constraint firstItem="JzE-X8-lLe" firstAttribute="centerY" secondItem="1Mw-7R-dLz" secondAttribute="centerY" id="4Il-Nw-NoN"/>
+                        <constraint firstItem="d27-7V-Wg8" firstAttribute="centerX" secondItem="0Jt-Nx-mxp" secondAttribute="centerX" id="5FM-g1-YxC"/>
+                        <constraint firstItem="39T-t6-0Wv" firstAttribute="centerX" secondItem="0Jt-Nx-mxp" secondAttribute="centerX" id="5e2-4O-Fsp"/>
+                        <constraint firstItem="x52-Ag-qNB" firstAttribute="centerY" secondItem="7U7-1u-Czi" secondAttribute="centerY" id="5lJ-KP-9I0"/>
+                        <constraint firstAttribute="height" constant="320" id="76Q-k0-SSd"/>
+                        <constraint firstItem="7U7-1u-Czi" firstAttribute="leading" secondItem="0Jt-Nx-mxp" secondAttribute="leading" constant="18" id="7c9-x7-KoI"/>
+                        <constraint firstAttribute="trailing" secondItem="fDw-RL-YZQ" secondAttribute="trailing" constant="16" id="HP4-kF-sOC"/>
+                        <constraint firstAttribute="width" constant="320" id="LP9-VX-MX3"/>
+                        <constraint firstAttribute="trailing" secondItem="JzE-X8-lLe" secondAttribute="trailing" constant="18" id="Nz2-Yb-Rlm"/>
+                        <constraint firstItem="Gq5-UG-UPH" firstAttribute="top" secondItem="0Jt-Nx-mxp" secondAttribute="top" constant="45" id="UXl-Ug-AeJ"/>
+                        <constraint firstItem="J1s-eN-oKW" firstAttribute="leading" secondItem="7U7-1u-Czi" secondAttribute="trailing" constant="8" id="W0q-B2-4na"/>
+                        <constraint firstItem="JzE-X8-lLe" firstAttribute="width" secondItem="1Mw-7R-dLz" secondAttribute="width" id="WKN-Wm-9Yd"/>
+                        <constraint firstItem="x52-Ag-qNB" firstAttribute="leading" secondItem="J1s-eN-oKW" secondAttribute="trailing" constant="8" id="aBW-dd-CaO"/>
+                        <constraint firstAttribute="trailing" secondItem="Gq5-UG-UPH" secondAttribute="trailing" id="c0O-mK-lEW"/>
+                        <constraint firstItem="fDw-RL-YZQ" firstAttribute="leading" secondItem="0Jt-Nx-mxp" secondAttribute="leading" constant="20" id="d4z-sv-jem"/>
+                        <constraint firstItem="x52-Ag-qNB" firstAttribute="height" secondItem="7U7-1u-Czi" secondAttribute="height" id="ebh-5Z-mfM"/>
+                        <constraint firstItem="JzE-X8-lLe" firstAttribute="height" secondItem="1Mw-7R-dLz" secondAttribute="height" id="fn9-T1-lqv"/>
+                        <constraint firstItem="Gq5-UG-UPH" firstAttribute="leading" secondItem="0Jt-Nx-mxp" secondAttribute="leading" id="iGf-AP-6wJ"/>
+                        <constraint firstItem="9DQ-zy-Jq5" firstAttribute="height" secondItem="1Mw-7R-dLz" secondAttribute="height" id="j2F-03-fR7"/>
+                        <constraint firstItem="fDw-RL-YZQ" firstAttribute="top" secondItem="1Mw-7R-dLz" secondAttribute="bottom" constant="10" id="kke-Er-EGE"/>
+                        <constraint firstItem="TEQ-Mu-woj" firstAttribute="top" secondItem="Gq5-UG-UPH" secondAttribute="bottom" constant="24" id="l9h-eX-tM7"/>
+                        <constraint firstItem="TEQ-Mu-woj" firstAttribute="leading" secondItem="0Jt-Nx-mxp" secondAttribute="leading" constant="18" id="mbH-Py-0kV"/>
+                        <constraint firstAttribute="bottom" secondItem="d27-7V-Wg8" secondAttribute="bottom" constant="12" id="meP-fV-07D"/>
+                        <constraint firstItem="39T-t6-0Wv" firstAttribute="top" secondItem="0Jt-Nx-mxp" secondAttribute="top" constant="12" id="n3K-8r-3La"/>
+                        <constraint firstItem="9DQ-zy-Jq5" firstAttribute="centerY" secondItem="1Mw-7R-dLz" secondAttribute="centerY" id="nLM-s2-wmR"/>
+                        <constraint firstAttribute="trailing" secondItem="x52-Ag-qNB" secondAttribute="trailing" constant="18" id="ni0-Br-dtN"/>
+                        <constraint firstItem="x52-Ag-qNB" firstAttribute="width" secondItem="7U7-1u-Czi" secondAttribute="width" id="oIR-2W-yI8"/>
+                        <constraint firstItem="JzE-X8-lLe" firstAttribute="leading" secondItem="9DQ-zy-Jq5" secondAttribute="trailing" constant="8" id="rUr-IM-rz0"/>
+                        <constraint firstItem="1Mw-7R-dLz" firstAttribute="top" secondItem="7U7-1u-Czi" secondAttribute="bottom" constant="9" id="sf0-GH-t1q"/>
+                        <constraint firstItem="J1s-eN-oKW" firstAttribute="centerY" secondItem="7U7-1u-Czi" secondAttribute="centerY" id="vEG-9g-xuH"/>
+                        <constraint firstItem="9DQ-zy-Jq5" firstAttribute="width" secondItem="1Mw-7R-dLz" secondAttribute="width" id="wD2-ML-f5L"/>
+                    </constraints>
+                    <userDefinedRuntimeAttributes>
+                        <userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
+                            <real key="value" value="10"/>
+                        </userDefinedRuntimeAttribute>
+                    </userDefinedRuntimeAttributes>
+                </view>
+                <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ih1-GM-HgL">
+                    <rect key="frame" x="741" y="75.5" width="44" height="44"/>
+                    <constraints>
+                        <constraint firstAttribute="height" constant="44" id="9oe-3I-cob"/>
+                        <constraint firstAttribute="width" constant="44" id="QQk-YE-bdJ"/>
+                    </constraints>
+                    <state key="normal" image="cloud_cancle"/>
+                </button>
+            </subviews>
+            <color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.52000000000000002" colorSpace="custom" customColorSpace="sRGB"/>
+            <constraints>
+                <constraint firstItem="0Jt-Nx-mxp" firstAttribute="centerX" secondItem="iN0-l3-epB" secondAttribute="centerX" id="9xt-7i-vZa"/>
+                <constraint firstItem="ih1-GM-HgL" firstAttribute="centerY" secondItem="0Jt-Nx-mxp" secondAttribute="top" id="ORp-y4-6jt"/>
+                <constraint firstItem="ih1-GM-HgL" firstAttribute="leading" secondItem="0Jt-Nx-mxp" secondAttribute="trailing" id="Yqg-ks-j0d"/>
+                <constraint firstItem="0Jt-Nx-mxp" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="centerY" id="vRQ-QD-vwY"/>
+            </constraints>
+            <nil key="simulatedTopBarMetrics"/>
+            <nil key="simulatedBottomBarMetrics"/>
+            <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
+            <connections>
+                <outlet property="countLabel" destination="9Jw-La-2Hn" id="Pbq-Rz-Khb"/>
+                <outlet property="textView" destination="l1z-4Y-3eT" id="xeT-Xf-3MD"/>
+                <outlet property="tipsLabel" destination="nvM-32-Z6W" id="pKp-zJ-BqV"/>
+            </connections>
+            <point key="canvasLocation" x="-4.3478260869565224" y="-53.90625"/>
+        </view>
+    </objects>
+    <resources>
+        <image name="cloud_cancle" width="26" height="26"/>
+        <systemColor name="labelColor">
+            <color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+        </systemColor>
+        <systemColor name="systemBackgroundColor">
+            <color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+        </systemColor>
+    </resources>
+</document>

+ 23 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/Help/CloudHelpView.h

@@ -0,0 +1,23 @@
+//
+//  CloudHelpView.h
+//  StudentDaya
+//
+//  Created by Kyle on 2021/12/15.
+//  Copyright © 2021 DayaMusic. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+typedef void(^CloudFeedbackAction)(void);
+
+@interface CloudHelpView : UIView
+
++ (instancetype)shareInstance;
+
+- (void)feedbackCallback:(CloudFeedbackAction)callback;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 254 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/Help/CloudHelpView.m

@@ -0,0 +1,254 @@
+//
+//  CloudHelpView.m
+//  StudentDaya
+//
+//  Created by Kyle on 2021/12/15.
+//  Copyright © 2021 DayaMusic. All rights reserved.
+//
+
+#import "CloudHelpView.h"
+#import <WebKit/WebKit.h>
+#import "WeakWebViewScriptMessageDelegate.h"
+
+@interface CloudHelpView ()
+
+@property (weak, nonatomic) IBOutlet UIButton *guideButton;
+@property (weak, nonatomic) IBOutlet UIView *backView;
+
+@property (weak, nonatomic) IBOutlet UIButton *helpButton;
+@property (weak, nonatomic) IBOutlet UIView *lineView;
+@property (weak, nonatomic) IBOutlet UIView *baseView;
+@property (weak, nonatomic) IBOutlet NSLayoutConstraint *lineViewLeft;
+
+@property (nonatomic, strong) UIScrollView *baseScrollView;
+
+@property (nonatomic, strong) WKWebView *guideWebView;
+
+@property (nonatomic, strong) WKWebView *helpWebView;
+
+@property (nonatomic, strong) UIButton *feedbackButton;
+
+@property (nonatomic, strong) CloudFeedbackAction callback;
+
+@end
+
+@implementation CloudHelpView
+
++ (instancetype)shareInstance {
+    CloudHelpView *view = [[[NSBundle mainBundle] loadNibNamed:@"CloudHelpView" owner:nil options:nil] firstObject];
+    [view configHelpView];
+    return view;
+}
+
+- (void)configHelpView {
+    
+    if (@available(iOS 11.0, *)) {
+        self.backView.layer.cornerRadius = 14;
+        self.backView.layer.maskedCorners = kCALayerMinXMinYCorner | kCALayerMinXMaxYCorner; // 左上圆角
+    }
+    else {
+        UIBezierPath * path = [UIBezierPath bezierPathWithRoundedRect:self.backView.bounds byRoundingCorners:UIRectCornerTopLeft | UIRectCornerBottomLeft cornerRadii:CGSizeMake(14, 14)];
+        CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
+        maskLayer.frame = self.backView.bounds;
+        maskLayer.path = path.CGPath;
+        self.backView.layer.mask = maskLayer;
+    }
+
+    [self.baseView addSubview:self.baseScrollView];
+    [self.baseScrollView addSubview:self.guideWebView];
+    [self.baseScrollView addSubview:self.helpWebView];
+    [self setUserAgent];
+    [self.baseScrollView addSubview:self.feedbackButton];
+}
+
+
+
+- (void)setUserAgent {
+    
+    MJWeakSelf;
+    [self.guideWebView evaluateJavaScript:@"navigator.userAgent" completionHandler:^(id result, NSError *error) {
+        NSString *oldUserAgent = result;
+        NSString *newUserAgent = [NSString stringWithFormat:@"%@ %@",oldUserAgent,@"DAYAAPPI"];
+        weakSelf.guideWebView.customUserAgent = newUserAgent;
+        [weakSelf loadRequestIsGuide:YES];
+    }];
+    [self.helpWebView evaluateJavaScript:@"navigator.userAgent" completionHandler:^(id result, NSError *error) {
+        NSString *oldUserAgent = result;
+        NSString *newUserAgent = [NSString stringWithFormat:@"%@ %@",oldUserAgent,@"DAYAAPPI"];
+        weakSelf.helpWebView.customUserAgent = newUserAgent;
+        [weakSelf loadRequestIsGuide:NO];;
+    }];
+}
+
+- (void)loadRequestIsGuide:(BOOL)isGuide {
+    MJWeakSelf;
+    if (isGuide) {
+        [self.guideWebView evaluateJavaScript:@"navigator.userAgent" completionHandler:^(id _Nullable result, NSError * _Nullable error) {
+            NSLog(@"%@",result);
+            NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[weakSelf getUrlIsGuide:YES]] cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:60.0f];
+            [weakSelf.guideWebView loadRequest:request];
+        }];
+    }
+    else {
+        [self.helpWebView evaluateJavaScript:@"navigator.userAgent" completionHandler:^(id _Nullable result, NSError * _Nullable error) {
+            NSLog(@"%@",result);
+            NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[weakSelf getUrlIsGuide:NO]] cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:60.0f];
+            [weakSelf.helpWebView loadRequest:request];
+        }];
+    }
+}
+
+- (NSString *)getUrlIsGuide:(BOOL)isGuide {
+    NSString *tokenStr = UserDefault(TokenKey);
+    NSString *token = nil;
+    if (![NSString isEmptyString:tokenStr]) {
+        token = [[NSString stringWithFormat:@"Authorization=%@ %@", UserDefault(Token_type), tokenStr] stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
+    }
+    if (isGuide) {
+        NSString *url = @"https://mteaonline.dayaedu.com/#/guide";
+        if (![NSString isEmptyString:token]) {
+            url = [NSString stringWithFormat:@"%@?%@",url,token];
+        }
+        return url;
+    }
+    else {
+        NSString *url = @"https://mstuonline.dayaedu.com/#/KeepRepaire?mode=accompany";
+        if (![NSString isEmptyString:token]) {
+            url = [NSString stringWithFormat:@"%@&%@",url,token];
+        }
+        return url;
+    }
+}
+
+
+- (void)feedbackCallback:(CloudFeedbackAction)callback {
+    if (callback) {
+        self.callback = callback;
+    }
+}
+
+- (IBAction)guideAction:(id)sender { // 投屏
+    [self configGuideChoose:YES];
+}
+
+- (IBAction)helpAction:(id)sender { // 帮助
+    [self configGuideChoose:NO];
+}
+
+- (void)configGuideChoose:(BOOL)isChooseSettingButton {
+    CGFloat positionX = 0.0f;
+    NSInteger pageIndex = 0;
+    if (isChooseSettingButton) {
+        [self.guideButton setTitleColor:HexRGB(0x01c1b5) forState:UIControlStateNormal];
+        [self.helpButton setTitleColor:HexRGB(0x1a1a1a) forState:UIControlStateNormal];
+        positionX = 65.0f;
+        pageIndex = 0;
+    }
+    else {
+        [self.guideButton setTitleColor:HexRGB(0x1a1a1a) forState:UIControlStateNormal];
+        [self.helpButton setTitleColor:HexRGB(0x01c1b5) forState:UIControlStateNormal];
+        positionX = 225.0f;
+        pageIndex = 1;
+    }
+    [UIView animateWithDuration:0.5f animations:^{
+        self.lineViewLeft.constant = positionX;
+        [self.baseScrollView setContentOffset:CGPointMake(pageIndex*320, 0)];
+    }];
+}
+
+- (IBAction)hideButtonAction:(id)sender {
+    [self removeFromSuperview];
+}
+
+- (void)feedbackButtonAction {
+    if (self.callback) {
+        self.callback();
+    }
+}
+
+#pragma mark ---- lazying
+- (UIScrollView *)baseScrollView {
+    if (!_baseScrollView) {
+        CGFloat height = KLandscapeHeight - 55;
+        _baseScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 320, height)];
+        [_baseScrollView setContentSize:CGSizeMake(CGRectGetHeight(self.baseView.frame)*2, height)];
+        _baseScrollView.scrollEnabled = NO;
+    }
+    return _baseScrollView;
+}
+
+- (WKWebView *)guideWebView {
+    if (!_guideWebView) {
+        _guideWebView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, KLandscapeHeight) configuration:[self getwebConfig]];
+        _guideWebView.scrollView.bounces = NO;
+    }
+    return _guideWebView;
+}
+
+- (WKWebView *)helpWebView {
+    if (!_helpWebView) {
+        _helpWebView = [[WKWebView alloc] initWithFrame:CGRectMake(320, 0, 320, KLandscapeHeight - 55) configuration:[self getwebConfig]];
+        _helpWebView.scrollView.bounces = NO;
+    }
+    return _helpWebView;
+}
+
+- (WKWebViewConfiguration *)getwebConfig {
+    WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
+    config.selectionGranularity = WKSelectionGranularityDynamic;
+    config.allowsInlineMediaPlayback = YES;
+    if (@available(iOS 10.0, *)) {
+        config.mediaTypesRequiringUserActionForPlayback = NO;
+    } else {
+        // Fallback on earlier versions
+        config.requiresUserActionForMediaPlayback = NO;
+    }
+    config.processPool = [CloudHelpView singleWkProcessPool];
+    config.websiteDataStore = [WKWebsiteDataStore defaultDataStore];
+    //自定义的WKScriptMessageHandler 是为了解决内存不释放的问题
+    WeakWebViewScriptMessageDelegate *weakScriptMessageDelegate = [[WeakWebViewScriptMessageDelegate alloc] initWithDelegate:self];
+    //这个类主要用来做native与JavaScript的交互管理
+    WKUserContentController * wkUController = [[WKUserContentController alloc] init];
+    [wkUController addScriptMessageHandler:weakScriptMessageDelegate name:@"DAYA"];
+    config.userContentController = wkUController;
+    
+    WKPreferences *preferences = [WKPreferences new];
+    // 是否支出javaScript
+    preferences.javaScriptEnabled = YES;
+    //不通过用户交互,是否可以打开窗口
+    preferences.javaScriptCanOpenWindowsAutomatically = YES;
+    config.preferences = preferences;
+    return config;
+}
+
+- (UIButton *)feedbackButton {
+    if (!_feedbackButton) {
+        _feedbackButton = [UIButton buttonWithType:UIButtonTypeCustom];
+        _feedbackButton.frame = CGRectMake(320, KLandscapeHeight - 55 - 55, 320, 55);
+        [_feedbackButton setTitle:@"意见反馈 >" forState:UIControlStateNormal];
+        [_feedbackButton setTitleColor:HexRGB(0x01c1b5) forState:UIControlStateNormal];
+        [_feedbackButton.titleLabel setFont:[UIFont systemFontOfSize:14.0f]];
+        [_feedbackButton addTarget:self action:@selector(feedbackButtonAction) forControlEvents:UIControlEventTouchUpInside];
+    }
+    return _feedbackButton;
+}
+
+
++ (WKProcessPool*)singleWkProcessPool {
+    static WKProcessPool *sharedPool;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        sharedPool = [[WKProcessPool alloc] init];
+    });
+    return sharedPool;
+}
+
+/*
+// Only override drawRect: if you perform custom drawing.
+// An empty implementation adversely affects performance during animation.
+- (void)drawRect:(CGRect)rect {
+    // Drawing code
+}
+*/
+
+@end

+ 131 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/Help/CloudHelpView.xib

@@ -0,0 +1,131 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
+    <device id="retina6_1" orientation="portrait" appearance="light"/>
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/>
+        <capability name="System colors in document resources" minToolsVersion="11.0"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <objects>
+        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
+        <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
+        <view contentMode="scaleToFill" id="iN0-l3-epB" customClass="CloudHelpView">
+            <rect key="frame" x="0.0" y="0.0" width="664" height="386"/>
+            <autoresizingMask key="autoresizingMask"/>
+            <subviews>
+                <view clipsSubviews="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="obm-zW-QzQ">
+                    <rect key="frame" x="344" y="0.0" width="320" height="386"/>
+                    <subviews>
+                        <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Pfo-si-2Hf">
+                            <rect key="frame" x="50" y="0.0" width="60" height="55"/>
+                            <constraints>
+                                <constraint firstAttribute="width" constant="60" id="uvn-eh-hGh"/>
+                            </constraints>
+                            <fontDescription key="fontDescription" type="system" weight="medium" pointSize="16"/>
+                            <state key="normal" title="投屏">
+                                <color key="titleColor" red="0.0039215686269999999" green="0.75686274509999996" blue="0.70980392160000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                            </state>
+                            <connections>
+                                <action selector="guideAction:" destination="iN0-l3-epB" eventType="touchUpInside" id="z9N-4C-3t8"/>
+                            </connections>
+                        </button>
+                        <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="WNO-Ji-qbp">
+                            <rect key="frame" x="65" y="51" width="30" height="3"/>
+                            <color key="backgroundColor" red="0.0039215686269999999" green="0.75686274509999996" blue="0.70980392160000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                            <constraints>
+                                <constraint firstAttribute="width" constant="30" id="JJC-YP-gAm"/>
+                                <constraint firstAttribute="height" constant="3" id="S3K-Kl-hfz"/>
+                            </constraints>
+                            <userDefinedRuntimeAttributes>
+                                <userDefinedRuntimeAttribute type="number" keyPath="layer.CornerRadius">
+                                    <real key="value" value="1.5"/>
+                                </userDefinedRuntimeAttribute>
+                            </userDefinedRuntimeAttributes>
+                        </view>
+                        <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="uc4-t2-7YM">
+                            <rect key="frame" x="0.0" y="55" width="320" height="1"/>
+                            <color key="backgroundColor" red="0.94117647059999998" green="0.94117647059999998" blue="0.94117647059999998" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                            <constraints>
+                                <constraint firstAttribute="height" constant="1" id="k4O-rI-Awa"/>
+                            </constraints>
+                        </view>
+                        <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="lBO-uK-4OU">
+                            <rect key="frame" x="210" y="0.0" width="60" height="55"/>
+                            <constraints>
+                                <constraint firstAttribute="width" constant="60" id="vu6-VN-5eC"/>
+                            </constraints>
+                            <fontDescription key="fontDescription" type="system" weight="medium" pointSize="16"/>
+                            <state key="normal" title="帮助">
+                                <color key="titleColor" red="0.1019607843" green="0.1019607843" blue="0.1019607843" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                            </state>
+                            <connections>
+                                <action selector="helpAction:" destination="iN0-l3-epB" eventType="touchUpInside" id="1JP-jM-sMu"/>
+                            </connections>
+                        </button>
+                        <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="71I-Qi-kjr">
+                            <rect key="frame" x="0.0" y="56" width="320" height="330"/>
+                            <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+                        </view>
+                    </subviews>
+                    <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+                    <constraints>
+                        <constraint firstAttribute="trailing" secondItem="uc4-t2-7YM" secondAttribute="trailing" id="0dz-Ph-iv1"/>
+                        <constraint firstItem="Pfo-si-2Hf" firstAttribute="top" secondItem="obm-zW-QzQ" secondAttribute="top" id="0so-tT-vKx"/>
+                        <constraint firstItem="uc4-t2-7YM" firstAttribute="leading" secondItem="obm-zW-QzQ" secondAttribute="leading" id="4V8-I7-BUQ"/>
+                        <constraint firstItem="uc4-t2-7YM" firstAttribute="top" secondItem="lBO-uK-4OU" secondAttribute="bottom" id="9GF-0F-mT8"/>
+                        <constraint firstItem="WNO-Ji-qbp" firstAttribute="leading" secondItem="obm-zW-QzQ" secondAttribute="leading" constant="65" id="Cel-5r-oZ9"/>
+                        <constraint firstItem="uc4-t2-7YM" firstAttribute="top" secondItem="WNO-Ji-qbp" secondAttribute="bottom" constant="1" id="Z93-Es-h8v"/>
+                        <constraint firstItem="71I-Qi-kjr" firstAttribute="top" secondItem="uc4-t2-7YM" secondAttribute="bottom" id="a6k-qz-AQp"/>
+                        <constraint firstAttribute="width" constant="320" id="gnO-9b-8yi"/>
+                        <constraint firstItem="lBO-uK-4OU" firstAttribute="centerX" secondItem="obm-zW-QzQ" secondAttribute="centerX" multiplier="1.5" id="ium-a0-a9D"/>
+                        <constraint firstItem="uc4-t2-7YM" firstAttribute="top" secondItem="Pfo-si-2Hf" secondAttribute="bottom" id="ixj-NX-tsw"/>
+                        <constraint firstAttribute="bottom" secondItem="71I-Qi-kjr" secondAttribute="bottom" id="j6y-lQ-MyW"/>
+                        <constraint firstItem="lBO-uK-4OU" firstAttribute="top" secondItem="obm-zW-QzQ" secondAttribute="top" id="odh-RK-gZ6"/>
+                        <constraint firstItem="Pfo-si-2Hf" firstAttribute="centerX" secondItem="obm-zW-QzQ" secondAttribute="centerX" multiplier="0.5" id="ps6-fp-tnP"/>
+                        <constraint firstItem="uc4-t2-7YM" firstAttribute="top" secondItem="obm-zW-QzQ" secondAttribute="top" constant="55" id="q58-oF-BP6"/>
+                        <constraint firstItem="71I-Qi-kjr" firstAttribute="leading" secondItem="obm-zW-QzQ" secondAttribute="leading" id="qjc-20-qyp"/>
+                        <constraint firstAttribute="trailing" secondItem="71I-Qi-kjr" secondAttribute="trailing" id="ruC-NP-2Gb"/>
+                    </constraints>
+                </view>
+                <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="9oz-N7-A8a">
+                    <rect key="frame" x="314" y="171" width="30" height="44"/>
+                    <constraints>
+                        <constraint firstAttribute="width" constant="30" id="EZT-YP-4uR"/>
+                        <constraint firstAttribute="height" constant="44" id="Pg5-h0-M6s"/>
+                    </constraints>
+                    <state key="normal" image="help_hide"/>
+                    <connections>
+                        <action selector="hideButtonAction:" destination="iN0-l3-epB" eventType="touchUpInside" id="srH-JR-kyz"/>
+                    </connections>
+                </button>
+            </subviews>
+            <color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.52000000000000002" colorSpace="custom" customColorSpace="sRGB"/>
+            <constraints>
+                <constraint firstItem="9oz-N7-A8a" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="centerY" id="9Y1-dp-fZY"/>
+                <constraint firstAttribute="bottom" secondItem="obm-zW-QzQ" secondAttribute="bottom" id="aNJ-Eu-aUX"/>
+                <constraint firstItem="obm-zW-QzQ" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="djf-ox-UZr"/>
+                <constraint firstItem="obm-zW-QzQ" firstAttribute="trailing" secondItem="iN0-l3-epB" secondAttribute="trailing" id="lR7-Xg-NWG"/>
+                <constraint firstItem="obm-zW-QzQ" firstAttribute="leading" secondItem="9oz-N7-A8a" secondAttribute="trailing" id="rqU-Um-xDZ"/>
+            </constraints>
+            <nil key="simulatedTopBarMetrics"/>
+            <nil key="simulatedBottomBarMetrics"/>
+            <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
+            <connections>
+                <outlet property="backView" destination="obm-zW-QzQ" id="Xt4-WW-JX9"/>
+                <outlet property="baseView" destination="71I-Qi-kjr" id="CuG-VD-HGS"/>
+                <outlet property="guideButton" destination="Pfo-si-2Hf" id="yEj-8V-jim"/>
+                <outlet property="helpButton" destination="lBO-uK-4OU" id="dQs-Y4-hN3"/>
+                <outlet property="lineView" destination="WNO-Ji-qbp" id="WQ2-d8-T5g"/>
+                <outlet property="lineViewLeft" destination="Cel-5r-oZ9" id="5jX-dg-uBc"/>
+            </connections>
+            <point key="canvasLocation" x="257.97101449275362" y="-97.098214285714278"/>
+        </view>
+    </objects>
+    <resources>
+        <image name="help_hide" width="30" height="44"/>
+        <systemColor name="systemBackgroundColor">
+            <color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+        </systemColor>
+    </resources>
+</document>

+ 29 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/SettingView/JudgePageView.h

@@ -0,0 +1,29 @@
+//
+//  JudgePageView.h
+//  MidiPlayer
+//
+//  Created by Kyle on 2021/12/7.
+//
+
+#import <UIKit/UIKit.h>
+#import "KSCloudSettingView.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface JudgePageView : UIView
+
+@property (nonatomic, assign) NSString *level; //评测难度
+
+@property (nonatomic, assign) BOOL saveVideo;   // 是否保存到相册
+
+@property (nonatomic, assign) BOOL accompanyOn; // 是否开启伴奏
+
++ (instancetype)shareInstance;
+
+- (void)querySettingConfig;
+
+- (void)judgeSettingCallback:(CloudSettingCallback)callback;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 176 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/SettingView/JudgePageView.m

@@ -0,0 +1,176 @@
+//
+//  JudgePageView.m
+//  MidiPlayer
+//
+//  Created by Kyle on 2021/12/7.
+//
+
+#import "JudgePageView.h"
+
+@interface JudgePageView ()
+
+@property (weak, nonatomic) IBOutlet UIButton *primerButton;
+
+@property (weak, nonatomic) IBOutlet UIButton *advancedButton;
+
+@property (weak, nonatomic) IBOutlet UIButton *masterButton;
+
+@property (weak, nonatomic) IBOutlet UIButton *saveButton;
+
+@property (weak, nonatomic) IBOutlet UIButton *accompanyButton;
+
+@property (nonatomic, copy) CloudSettingCallback callback;
+
+@property (nonatomic, strong) NSMutableDictionary *configDict;
+
+@end
+
+@implementation JudgePageView
+
++ (instancetype)shareInstance {
+    JudgePageView *view = [[[NSBundle mainBundle] loadNibNamed:@"JudgePageView" owner:nil options:nil] firstObject];
+    [view querySettingConfig];
+    return view;
+}
+
+- (void)setButtonSelected:(BOOL)isSelected withButton:(UIButton *)button {
+    if (isSelected) {
+        button.backgroundColor = HexRGB(0xe2fff9);
+        button.layer.borderColor = HexRGB(0x01c1b5).CGColor;
+        [button setTitleColor:HexRGB(0x01c1b5) forState:UIControlStateNormal];
+        [button.titleLabel setFont:[UIFont systemFontOfSize:14.0f weight:UIFontWeightMedium]];
+    }
+    else {
+        button.backgroundColor = HexRGB(0xf8f8f8);
+        button.layer.borderColor = HexRGB(0xf8f8f8).CGColor;
+        [button setTitleColor:HexRGB(0x666666) forState:UIControlStateNormal];
+        [button.titleLabel setFont:[UIFont systemFontOfSize:14.0f weight:UIFontWeightRegular]];
+    }
+}
+
+- (void)defaultSetting {
+    self.primerButton.layer.cornerRadius = 5.0f;
+    self.primerButton.layer.borderWidth = 1.0f;
+    
+    self.advancedButton.layer.cornerRadius = 5.0f;
+    self.advancedButton.layer.borderWidth = 1.0f;
+    
+    self.masterButton.layer.cornerRadius = 5.0f;
+    self.masterButton.layer.borderWidth = 1.0f;
+    
+    [self setButtonSelected:YES withButton:self.primerButton];
+    [self setButtonSelected:NO withButton:self.advancedButton];
+    [self setButtonSelected:NO withButton:self.masterButton];
+    self.level = @"BEGINNER";
+    self.accompanyOn = NO;
+    self.saveVideo = NO;
+}
+
+- (void)querySettingConfig {
+    [self defaultSetting];
+    
+    // 根据储存的配置项显示
+    NSDictionary *config = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"cloudConfig"];
+    
+    self.configDict = [NSMutableDictionary dictionaryWithDictionary:config];
+    self.level = [config stringValueForKey:@"level"];
+    self.accompanyOn = [config boolValueForKey:@"accompanyOn"];
+    self.saveVideo = [config boolValueForKey:@"save"];
+    if ([self.level isEqualToString:@"BEGINNER"]) {
+        
+    }
+    else if ([self.level isEqualToString:@"ADVANCED"]) {
+        [self setButtonSelected:NO withButton:self.primerButton];
+        [self setButtonSelected:YES withButton:self.advancedButton];
+    }
+    else if ([self.level isEqualToString:@"PERFORMER"]) {
+        [self setButtonSelected:NO withButton:self.primerButton];
+        [self setButtonSelected:YES withButton:self.masterButton];
+    }
+    
+}
+
+- (void)synchronizeConfig {
+    [[NSUserDefaults standardUserDefaults] setObject:self.configDict forKey:@"cloudConfig"];
+    [[NSUserDefaults standardUserDefaults] synchronize];
+}
+
+- (void)judgeSettingCallback:(CloudSettingCallback)callback {
+    if (callback) {
+        self.callback = callback;
+    }
+}
+
+- (IBAction)levelButtonAction:(UIButton *)sender {
+    
+    NSInteger buttonIndex = sender.tag;
+    if (buttonIndex == 1001) {
+        self.level = @"BEGINNER";
+        [self setButtonSelected:YES withButton:self.primerButton];
+        [self setButtonSelected:NO withButton:self.advancedButton];
+        [self setButtonSelected:NO withButton:self.masterButton];
+        
+        [self.configDict setValue:@"BEGINNER" forKey:@"level"];
+    }
+    else if (buttonIndex == 1002) {
+        self.level = @"ADVANCED";
+        [self setButtonSelected:NO withButton:self.primerButton];
+        [self setButtonSelected:YES withButton:self.advancedButton];
+        [self setButtonSelected:NO withButton:self.masterButton];
+        [self.configDict setValue:@"ADVANCED" forKey:@"level"];
+    }
+    else {
+        self.level = @"PERFORMER";
+        [self setButtonSelected:NO withButton:self.primerButton];
+        [self setButtonSelected:NO withButton:self.advancedButton];
+        [self setButtonSelected:YES withButton:self.masterButton];
+        [self.configDict setValue:@"PERFORMER" forKey:@"level"];
+    }
+    [self synchronizeConfig];
+    
+    if (self.callback) {
+        self.callback(CLOUDSETTING_TYPE_LEVEL, NO, self.level);
+    }
+}
+
+- (IBAction)saveVideoAction:(UIButton *)sender {
+    self.saveVideo = !self.saveVideo;
+    [self.configDict setValue:@(self.saveVideo) forKey:@"save"];
+    [self synchronizeConfig];
+    if (self.callback) {
+        self.callback(CLOUDSETTING_TYPE_SAVEVIDEO, self.saveVideo, @"");
+    }
+}
+
+- (IBAction)accompanyAction:(id)sender {
+    self.accompanyOn = !self.accompanyOn;
+    [self.configDict setValue:@(self.accompanyOn) forKey:@"accompanyOn"];
+    [self synchronizeConfig];
+    if (self.callback) {
+        self.callback(CLOUDSETTING_TYPE_ACCOMPANY, self.accompanyOn, @"");
+    }
+    
+}
+
+#pragma mark -- setter
+- (void)setAccompanyOn:(BOOL)accompanyOn {
+    _accompanyOn = accompanyOn;
+    NSString *imageName = accompanyOn ? @"switch_on" : @"switch_off";
+    [self.accompanyButton setImage:[UIImage imageNamed:imageName] forState:UIControlStateNormal];
+}
+
+- (void)setSaveVideo:(BOOL)saveVideo {
+    _saveVideo = saveVideo;
+    NSString *imageName = saveVideo ? @"switch_on" : @"switch_off";
+    [self.saveButton setImage:[UIImage imageNamed:imageName] forState:UIControlStateNormal];
+}
+
+/*
+// Only override drawRect: if you perform custom drawing.
+// An empty implementation adversely affects performance during animation.
+- (void)drawRect:(CGRect)rect {
+    // Drawing code
+}
+*/
+
+@end

+ 157 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/SettingView/JudgePageView.xib

@@ -0,0 +1,157 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
+    <device id="retina6_1" orientation="portrait" appearance="light"/>
+    <dependencies>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/>
+        <capability name="System colors in document resources" minToolsVersion="11.0"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <objects>
+        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
+        <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
+        <view contentMode="scaleToFill" id="iN0-l3-epB" customClass="JudgePageView">
+            <rect key="frame" x="0.0" y="0.0" width="414" height="357"/>
+            <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+            <subviews>
+                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="请选择评测难度" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="6tM-MJ-pJi">
+                    <rect key="frame" x="18" y="14" width="100" height="20"/>
+                    <constraints>
+                        <constraint firstAttribute="height" constant="20" id="UhA-MQ-PwR"/>
+                    </constraints>
+                    <fontDescription key="fontDescription" type="system" pointSize="14"/>
+                    <color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                    <nil key="highlightedColor"/>
+                </label>
+                <button opaque="NO" tag="1001" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Mix-L6-JTm">
+                    <rect key="frame" x="18" y="44" width="122.5" height="30"/>
+                    <color key="backgroundColor" red="0.88627450980392153" green="1" blue="0.97647058823529409" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                    <constraints>
+                        <constraint firstAttribute="height" constant="30" id="9PN-D0-3W4"/>
+                    </constraints>
+                    <fontDescription key="fontDescription" type="system" pointSize="14"/>
+                    <state key="normal" title="入门级">
+                        <color key="titleColor" red="0.0039215686269999999" green="0.75686274509999996" blue="0.70980392160000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                    </state>
+                    <connections>
+                        <action selector="levelButtonAction:" destination="iN0-l3-epB" eventType="touchUpInside" id="UAc-wl-i3a"/>
+                    </connections>
+                </button>
+                <button opaque="NO" tag="1002" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="1Ba-L1-uR3">
+                    <rect key="frame" x="145.5" y="44" width="123" height="30"/>
+                    <color key="backgroundColor" red="0.97254901960784312" green="0.97254901960784312" blue="0.97254901960784312" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                    <fontDescription key="fontDescription" type="system" pointSize="14"/>
+                    <state key="normal" title="进阶级">
+                        <color key="titleColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                    </state>
+                    <connections>
+                        <action selector="levelButtonAction:" destination="iN0-l3-epB" eventType="touchUpInside" id="R5s-ej-zFk"/>
+                    </connections>
+                </button>
+                <button opaque="NO" tag="1003" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="yaq-mM-z7i">
+                    <rect key="frame" x="273.5" y="44" width="122.5" height="30"/>
+                    <color key="backgroundColor" red="0.97254901960784312" green="0.97254901960784312" blue="0.97254901960784312" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                    <fontDescription key="fontDescription" type="system" pointSize="14"/>
+                    <state key="normal" title="大师级">
+                        <color key="titleColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                    </state>
+                    <connections>
+                        <action selector="levelButtonAction:" destination="iN0-l3-epB" eventType="touchUpInside" id="HIW-wJ-OsT"/>
+                    </connections>
+                </button>
+                <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ewd-ee-kt8">
+                    <rect key="frame" x="0.0" y="94" width="414" height="50"/>
+                    <subviews>
+                        <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="保存到相册" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="mBe-Xi-HzQ">
+                            <rect key="frame" x="25" y="16" width="76.5" height="18"/>
+                            <fontDescription key="fontDescription" type="system" pointSize="15"/>
+                            <color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                            <nil key="highlightedColor"/>
+                        </label>
+                        <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="v9d-6q-6e7">
+                            <rect key="frame" x="342" y="0.0" width="47" height="50"/>
+                            <state key="normal" image="switch_off"/>
+                            <connections>
+                                <action selector="saveVideoAction:" destination="iN0-l3-epB" eventType="touchUpInside" id="pM0-BB-58b"/>
+                            </connections>
+                        </button>
+                    </subviews>
+                    <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+                    <constraints>
+                        <constraint firstItem="v9d-6q-6e7" firstAttribute="top" secondItem="ewd-ee-kt8" secondAttribute="top" id="7yN-C5-3LK"/>
+                        <constraint firstAttribute="height" constant="50" id="ABJ-aC-X3d"/>
+                        <constraint firstAttribute="trailing" secondItem="v9d-6q-6e7" secondAttribute="trailing" constant="25" id="Lg4-nm-UEh"/>
+                        <constraint firstItem="mBe-Xi-HzQ" firstAttribute="leading" secondItem="ewd-ee-kt8" secondAttribute="leading" constant="25" id="q3b-dc-YC5"/>
+                        <constraint firstItem="mBe-Xi-HzQ" firstAttribute="centerY" secondItem="ewd-ee-kt8" secondAttribute="centerY" id="xiw-AK-3l4"/>
+                        <constraint firstAttribute="bottom" secondItem="v9d-6q-6e7" secondAttribute="bottom" id="yCX-Tw-oXC"/>
+                    </constraints>
+                </view>
+                <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="wT6-4G-RF6">
+                    <rect key="frame" x="0.0" y="144" width="414" height="50"/>
+                    <subviews>
+                        <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="开启伴奏" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="EGX-eA-JmP">
+                            <rect key="frame" x="25" y="16" width="61.5" height="18"/>
+                            <fontDescription key="fontDescription" type="system" pointSize="15"/>
+                            <color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                            <nil key="highlightedColor"/>
+                        </label>
+                        <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="TiY-1T-4xp">
+                            <rect key="frame" x="342" y="0.0" width="47" height="50"/>
+                            <state key="normal" image="switch_off"/>
+                            <connections>
+                                <action selector="accompanyAction:" destination="iN0-l3-epB" eventType="touchUpInside" id="oDS-hQ-fsL"/>
+                            </connections>
+                        </button>
+                    </subviews>
+                    <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+                    <constraints>
+                        <constraint firstAttribute="height" constant="50" id="HxP-dR-8c0"/>
+                        <constraint firstItem="EGX-eA-JmP" firstAttribute="centerY" secondItem="wT6-4G-RF6" secondAttribute="centerY" id="NEJ-PG-EAZ"/>
+                        <constraint firstItem="TiY-1T-4xp" firstAttribute="top" secondItem="wT6-4G-RF6" secondAttribute="top" id="Vuy-vz-yRz"/>
+                        <constraint firstItem="EGX-eA-JmP" firstAttribute="leading" secondItem="wT6-4G-RF6" secondAttribute="leading" constant="25" id="WqS-4b-dJf"/>
+                        <constraint firstAttribute="bottom" secondItem="TiY-1T-4xp" secondAttribute="bottom" id="nHp-fe-9n3"/>
+                        <constraint firstAttribute="trailing" secondItem="TiY-1T-4xp" secondAttribute="trailing" constant="25" id="vUS-JS-DBi"/>
+                    </constraints>
+                </view>
+            </subviews>
+            <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+            <constraints>
+                <constraint firstItem="yaq-mM-z7i" firstAttribute="width" secondItem="Mix-L6-JTm" secondAttribute="width" id="6bo-sh-Nwi"/>
+                <constraint firstItem="wT6-4G-RF6" firstAttribute="trailing" secondItem="ewd-ee-kt8" secondAttribute="trailing" id="7o6-A3-2si"/>
+                <constraint firstItem="1Ba-L1-uR3" firstAttribute="height" secondItem="Mix-L6-JTm" secondAttribute="height" id="DLz-1P-vYA"/>
+                <constraint firstItem="6tM-MJ-pJi" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" constant="14" id="GNi-xR-SgQ"/>
+                <constraint firstItem="6tM-MJ-pJi" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="18" id="JmM-tg-2JP"/>
+                <constraint firstItem="ewd-ee-kt8" firstAttribute="top" secondItem="Mix-L6-JTm" secondAttribute="bottom" constant="20" id="Ky4-rH-ieE"/>
+                <constraint firstItem="Mix-L6-JTm" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="18" id="N1w-be-fcb"/>
+                <constraint firstAttribute="trailing" secondItem="yaq-mM-z7i" secondAttribute="trailing" constant="18" id="Qni-vs-IQ5"/>
+                <constraint firstItem="yaq-mM-z7i" firstAttribute="centerY" secondItem="Mix-L6-JTm" secondAttribute="centerY" id="RHc-4i-LCz"/>
+                <constraint firstItem="ewd-ee-kt8" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="T2Z-Rf-LUn"/>
+                <constraint firstItem="1Ba-L1-uR3" firstAttribute="width" secondItem="Mix-L6-JTm" secondAttribute="width" id="VOu-dl-xiJ"/>
+                <constraint firstItem="Mix-L6-JTm" firstAttribute="top" secondItem="6tM-MJ-pJi" secondAttribute="bottom" constant="10" id="Xei-zn-Itl"/>
+                <constraint firstItem="yaq-mM-z7i" firstAttribute="leading" secondItem="1Ba-L1-uR3" secondAttribute="trailing" constant="5" id="YYC-2y-ScH"/>
+                <constraint firstItem="wT6-4G-RF6" firstAttribute="leading" secondItem="ewd-ee-kt8" secondAttribute="leading" id="cAn-uv-UNL"/>
+                <constraint firstAttribute="trailing" secondItem="ewd-ee-kt8" secondAttribute="trailing" id="fYt-iT-CUo"/>
+                <constraint firstItem="1Ba-L1-uR3" firstAttribute="centerY" secondItem="Mix-L6-JTm" secondAttribute="centerY" id="hx4-zc-P3c"/>
+                <constraint firstItem="1Ba-L1-uR3" firstAttribute="leading" secondItem="Mix-L6-JTm" secondAttribute="trailing" constant="5" id="udr-3Q-VlX"/>
+                <constraint firstItem="wT6-4G-RF6" firstAttribute="top" secondItem="ewd-ee-kt8" secondAttribute="bottom" id="xWz-Qo-3Pw"/>
+                <constraint firstItem="yaq-mM-z7i" firstAttribute="height" secondItem="Mix-L6-JTm" secondAttribute="height" id="ynh-O9-NS9"/>
+            </constraints>
+            <nil key="simulatedTopBarMetrics"/>
+            <nil key="simulatedBottomBarMetrics"/>
+            <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
+            <connections>
+                <outlet property="accompanyButton" destination="TiY-1T-4xp" id="UXU-9m-y1O"/>
+                <outlet property="advancedButton" destination="1Ba-L1-uR3" id="mli-9q-Z1z"/>
+                <outlet property="masterButton" destination="yaq-mM-z7i" id="boo-lD-3gf"/>
+                <outlet property="primerButton" destination="Mix-L6-JTm" id="aCK-L5-fDs"/>
+                <outlet property="saveButton" destination="v9d-6q-6e7" id="mnk-y8-eQ9"/>
+            </connections>
+            <point key="canvasLocation" x="76.811594202898561" y="-12.388392857142856"/>
+        </view>
+    </objects>
+    <resources>
+        <image name="switch_off" width="47" height="24"/>
+        <systemColor name="systemBackgroundColor">
+            <color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+        </systemColor>
+    </resources>
+</document>

+ 36 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/SettingView/KSCloudSettingView.h

@@ -0,0 +1,36 @@
+//
+//  KSCloudSettingView.h
+//  MidiPlayer
+//
+//  Created by Kyle on 2021/12/7.
+//
+
+#import <UIKit/UIKit.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+typedef NS_ENUM(NSInteger, CLOUDSETTING_TYPE) {
+    CLOUDSETTING_TYPE_EYESHIELD,    // 护眼模式
+    CLOUDSETTING_TYPE_SOUNDCHECK,   // 校音
+    CLOUDSETTING_TYPE_CAMERA,       // 摄像头
+    CLOUDSETTING_TYPE_REPEAT,       // 循环播放
+    CLOUDSETTING_TYPE_FINGER,       // 显示指法
+    CLOUDSETTING_TYPE_LEVEL,        // 评测级别
+    CLOUDSETTING_TYPE_SAVEVIDEO,    // 保存视频
+    CLOUDSETTING_TYPE_ACCOMPANY,    // 伴奏
+};
+
+
+typedef void(^CloudSettingCallback)(CLOUDSETTING_TYPE type, BOOL isOn, NSString *level);
+
+/// 云教练设置页面
+@interface KSCloudSettingView : UIView
+
++ (instancetype)shareInstance;
+
+
+- (void)settingCallback:(CloudSettingCallback)callback;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 139 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/SettingView/KSCloudSettingView.m

@@ -0,0 +1,139 @@
+//
+//  KSCloudSettingView.m
+//  MidiPlayer
+//
+//  Created by Kyle on 2021/12/7.
+//
+
+#import "KSCloudSettingView.h"
+#import "SettingPageView.h"
+#import "JudgePageView.h"
+
+@interface KSCloudSettingView ()
+
+@property (nonatomic, copy) CloudSettingCallback callback;
+
+@property (weak, nonatomic) IBOutlet UIButton *settingButton;
+
+@property (weak, nonatomic) IBOutlet UIButton *judgeButton;
+
+@property (weak, nonatomic) IBOutlet UIView *lineView;
+
+@property (weak, nonatomic) IBOutlet UIView *baseView;
+
+@property (weak, nonatomic) IBOutlet NSLayoutConstraint *lineViewLeft;
+
+@property (nonatomic, strong) UIScrollView *baseScrollView;
+
+@property (nonatomic, strong) SettingPageView *settingView;
+
+@property (nonatomic, strong) JudgePageView *judgeView;
+
+@end
+
+
+@implementation KSCloudSettingView
+
++ (instancetype)shareInstance {
+    KSCloudSettingView *view = [[[NSBundle mainBundle] loadNibNamed:@"KSCloudSettingView" owner:nil options:nil] firstObject];
+    [view configSettingView];
+    return view;
+}
+
+- (void)settingCallback:(CloudSettingCallback)callback {
+    if (callback) {
+        self.callback = callback;
+    }
+}
+
+- (void)configSettingView {
+    
+    
+    [self.baseView addSubview:self.baseScrollView];
+    [self.baseScrollView addSubview:self.settingView];
+    [self.baseScrollView addSubview:self.judgeView];
+    
+}
+
+- (IBAction)settingAction:(id)sender { // 设置
+    [self configSettingChoose:YES];
+}
+
+- (IBAction)judgeAction:(id)sender { // 评测
+    [self configSettingChoose:NO];
+}
+
+- (IBAction)cancleAction:(id)sender {
+    [self removeFromSuperview];
+}
+
+- (void)configSettingChoose:(BOOL)isChooseSettingButton {
+    CGFloat positionX = 0.0f;
+    NSInteger pageIndex = 0;
+    if (isChooseSettingButton) {
+        [self.settingButton setTitleColor:HexRGB(0x01c1b5) forState:UIControlStateNormal];
+        [self.judgeButton setTitleColor:HexRGB(0x1a1a1a) forState:UIControlStateNormal];
+        positionX = 65.0f;
+        pageIndex = 0;
+    }
+    else {
+        [self.settingButton setTitleColor:HexRGB(0x1a1a1a) forState:UIControlStateNormal];
+        [self.judgeButton setTitleColor:HexRGB(0x01c1b5) forState:UIControlStateNormal];
+        positionX = 225.0f;
+        pageIndex = 1;
+    }
+    [UIView animateWithDuration:0.5f animations:^{
+        self.lineViewLeft.constant = positionX;
+        [self.baseScrollView setContentOffset:CGPointMake(pageIndex*320, 0)];
+    }];
+}
+
+#pragma mark ---- lazying
+- (UIScrollView *)baseScrollView {
+    if (!_baseScrollView) {
+        CGFloat height = CGRectGetHeight(self.frame) - 33*2 - 55;
+        _baseScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 320, height)];
+        [_baseScrollView setContentSize:CGSizeMake(320*2, height)];
+        _baseScrollView.scrollEnabled = NO;
+    }
+    return _baseScrollView;
+}
+
+- (SettingPageView *)settingView {
+    if (!_settingView) {
+        CGFloat height = CGRectGetHeight(self.frame) - 33*2 - 55;
+        _settingView = [SettingPageView shareInstance];
+        _settingView.frame = CGRectMake(0, 0, 320, height);
+        __weak typeof(self) weakSelf = self;
+        [_settingView settingCallback:^(CLOUDSETTING_TYPE type, BOOL isOn) {
+            if (weakSelf.callback) {
+                weakSelf.callback(type, isOn, @"");
+            }
+        }];
+    }
+    return _settingView;
+}
+
+- (JudgePageView *)judgeView {
+    if (!_judgeView) {
+        CGFloat height = CGRectGetHeight(self.frame) - 33*2 - 55;
+        _judgeView = [JudgePageView shareInstance];
+        _judgeView.frame = CGRectMake(320, 0, 320, height);
+        __weak typeof(self) weakSelf = self;
+        [_judgeView judgeSettingCallback:^(CLOUDSETTING_TYPE type, BOOL isOn, NSString * _Nonnull level) {
+            if (weakSelf.callback) {
+                weakSelf.callback(type, isOn, level);
+            }
+        }];
+    }
+    return _judgeView;
+}
+/*
+// Only override drawRect: if you perform custom drawing.
+// An empty implementation adversely affects performance during animation.
+- (void)drawRect:(CGRect)rect {
+    // Drawing code
+}
+*/
+
+@end

+ 135 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/SettingView/KSCloudSettingView.xib

@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="19529" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
+    <device id="retina6_1" orientation="portrait" appearance="light"/>
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19519"/>
+        <capability name="System colors in document resources" minToolsVersion="11.0"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <objects>
+        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
+        <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
+        <view contentMode="scaleToFill" id="iN0-l3-epB" customClass="KSCloudSettingView">
+            <rect key="frame" x="0.0" y="0.0" width="765" height="340"/>
+            <autoresizingMask key="autoresizingMask"/>
+            <subviews>
+                <view clipsSubviews="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="hfV-D9-4RA">
+                    <rect key="frame" x="222.5" y="33" width="320" height="274"/>
+                    <subviews>
+                        <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ZaF-YM-MHQ">
+                            <rect key="frame" x="0.0" y="54" width="320" height="1"/>
+                            <color key="backgroundColor" red="0.94117647058823528" green="0.94117647058823528" blue="0.94117647058823528" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                            <constraints>
+                                <constraint firstAttribute="height" constant="1" id="JMv-iI-n4b"/>
+                            </constraints>
+                        </view>
+                        <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="p8P-5R-8Rj">
+                            <rect key="frame" x="50" y="0.0" width="60" height="54"/>
+                            <constraints>
+                                <constraint firstAttribute="width" constant="60" id="OK4-wC-P1G"/>
+                            </constraints>
+                            <fontDescription key="fontDescription" type="system" weight="medium" pointSize="16"/>
+                            <state key="normal" title="设置">
+                                <color key="titleColor" red="0.0039215686274509803" green="0.75686274509803919" blue="0.70980392156862748" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                            </state>
+                            <connections>
+                                <action selector="settingAction:" destination="iN0-l3-epB" eventType="touchUpInside" id="ef8-rX-DbF"/>
+                            </connections>
+                        </button>
+                        <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="a7T-m6-cO3">
+                            <rect key="frame" x="210" y="0.0" width="60" height="54"/>
+                            <constraints>
+                                <constraint firstAttribute="width" constant="60" id="HYZ-j2-02K"/>
+                            </constraints>
+                            <fontDescription key="fontDescription" type="system" weight="medium" pointSize="16"/>
+                            <state key="normal" title="评测">
+                                <color key="titleColor" red="0.10196078431372549" green="0.10196078431372549" blue="0.10196078431372549" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                            </state>
+                            <connections>
+                                <action selector="judgeAction:" destination="iN0-l3-epB" eventType="touchUpInside" id="yT1-jF-wEg"/>
+                            </connections>
+                        </button>
+                        <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="C3c-hV-10p">
+                            <rect key="frame" x="65" y="50" width="30" height="3"/>
+                            <color key="backgroundColor" red="0.0039215686269999999" green="0.75686274509999996" blue="0.70980392160000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                            <constraints>
+                                <constraint firstAttribute="width" constant="30" id="mPD-5O-93G"/>
+                                <constraint firstAttribute="height" constant="3" id="vgm-Wv-AR0"/>
+                            </constraints>
+                            <userDefinedRuntimeAttributes>
+                                <userDefinedRuntimeAttribute type="number" keyPath="layer.CornerRadius">
+                                    <real key="value" value="1.5"/>
+                                </userDefinedRuntimeAttribute>
+                            </userDefinedRuntimeAttributes>
+                        </view>
+                        <view clipsSubviews="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="pPQ-tv-M04">
+                            <rect key="frame" x="0.0" y="55" width="320" height="219"/>
+                            <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+                        </view>
+                    </subviews>
+                    <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+                    <constraints>
+                        <constraint firstAttribute="trailing" secondItem="ZaF-YM-MHQ" secondAttribute="trailing" id="0hN-uw-S9A"/>
+                        <constraint firstItem="ZaF-YM-MHQ" firstAttribute="top" secondItem="C3c-hV-10p" secondAttribute="bottom" constant="1" id="6n2-Bw-93p"/>
+                        <constraint firstItem="p8P-5R-8Rj" firstAttribute="centerX" secondItem="hfV-D9-4RA" secondAttribute="centerX" multiplier="0.5" id="AgX-v4-FXk"/>
+                        <constraint firstAttribute="bottom" secondItem="pPQ-tv-M04" secondAttribute="bottom" id="FdO-jV-SRW"/>
+                        <constraint firstItem="ZaF-YM-MHQ" firstAttribute="top" secondItem="a7T-m6-cO3" secondAttribute="bottom" id="LJK-pe-9BW"/>
+                        <constraint firstItem="pPQ-tv-M04" firstAttribute="leading" secondItem="hfV-D9-4RA" secondAttribute="leading" id="NMN-jt-5Dc"/>
+                        <constraint firstItem="ZaF-YM-MHQ" firstAttribute="top" secondItem="p8P-5R-8Rj" secondAttribute="bottom" id="OcI-9y-j24"/>
+                        <constraint firstItem="ZaF-YM-MHQ" firstAttribute="leading" secondItem="hfV-D9-4RA" secondAttribute="leading" id="RJY-gk-vSg"/>
+                        <constraint firstAttribute="trailing" secondItem="pPQ-tv-M04" secondAttribute="trailing" id="TVZ-d9-Jaw"/>
+                        <constraint firstItem="a7T-m6-cO3" firstAttribute="centerX" secondItem="hfV-D9-4RA" secondAttribute="centerX" multiplier="1.5" id="aBp-ee-0ie"/>
+                        <constraint firstAttribute="width" constant="320" id="apj-m7-hnD"/>
+                        <constraint firstItem="pPQ-tv-M04" firstAttribute="top" secondItem="ZaF-YM-MHQ" secondAttribute="bottom" id="npH-Ce-Fgh"/>
+                        <constraint firstItem="ZaF-YM-MHQ" firstAttribute="top" secondItem="hfV-D9-4RA" secondAttribute="top" constant="54" id="uxK-qx-D55"/>
+                        <constraint firstItem="C3c-hV-10p" firstAttribute="leading" secondItem="hfV-D9-4RA" secondAttribute="leading" constant="65" id="vrQ-K4-VVb"/>
+                        <constraint firstItem="p8P-5R-8Rj" firstAttribute="top" secondItem="hfV-D9-4RA" secondAttribute="top" id="yTP-om-2bY"/>
+                        <constraint firstItem="a7T-m6-cO3" firstAttribute="top" secondItem="hfV-D9-4RA" secondAttribute="top" id="zNK-WD-UU4"/>
+                    </constraints>
+                    <userDefinedRuntimeAttributes>
+                        <userDefinedRuntimeAttribute type="number" keyPath="layer.CornerRadius">
+                            <integer key="value" value="10"/>
+                        </userDefinedRuntimeAttribute>
+                    </userDefinedRuntimeAttributes>
+                </view>
+                <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="q0f-Z3-hFt">
+                    <rect key="frame" x="542.5" y="11" width="44" height="44"/>
+                    <constraints>
+                        <constraint firstAttribute="height" constant="44" id="Gty-rQ-CAi"/>
+                        <constraint firstAttribute="width" constant="44" id="tQd-jw-Mrl"/>
+                    </constraints>
+                    <state key="normal" image="cloud_cancle"/>
+                    <connections>
+                        <action selector="cancleAction:" destination="iN0-l3-epB" eventType="touchUpInside" id="MHR-ak-SfF"/>
+                    </connections>
+                </button>
+            </subviews>
+            <color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.52000000000000002" colorSpace="custom" customColorSpace="sRGB"/>
+            <constraints>
+                <constraint firstItem="hfV-D9-4RA" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" constant="33" id="5jd-PQ-73w"/>
+                <constraint firstItem="q0f-Z3-hFt" firstAttribute="leading" secondItem="hfV-D9-4RA" secondAttribute="trailing" id="6p1-95-Who"/>
+                <constraint firstItem="q0f-Z3-hFt" firstAttribute="centerY" secondItem="hfV-D9-4RA" secondAttribute="top" id="AdV-NL-x1c"/>
+                <constraint firstItem="hfV-D9-4RA" firstAttribute="centerX" secondItem="iN0-l3-epB" secondAttribute="centerX" id="Tyq-ZP-0uJ"/>
+                <constraint firstItem="hfV-D9-4RA" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="centerY" id="biw-7T-hGm"/>
+                <constraint firstAttribute="bottom" secondItem="hfV-D9-4RA" secondAttribute="bottom" constant="33" id="pMc-ew-3II"/>
+            </constraints>
+            <nil key="simulatedBottomBarMetrics"/>
+            <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
+            <connections>
+                <outlet property="baseView" destination="pPQ-tv-M04" id="g36-p2-WKr"/>
+                <outlet property="judgeButton" destination="a7T-m6-cO3" id="iNa-m8-fej"/>
+                <outlet property="lineView" destination="C3c-hV-10p" id="feE-N6-23a"/>
+                <outlet property="lineViewLeft" destination="vrQ-K4-VVb" id="6zN-xC-hNi"/>
+                <outlet property="settingButton" destination="p8P-5R-8Rj" id="TAu-gf-cnz"/>
+            </connections>
+            <point key="canvasLocation" x="542.75362318840587" y="-44.866071428571423"/>
+        </view>
+    </objects>
+    <resources>
+        <image name="cloud_cancle" width="26" height="26"/>
+        <systemColor name="systemBackgroundColor">
+            <color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+        </systemColor>
+    </resources>
+</document>

+ 36 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/SettingView/SettingPageView.h

@@ -0,0 +1,36 @@
+//
+//  SettingPageView.h
+//  MidiPlayer
+//
+//  Created by Kyle on 2021/12/7.
+//
+
+#import <UIKit/UIKit.h>
+#import "KSCloudSettingView.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+typedef void(^SettingCallback)(CLOUDSETTING_TYPE type, BOOL isOn);
+
+@interface SettingPageView : UIView
+
+@property (nonatomic, assign) BOOL eyeProtect;   // 护眼模式
+
+@property (nonatomic, assign) BOOL soundCheck;   // 校音提醒
+
+@property (nonatomic, assign) BOOL cameraOn;     // 摄像头开关
+
+@property (nonatomic, assign) BOOL repeatOn;     // 循环播放
+
+@property (nonatomic, assign) BOOL showFinger;   // 显示指法
+
+
++ (instancetype)shareInstance;
+
+- (void)querySettingConfig;
+
+- (void)settingCallback:(SettingCallback)callback;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 155 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/SettingView/SettingPageView.m

@@ -0,0 +1,155 @@
+//
+//  SettingPageView.m
+//  MidiPlayer
+//
+//  Created by Kyle on 2021/12/7.
+//
+
+#import "SettingPageView.h"
+
+@interface SettingPageView ()
+
+@property (weak, nonatomic) IBOutlet UIButton *eyeButton;
+
+@property (weak, nonatomic) IBOutlet UIButton *judgeButton;
+
+@property (weak, nonatomic) IBOutlet UIButton *cameraButton;
+
+@property (weak, nonatomic) IBOutlet UIButton *repeatButton;
+
+@property (weak, nonatomic) IBOutlet UIButton *fingerButton;
+
+@property (nonatomic, copy) SettingCallback callback;
+
+@property (nonatomic, strong) NSMutableDictionary *configDict;
+
+@end
+
+@implementation SettingPageView
+
++ (instancetype)shareInstance {
+    SettingPageView *view = [[[NSBundle mainBundle] loadNibNamed:@"SettingPageView" owner:nil options:nil] firstObject];
+    [view querySettingConfig];
+    return view;
+}
+- (void)defaultSetting {
+    self.eyeProtect = NO;
+    self.soundCheck = YES;
+    self.cameraOn = NO;
+    self.repeatOn = YES;
+    self.showFinger = YES;
+}
+
+- (void)querySettingConfig {
+    [self defaultSetting];
+    // 根据储存的配置项显示
+    NSDictionary *config = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"cloudConfig"];
+    self.configDict = [NSMutableDictionary dictionaryWithDictionary:config];
+    
+    self.eyeProtect = [[config valueForKey:@"eyeshield"] boolValue];
+    self.soundCheck = [[config valueForKey:@"soundCheck"] boolValue];
+    self.cameraOn = [[config valueForKey:@"cameraOn"] boolValue];
+    self.repeatOn = [[config valueForKey:@"repeatOn"] boolValue];
+    self.showFinger = [[config valueForKey:@"showFinger"] boolValue];
+    
+}
+
+- (void)synchronizeConfig {
+    [[NSUserDefaults standardUserDefaults] setObject:self.configDict forKey:@"cloudConfig"];
+    [[NSUserDefaults standardUserDefaults] synchronize];
+}
+
+- (void)settingCallback:(SettingCallback)callback {
+    if (callback) {
+        self.callback = callback;
+    }
+}
+#pragma mark ---- button action
+// 护眼模式
+- (IBAction)settingAction:(id)sender {
+    self.eyeProtect = !self.eyeProtect;
+    [self.configDict setValue:@(self.eyeProtect) forKey:@"eyeshield"];
+    [self synchronizeConfig];
+    if (self.callback) {
+        self.callback(CLOUDSETTING_TYPE_EYESHIELD, self.eyeProtect);
+    }
+}
+
+- (IBAction)judgeAction:(id)sender {
+    self.soundCheck = !self.soundCheck;
+    [self.configDict setValue:@(self.soundCheck) forKey:@"soundCheck"];
+    [self synchronizeConfig];
+    if (self.callback) {
+        self.callback(CLOUDSETTING_TYPE_SOUNDCHECK, self.soundCheck);
+    }
+}
+
+- (IBAction)cameraAction:(id)sender {
+    self.cameraOn = !self.cameraOn;
+    [self.configDict setValue:@(self.cameraOn) forKey:@"cameraOn"];
+    [self synchronizeConfig];
+    if (self.callback) {
+        self.callback(CLOUDSETTING_TYPE_CAMERA, self.cameraOn);
+    }
+}
+
+- (IBAction)repeatAction:(id)sender {
+    self.repeatOn = !self.repeatOn;
+    [self.configDict setValue:@(self.repeatOn) forKey:@"repeatOn"];
+    [self synchronizeConfig];
+    if (self.callback) {
+        self.callback(CLOUDSETTING_TYPE_REPEAT, self.repeatOn);
+    }
+}
+- (IBAction)showFingerAction:(id)sender {
+    self.showFinger = !self.showFinger;
+    [self.configDict setValue:@(self.showFinger) forKey:@"showFinger"];
+    [self synchronizeConfig];
+    if (self.callback) {
+        self.callback(CLOUDSETTING_TYPE_FINGER, self.showFinger);
+    }
+}
+
+#pragma mark ----  setter
+- (void)setEyeProtect:(BOOL)eyeProtect {
+    _eyeProtect = eyeProtect;
+    NSString *imageName = eyeProtect ? @"switch_on" : @"switch_off";
+    [self.eyeButton setImage:[UIImage imageNamed:imageName] forState:UIControlStateNormal];
+    
+}
+
+- (void)setSoundCheck:(BOOL)soundCheck {
+    _soundCheck = soundCheck;
+    NSString *imageName = soundCheck ? @"switch_on" : @"switch_off";
+    [self.judgeButton setImage:[UIImage imageNamed:imageName] forState:UIControlStateNormal];
+}
+
+- (void)setCameraOn:(BOOL)cameraOn {
+    _cameraOn = cameraOn;
+    NSString *imageName = cameraOn ? @"switch_on" : @"switch_off";
+    [self.cameraButton setImage:[UIImage imageNamed:imageName] forState:UIControlStateNormal];
+    
+}
+
+- (void)setRepeatOn:(BOOL)repeatOn {
+    _repeatOn = repeatOn;
+    NSString *imageName = repeatOn ? @"switch_on" : @"switch_off";
+    [self.repeatButton setImage:[UIImage imageNamed:imageName] forState:UIControlStateNormal];
+    
+}
+- (void)setShowFinger:(BOOL)showFinger {
+    _showFinger = showFinger;
+    NSString *imageName = showFinger ? @"switch_on" : @"switch_off";
+    [self.fingerButton setImage:[UIImage imageNamed:imageName] forState:UIControlStateNormal];
+    
+}
+/*
+// Only override drawRect: if you perform custom drawing.
+// An empty implementation adversely affects performance during animation.
+- (void)drawRect:(CGRect)rect {
+    // Drawing code
+}
+*/
+
+
+@end

+ 190 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/SettingView/SettingPageView.xib

@@ -0,0 +1,190 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
+    <device id="retina6_1" orientation="portrait" appearance="light"/>
+    <dependencies>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/>
+        <capability name="System colors in document resources" minToolsVersion="11.0"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <objects>
+        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
+        <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
+        <view contentMode="scaleToFill" id="iN0-l3-epB" customClass="SettingPageView">
+            <rect key="frame" x="0.0" y="0.0" width="414" height="339"/>
+            <autoresizingMask key="autoresizingMask"/>
+            <subviews>
+                <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="TPz-Lt-ID7">
+                    <rect key="frame" x="0.0" y="0.0" width="414" height="68"/>
+                    <subviews>
+                        <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="护眼模式" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hjM-RO-Jsw">
+                            <rect key="frame" x="25" y="25" width="62" height="18"/>
+                            <fontDescription key="fontDescription" type="system" pointSize="15"/>
+                            <color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                            <nil key="highlightedColor"/>
+                        </label>
+                        <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="md8-79-tRj">
+                            <rect key="frame" x="342" y="0.0" width="47" height="68"/>
+                            <state key="normal" image="switch_off"/>
+                            <connections>
+                                <action selector="settingAction:" destination="iN0-l3-epB" eventType="touchUpInside" id="DLH-pG-G3y"/>
+                            </connections>
+                        </button>
+                    </subviews>
+                    <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+                    <constraints>
+                        <constraint firstAttribute="bottom" secondItem="md8-79-tRj" secondAttribute="bottom" id="atg-yD-lBK"/>
+                        <constraint firstItem="hjM-RO-Jsw" firstAttribute="leading" secondItem="TPz-Lt-ID7" secondAttribute="leading" constant="25" id="cTH-K0-35d"/>
+                        <constraint firstAttribute="trailing" secondItem="md8-79-tRj" secondAttribute="trailing" constant="25" id="jNX-dW-Dfb"/>
+                        <constraint firstItem="hjM-RO-Jsw" firstAttribute="centerY" secondItem="TPz-Lt-ID7" secondAttribute="centerY" id="nAZ-Pi-kF0"/>
+                        <constraint firstItem="md8-79-tRj" firstAttribute="top" secondItem="TPz-Lt-ID7" secondAttribute="top" id="r0k-ZD-CFS"/>
+                    </constraints>
+                </view>
+                <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="wq8-rv-zdW">
+                    <rect key="frame" x="0.0" y="68" width="414" height="67.5"/>
+                    <subviews>
+                        <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="校音提醒" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="XQy-R8-0mY">
+                            <rect key="frame" x="25" y="24.5" width="62" height="18"/>
+                            <fontDescription key="fontDescription" type="system" pointSize="15"/>
+                            <color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                            <nil key="highlightedColor"/>
+                        </label>
+                        <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="3ZL-a9-mCu">
+                            <rect key="frame" x="342" y="0.0" width="47" height="67.5"/>
+                            <state key="normal" image="switch_off"/>
+                            <connections>
+                                <action selector="judgeAction:" destination="iN0-l3-epB" eventType="touchUpInside" id="ZHW-it-NZn"/>
+                            </connections>
+                        </button>
+                    </subviews>
+                    <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+                    <constraints>
+                        <constraint firstItem="3ZL-a9-mCu" firstAttribute="top" secondItem="wq8-rv-zdW" secondAttribute="top" id="1q5-mT-g66"/>
+                        <constraint firstAttribute="trailing" secondItem="3ZL-a9-mCu" secondAttribute="trailing" constant="25" id="Nbd-3b-VGJ"/>
+                        <constraint firstAttribute="bottom" secondItem="3ZL-a9-mCu" secondAttribute="bottom" id="YOl-mQ-kEz"/>
+                        <constraint firstItem="XQy-R8-0mY" firstAttribute="centerY" secondItem="wq8-rv-zdW" secondAttribute="centerY" id="aIc-ve-n5V"/>
+                        <constraint firstItem="XQy-R8-0mY" firstAttribute="leading" secondItem="wq8-rv-zdW" secondAttribute="leading" constant="25" id="zVu-mo-rhz"/>
+                    </constraints>
+                </view>
+                <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="z67-Ah-wa8">
+                    <rect key="frame" x="0.0" y="135.5" width="414" height="68"/>
+                    <subviews>
+                        <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="摄像头" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="HW4-ck-jy7">
+                            <rect key="frame" x="25" y="25" width="46" height="18"/>
+                            <fontDescription key="fontDescription" type="system" pointSize="15"/>
+                            <color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                            <nil key="highlightedColor"/>
+                        </label>
+                        <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="7rE-UU-UnI">
+                            <rect key="frame" x="342" y="0.0" width="47" height="68"/>
+                            <state key="normal" image="switch_off"/>
+                            <connections>
+                                <action selector="cameraAction:" destination="iN0-l3-epB" eventType="touchUpInside" id="lHw-xs-DUp"/>
+                            </connections>
+                        </button>
+                    </subviews>
+                    <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+                    <constraints>
+                        <constraint firstAttribute="trailing" secondItem="7rE-UU-UnI" secondAttribute="trailing" constant="25" id="1Y2-MB-VhC"/>
+                        <constraint firstItem="HW4-ck-jy7" firstAttribute="leading" secondItem="z67-Ah-wa8" secondAttribute="leading" constant="25" id="3Cb-e9-Nij"/>
+                        <constraint firstAttribute="bottom" secondItem="7rE-UU-UnI" secondAttribute="bottom" id="9i1-IH-YVS"/>
+                        <constraint firstItem="7rE-UU-UnI" firstAttribute="top" secondItem="z67-Ah-wa8" secondAttribute="top" id="Kgw-at-gJp"/>
+                        <constraint firstItem="HW4-ck-jy7" firstAttribute="centerY" secondItem="z67-Ah-wa8" secondAttribute="centerY" id="mBn-qQ-Prm"/>
+                    </constraints>
+                </view>
+                <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="zDJ-Vf-KV4">
+                    <rect key="frame" x="0.0" y="203.5" width="414" height="67.5"/>
+                    <subviews>
+                        <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="循环播放" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Gnw-gE-pov">
+                            <rect key="frame" x="25" y="25" width="61.5" height="18"/>
+                            <fontDescription key="fontDescription" type="system" pointSize="15"/>
+                            <color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                            <nil key="highlightedColor"/>
+                        </label>
+                        <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="pfs-HS-b09">
+                            <rect key="frame" x="342" y="0.0" width="47" height="67.5"/>
+                            <state key="normal" image="switch_off"/>
+                            <connections>
+                                <action selector="repeatAction:" destination="iN0-l3-epB" eventType="touchUpInside" id="4HJ-j5-F3s"/>
+                            </connections>
+                        </button>
+                    </subviews>
+                    <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+                    <constraints>
+                        <constraint firstItem="pfs-HS-b09" firstAttribute="top" secondItem="zDJ-Vf-KV4" secondAttribute="top" id="4Aj-F1-5Xb"/>
+                        <constraint firstAttribute="trailing" secondItem="pfs-HS-b09" secondAttribute="trailing" constant="25" id="E64-KK-WZD"/>
+                        <constraint firstItem="Gnw-gE-pov" firstAttribute="leading" secondItem="zDJ-Vf-KV4" secondAttribute="leading" constant="25" id="JpA-ig-FAM"/>
+                        <constraint firstAttribute="bottom" secondItem="pfs-HS-b09" secondAttribute="bottom" id="kH0-d7-2UN"/>
+                        <constraint firstItem="Gnw-gE-pov" firstAttribute="centerY" secondItem="zDJ-Vf-KV4" secondAttribute="centerY" id="sJp-ZW-jHQ"/>
+                    </constraints>
+                </view>
+                <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="V10-3p-7eW">
+                    <rect key="frame" x="0.0" y="271" width="414" height="68"/>
+                    <subviews>
+                        <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="显示指法" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="kKb-ZC-FIu">
+                            <rect key="frame" x="25" y="25" width="61.5" height="18"/>
+                            <fontDescription key="fontDescription" type="system" pointSize="15"/>
+                            <color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                            <nil key="highlightedColor"/>
+                        </label>
+                        <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="gvo-y6-Ae5">
+                            <rect key="frame" x="342" y="0.0" width="47" height="68"/>
+                            <state key="normal" image="switch_off"/>
+                            <connections>
+                                <action selector="showFingerAction:" destination="iN0-l3-epB" eventType="touchUpInside" id="VBb-pJ-HhL"/>
+                            </connections>
+                        </button>
+                    </subviews>
+                    <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+                    <constraints>
+                        <constraint firstItem="gvo-y6-Ae5" firstAttribute="top" secondItem="V10-3p-7eW" secondAttribute="top" id="MXk-Nd-kGY"/>
+                        <constraint firstAttribute="trailing" secondItem="gvo-y6-Ae5" secondAttribute="trailing" constant="25" id="OAR-r7-irb"/>
+                        <constraint firstItem="kKb-ZC-FIu" firstAttribute="leading" secondItem="V10-3p-7eW" secondAttribute="leading" constant="25" id="bPa-kw-nNs"/>
+                        <constraint firstItem="kKb-ZC-FIu" firstAttribute="centerY" secondItem="V10-3p-7eW" secondAttribute="centerY" id="dO2-0S-Psh"/>
+                        <constraint firstAttribute="bottom" secondItem="gvo-y6-Ae5" secondAttribute="bottom" id="uxT-ep-RPh"/>
+                    </constraints>
+                </view>
+            </subviews>
+            <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+            <constraints>
+                <constraint firstAttribute="trailing" secondItem="wq8-rv-zdW" secondAttribute="trailing" id="6wM-KH-uQK"/>
+                <constraint firstItem="zDJ-Vf-KV4" firstAttribute="height" secondItem="TPz-Lt-ID7" secondAttribute="height" id="Bhi-bK-WDf"/>
+                <constraint firstAttribute="bottom" secondItem="V10-3p-7eW" secondAttribute="bottom" id="CxM-Pd-ssC"/>
+                <constraint firstItem="wq8-rv-zdW" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="Erw-uP-QV5"/>
+                <constraint firstItem="TPz-Lt-ID7" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="HI5-MG-dPY"/>
+                <constraint firstItem="z67-Ah-wa8" firstAttribute="height" secondItem="TPz-Lt-ID7" secondAttribute="height" id="ILl-Ih-a4Q"/>
+                <constraint firstAttribute="trailing" secondItem="zDJ-Vf-KV4" secondAttribute="trailing" id="Irg-ee-Bmh"/>
+                <constraint firstItem="zDJ-Vf-KV4" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="KYH-Mi-Qp3"/>
+                <constraint firstAttribute="trailing" secondItem="TPz-Lt-ID7" secondAttribute="trailing" id="SJw-JV-J1f"/>
+                <constraint firstItem="V10-3p-7eW" firstAttribute="height" secondItem="TPz-Lt-ID7" secondAttribute="height" id="Uzr-qa-gr5"/>
+                <constraint firstItem="V10-3p-7eW" firstAttribute="top" secondItem="zDJ-Vf-KV4" secondAttribute="bottom" id="YLv-Yy-M9a"/>
+                <constraint firstItem="wq8-rv-zdW" firstAttribute="top" secondItem="TPz-Lt-ID7" secondAttribute="bottom" id="fhn-gJ-GfI"/>
+                <constraint firstItem="z67-Ah-wa8" firstAttribute="top" secondItem="wq8-rv-zdW" secondAttribute="bottom" id="glK-Rc-hfc"/>
+                <constraint firstAttribute="trailing" secondItem="V10-3p-7eW" secondAttribute="trailing" id="hal-ff-Oaq"/>
+                <constraint firstItem="wq8-rv-zdW" firstAttribute="height" secondItem="TPz-Lt-ID7" secondAttribute="height" id="nJP-su-1KL"/>
+                <constraint firstItem="z67-Ah-wa8" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="uL5-Ms-8Sr"/>
+                <constraint firstItem="TPz-Lt-ID7" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="uTR-sy-eZx"/>
+                <constraint firstAttribute="trailing" secondItem="z67-Ah-wa8" secondAttribute="trailing" id="ugX-F6-s7a"/>
+                <constraint firstItem="zDJ-Vf-KV4" firstAttribute="top" secondItem="z67-Ah-wa8" secondAttribute="bottom" id="vFX-hG-wz7"/>
+                <constraint firstItem="V10-3p-7eW" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="xYU-9u-VUc"/>
+                <constraint firstAttribute="bottom" secondItem="V10-3p-7eW" secondAttribute="bottom" id="yCa-rq-sI7"/>
+            </constraints>
+            <nil key="simulatedTopBarMetrics"/>
+            <nil key="simulatedBottomBarMetrics"/>
+            <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
+            <connections>
+                <outlet property="cameraButton" destination="7rE-UU-UnI" id="4Ma-2q-NLS"/>
+                <outlet property="eyeButton" destination="md8-79-tRj" id="jRV-jI-RIg"/>
+                <outlet property="fingerButton" destination="gvo-y6-Ae5" id="guC-Hj-VS6"/>
+                <outlet property="judgeButton" destination="3ZL-a9-mCu" id="Cn6-tM-eEY"/>
+                <outlet property="repeatButton" destination="pfs-HS-b09" id="qeg-gI-XyC"/>
+            </connections>
+            <point key="canvasLocation" x="76.811594202898561" y="37.165178571428569"/>
+        </view>
+    </objects>
+    <resources>
+        <image name="switch_off" width="47" height="24"/>
+        <systemColor name="systemBackgroundColor">
+            <color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+        </systemColor>
+    </resources>
+</document>

+ 43 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/SoundCheckView/SoundCheckView.h

@@ -0,0 +1,43 @@
+//
+//  SoundCheckView.h
+//  StudentDaya
+//
+//  Created by Kyle on 2021/12/13.
+//  Copyright © 2021 DayaMusic. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+typedef NS_ENUM(NSInteger, CLOSEACTION) {
+    CLOSEACTION_SKIP,
+    CLOSEACTION_CLOSECHECK,
+    CLOSEACTION_SUCCESS,
+};
+
+typedef NS_ENUM(NSInteger, LIGHTDISPLAY) {
+    LIGHTDISPLAY_LOW,   // 频率低了
+    LIGHTDISPLAY_RIGHT, // 频率正常
+    LIGHTDISPLAY_HIGH,  // 频率高了
+};
+
+typedef void(^SoundCheckCallback)(CLOSEACTION action);
+
+@interface SoundCheckView : UIView
+
+@property (nonatomic, assign) NSInteger pitch;
+
+@property (nonatomic, copy) SoundCheckCallback callback;
+
++ (instancetype)shareInstance;
+
+- (void)configSoundCheckWithSubjectId:(NSString *)subjectId callback:(SoundCheckCallback)callback;
+
+- (void)showSuccessCheckEnd;
+
+- (void)showLightDisplay:(LIGHTDISPLAY)type;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 153 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/SoundCheckView/SoundCheckView.m

@@ -0,0 +1,153 @@
+//
+//  SoundCheckView.m
+//  StudentDaya
+//
+//  Created by Kyle on 2021/12/13.
+//  Copyright © 2021 DayaMusic. All rights reserved.
+//
+
+#import "SoundCheckView.h"
+#import "LFPopupMenu.h"
+
+@interface SoundCheckView ()
+
+@property (weak, nonatomic) IBOutlet UIImageView *leftDot;
+
+@property (weak, nonatomic) IBOutlet UIImageView *midDot;
+
+@property (weak, nonatomic) IBOutlet UIImageView *rightDot;
+
+
+@property (weak, nonatomic) IBOutlet UIImageView *staffImageView;
+
+
+@end
+
+@implementation SoundCheckView
+
++ (instancetype)shareInstance {
+    SoundCheckView *view = [[[NSBundle mainBundle] loadNibNamed:@"SoundCheckView" owner:nil options:nil] firstObject];
+    return view;
+}
+- (void)configSoundCheckWithSubjectId:(NSString *)subjectId callback:(SoundCheckCallback)callback {
+    if (callback) {
+        self.callback = callback;
+    }
+    [self configStaffImageWithSubjectId:subjectId];
+}
+
+- (void)configStaffImageWithSubjectId:(NSString *)subjectId {
+    NSString *imgName = @"";
+    if ([subjectId isEqualToString:@"12"] || [subjectId isEqualToString:@"4"]) { // 小号 || 单簧管
+        imgName = @"staff_C5";
+        self.pitch = 60;
+    }
+    else if ([subjectId isEqualToString:@"14"] || [subjectId isEqualToString:@"15"]) { // 长号 || 上低音号
+        imgName = @"staff_S";
+        self.pitch = 34;
+    }
+    else if ([subjectId isEqualToString:@"13"]) { // 圆号
+        imgName = @"staff_F";
+        self.pitch = 53;
+    }
+    else if ([subjectId isEqualToString:@"5"] || [subjectId isEqualToString:@"6"]) { // sax
+        imgName = @"staff_C5(C)";
+        self.pitch = 60;
+    }
+    else if ([subjectId isEqualToString:@"120"]) { // 竖笛
+        imgName = @"staff_A5";
+        self.pitch = 69;
+    }
+    else { // 剩余声部
+        imgName = @"staff_b-14";
+        self.pitch = 58;
+    }
+    [self.staffImageView setImage:[UIImage imageNamed:imgName]];
+}
+
+
+- (void)resetImage {
+    [self.leftDot setImage:[UIImage imageNamed:@"emptyDot"]];
+    [self.midDot setImage:[UIImage imageNamed:@"emptyDot"]];
+    [self.rightDot setImage:[UIImage imageNamed:@"emptyDot"]];
+}
+- (void)showLightDisplay:(LIGHTDISPLAY)type {
+    [self resetImage];
+    switch (type) {
+        case LIGHTDISPLAY_LOW:
+        {
+            [self.leftDot setImage:[UIImage imageNamed:@"sound_wrong"]];
+        }
+            break;
+        case LIGHTDISPLAY_RIGHT:
+        {
+            [self.midDot setImage:[UIImage imageNamed:@"sound_right"]];
+        }
+            break;
+        case LIGHTDISPLAY_HIGH:
+        {
+            [self.rightDot setImage:[UIImage imageNamed:@"sound_wrong"]];
+        }
+            break;
+        default:
+            break;
+    }
+}
+
+- (IBAction)cancleAction:(id)sender {
+    [self closeViewWithType:CLOSEACTION_SKIP];
+}
+
+- (IBAction)skipButtonAction:(UIButton *)sender {
+    [self showTipsView:sender];
+}
+
+- (void)showTipsView:(UIButton *)button {
+    MJWeakSelf;
+    LFPopupMenuItem *item1 = [LFPopupMenuItem createWithTitle:@"跳过本次" image:nil action:^{
+        // 跳过
+        [weakSelf closeViewWithType:CLOSEACTION_SKIP];
+    }];
+    LFPopupMenuItem *item2 = [LFPopupMenuItem createWithTitle:@"关闭校音" image:nil action:^{
+        // 关闭校音
+        [weakSelf closeViewWithType:CLOSEACTION_CLOSECHECK];
+    }];
+    
+    LFPopupMenu *menu = [[LFPopupMenu alloc] init];
+    menu.needBorder = YES;
+    menu.rowHeight = 40.0f;
+    menu.minWidth = 76;
+    menu.textColor = HexRGB(0x333333);
+    menu.textFont = [UIFont systemFontOfSize:12.0f weight:UIFontWeightMedium];
+    
+    [menu configWithItems:@[item1,item2]];
+    [menu showArrowToView:button];
+}
+
+- (void)closeViewWithType:(CLOSEACTION)type {
+    [self removeFromSuperview];
+    if (self.callback) {
+        self.callback(type);
+    }
+}
+
+- (void)successCheckCallback {
+    [self removeFromSuperview];
+    if (self.callback) {
+        self.callback(CLOSEACTION_SUCCESS);
+    }
+}
+
+- (void)showSuccessCheckEnd {
+    [self successCheckCallback];
+}
+
+/*
+// Only override drawRect: if you perform custom drawing.
+// An empty implementation adversely affects performance during animation.
+- (void)drawRect:(CGRect)rect {
+    // Drawing code
+}
+*/
+
+@end

+ 177 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/SoundCheckView/SoundCheckView.xib

@@ -0,0 +1,177 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
+    <device id="retina6_1" orientation="portrait" appearance="light"/>
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/>
+        <capability name="System colors in document resources" minToolsVersion="11.0"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <objects>
+        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
+        <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
+        <view contentMode="scaleToFill" id="iN0-l3-epB" customClass="SoundCheckView">
+            <rect key="frame" x="0.0" y="0.0" width="907" height="422"/>
+            <autoresizingMask key="autoresizingMask"/>
+            <subviews>
+                <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="redraw" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="soundCheck_bg" translatesAutoresizingMaskIntoConstraints="NO" id="DE8-bH-WR0">
+                    <rect key="frame" x="0.0" y="0.0" width="907" height="422"/>
+                </imageView>
+                <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Q7x-bJ-NJm">
+                    <rect key="frame" x="10" y="10" width="44" height="44"/>
+                    <constraints>
+                        <constraint firstAttribute="height" constant="44" id="8NW-Mp-yYf"/>
+                        <constraint firstAttribute="width" constant="44" id="a1E-FX-ymR"/>
+                    </constraints>
+                    <state key="normal" image="cloudback_white"/>
+                    <connections>
+                        <action selector="cancleAction:" destination="iN0-l3-epB" eventType="touchUpInside" id="r6I-rZ-iMK"/>
+                    </connections>
+                </button>
+                <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="cloud_music" translatesAutoresizingMaskIntoConstraints="NO" id="lv2-Oo-rwG">
+                    <rect key="frame" x="26" y="64" width="855" height="308"/>
+                    <constraints>
+                        <constraint firstAttribute="width" secondItem="lv2-Oo-rwG" secondAttribute="height" multiplier="286:103" id="czf-na-sj7"/>
+                    </constraints>
+                </imageView>
+                <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="FA0-6u-T0j">
+                    <rect key="frame" x="206.5" y="40" width="494" height="314"/>
+                    <subviews>
+                        <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="cloud_blackboard" translatesAutoresizingMaskIntoConstraints="NO" id="H1h-j5-1lu">
+                            <rect key="frame" x="0.0" y="0.0" width="494" height="314"/>
+                        </imageView>
+                        <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="OCq-9O-8fJ">
+                            <rect key="frame" x="172" y="227" width="150" height="50"/>
+                            <subviews>
+                                <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="emptyDot" translatesAutoresizingMaskIntoConstraints="NO" id="hdh-04-p1S">
+                                    <rect key="frame" x="15" y="10" width="30" height="30"/>
+                                </imageView>
+                                <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="emptyDot" translatesAutoresizingMaskIntoConstraints="NO" id="LE9-0Y-6d3">
+                                    <rect key="frame" x="60" y="10" width="30" height="30"/>
+                                    <constraints>
+                                        <constraint firstAttribute="width" constant="30" id="ldo-1V-e4N"/>
+                                        <constraint firstAttribute="height" constant="30" id="mFT-3S-Kpi"/>
+                                    </constraints>
+                                </imageView>
+                                <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="emptyDot" translatesAutoresizingMaskIntoConstraints="NO" id="0B9-od-SoC">
+                                    <rect key="frame" x="105" y="10" width="30" height="30"/>
+                                </imageView>
+                            </subviews>
+                            <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                            <constraints>
+                                <constraint firstItem="LE9-0Y-6d3" firstAttribute="centerY" secondItem="hdh-04-p1S" secondAttribute="centerY" id="3yP-Y6-H1v"/>
+                                <constraint firstItem="LE9-0Y-6d3" firstAttribute="leading" secondItem="hdh-04-p1S" secondAttribute="trailing" constant="15" id="8ac-fR-jUz"/>
+                                <constraint firstItem="LE9-0Y-6d3" firstAttribute="centerY" secondItem="OCq-9O-8fJ" secondAttribute="centerY" id="GwT-jl-9Ic"/>
+                                <constraint firstAttribute="width" constant="150" id="OUm-fX-CBP"/>
+                                <constraint firstAttribute="height" constant="50" id="VUq-ud-m3T"/>
+                                <constraint firstItem="LE9-0Y-6d3" firstAttribute="centerX" secondItem="OCq-9O-8fJ" secondAttribute="centerX" id="dlM-dP-Ta5"/>
+                                <constraint firstItem="0B9-od-SoC" firstAttribute="centerY" secondItem="hdh-04-p1S" secondAttribute="centerY" id="fx0-EP-KhH"/>
+                                <constraint firstItem="0B9-od-SoC" firstAttribute="leading" secondItem="LE9-0Y-6d3" secondAttribute="trailing" constant="15" id="sFv-Gb-Rk8"/>
+                            </constraints>
+                        </view>
+                        <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="staff_C" translatesAutoresizingMaskIntoConstraints="NO" id="dw5-YO-FzD">
+                            <rect key="frame" x="127" y="127" width="240" height="70"/>
+                            <constraints>
+                                <constraint firstAttribute="height" constant="70" id="KWY-Tw-37n"/>
+                                <constraint firstAttribute="width" constant="240" id="LKn-az-RPR"/>
+                            </constraints>
+                        </imageView>
+                        <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="请演奏直到中间绿灯亮起" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="cKL-Ai-eDg">
+                            <rect key="frame" x="168" y="197" width="158" height="20"/>
+                            <constraints>
+                                <constraint firstAttribute="height" constant="20" id="f3w-ar-xHb"/>
+                            </constraints>
+                            <fontDescription key="fontDescription" type="system" pointSize="14"/>
+                            <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                            <nil key="highlightedColor"/>
+                        </label>
+                    </subviews>
+                    <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                    <constraints>
+                        <constraint firstItem="dw5-YO-FzD" firstAttribute="centerX" secondItem="FA0-6u-T0j" secondAttribute="centerX" id="8TP-hN-kB8"/>
+                        <constraint firstAttribute="trailing" secondItem="H1h-j5-1lu" secondAttribute="trailing" id="Am4-Fd-A69"/>
+                        <constraint firstAttribute="width" secondItem="FA0-6u-T0j" secondAttribute="height" multiplier="494:314" id="Ok6-HV-AWa"/>
+                        <constraint firstItem="OCq-9O-8fJ" firstAttribute="centerX" secondItem="FA0-6u-T0j" secondAttribute="centerX" id="QpT-bj-z7Z"/>
+                        <constraint firstItem="H1h-j5-1lu" firstAttribute="leading" secondItem="FA0-6u-T0j" secondAttribute="leading" id="U0E-AZ-7LF"/>
+                        <constraint firstItem="H1h-j5-1lu" firstAttribute="top" secondItem="FA0-6u-T0j" secondAttribute="top" id="a9M-ya-ppz"/>
+                        <constraint firstAttribute="bottom" secondItem="H1h-j5-1lu" secondAttribute="bottom" id="evf-rJ-FT1"/>
+                        <constraint firstItem="OCq-9O-8fJ" firstAttribute="top" secondItem="cKL-Ai-eDg" secondAttribute="bottom" constant="10" id="gbJ-iC-jmF"/>
+                        <constraint firstItem="cKL-Ai-eDg" firstAttribute="centerX" secondItem="FA0-6u-T0j" secondAttribute="centerX" id="t6D-6o-Z0l"/>
+                        <constraint firstItem="cKL-Ai-eDg" firstAttribute="top" secondItem="dw5-YO-FzD" secondAttribute="bottom" id="teU-wM-41k"/>
+                    </constraints>
+                </view>
+                <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="check_headPhone" translatesAutoresizingMaskIntoConstraints="NO" id="YlK-H1-rfX">
+                    <rect key="frame" x="236.5" y="287" width="95" height="135"/>
+                    <constraints>
+                        <constraint firstAttribute="width" secondItem="YlK-H1-rfX" secondAttribute="height" multiplier="19:27" id="dm0-fU-LSm"/>
+                    </constraints>
+                </imageView>
+                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="跳过本次" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="8Dy-Po-zbc">
+                    <rect key="frame" x="785" y="24" width="74" height="22"/>
+                    <fontDescription key="fontDescription" type="system" weight="medium" pointSize="18"/>
+                    <nil key="textColor"/>
+                    <nil key="highlightedColor"/>
+                </label>
+                <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="down_arrow" translatesAutoresizingMaskIntoConstraints="NO" id="Gze-Pn-Alp">
+                    <rect key="frame" x="864" y="30" width="14" height="10"/>
+                </imageView>
+                <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="0Z7-6c-ASS">
+                    <rect key="frame" x="785" y="15" width="93" height="40"/>
+                    <constraints>
+                        <constraint firstAttribute="height" constant="40" id="uX4-r8-D35"/>
+                    </constraints>
+                    <connections>
+                        <action selector="skipButtonAction:" destination="iN0-l3-epB" eventType="touchUpInside" id="W3C-fk-Wbk"/>
+                    </connections>
+                </button>
+            </subviews>
+            <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+            <constraints>
+                <constraint firstItem="Gze-Pn-Alp" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" constant="30" id="3yW-2D-HEN"/>
+                <constraint firstAttribute="trailing" secondItem="lv2-Oo-rwG" secondAttribute="trailing" constant="26" id="6ma-Pu-Tw8"/>
+                <constraint firstItem="YlK-H1-rfX" firstAttribute="leading" secondItem="FA0-6u-T0j" secondAttribute="leading" constant="30" id="75t-zn-cHr"/>
+                <constraint firstItem="Gze-Pn-Alp" firstAttribute="leading" secondItem="8Dy-Po-zbc" secondAttribute="trailing" constant="5" id="8hF-04-ruI"/>
+                <constraint firstItem="Q7x-bJ-NJm" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="10" id="Dku-jN-vJj"/>
+                <constraint firstItem="Gze-Pn-Alp" firstAttribute="centerY" secondItem="0Z7-6c-ASS" secondAttribute="centerY" id="K5X-ql-gIC"/>
+                <constraint firstItem="0Z7-6c-ASS" firstAttribute="leading" secondItem="8Dy-Po-zbc" secondAttribute="leading" id="KHT-b9-YyP"/>
+                <constraint firstItem="lv2-Oo-rwG" firstAttribute="top" secondItem="Q7x-bJ-NJm" secondAttribute="bottom" constant="10" id="Mgh-1M-5db"/>
+                <constraint firstItem="FA0-6u-T0j" firstAttribute="centerX" secondItem="iN0-l3-epB" secondAttribute="centerX" id="Nly-GO-YZc"/>
+                <constraint firstItem="lv2-Oo-rwG" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="26" id="SVo-s8-PUO"/>
+                <constraint firstAttribute="trailing" secondItem="DE8-bH-WR0" secondAttribute="trailing" id="TAz-Gn-46Q"/>
+                <constraint firstItem="Gze-Pn-Alp" firstAttribute="trailing" secondItem="0Z7-6c-ASS" secondAttribute="trailing" id="Xt5-Z5-MkV"/>
+                <constraint firstItem="DE8-bH-WR0" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="aK9-i0-r7N"/>
+                <constraint firstAttribute="trailing" secondItem="Gze-Pn-Alp" secondAttribute="trailing" constant="29" id="bYk-qU-2I5"/>
+                <constraint firstItem="OCq-9O-8fJ" firstAttribute="top" secondItem="YlK-H1-rfX" secondAttribute="top" constant="-20" id="bvs-3l-u7h"/>
+                <constraint firstItem="DE8-bH-WR0" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="fFs-mc-dm4"/>
+                <constraint firstItem="FA0-6u-T0j" firstAttribute="top" secondItem="Q7x-bJ-NJm" secondAttribute="top" constant="30" id="feU-Hk-QNn"/>
+                <constraint firstAttribute="bottom" secondItem="DE8-bH-WR0" secondAttribute="bottom" id="jwC-i3-ICT"/>
+                <constraint firstItem="Gze-Pn-Alp" firstAttribute="centerY" secondItem="8Dy-Po-zbc" secondAttribute="centerY" id="k9w-ug-51F"/>
+                <constraint firstAttribute="bottom" secondItem="YlK-H1-rfX" secondAttribute="bottom" id="laE-no-0I1"/>
+                <constraint firstItem="Q7x-bJ-NJm" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" constant="10" id="n47-CT-3ga"/>
+            </constraints>
+            <nil key="simulatedTopBarMetrics"/>
+            <nil key="simulatedBottomBarMetrics"/>
+            <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
+            <connections>
+                <outlet property="leftDot" destination="hdh-04-p1S" id="Mz0-uF-8Nh"/>
+                <outlet property="midDot" destination="LE9-0Y-6d3" id="Wop-lD-frP"/>
+                <outlet property="rightDot" destination="0B9-od-SoC" id="GRI-oC-Mem"/>
+                <outlet property="staffImageView" destination="dw5-YO-FzD" id="g6B-dA-72c"/>
+            </connections>
+            <point key="canvasLocation" x="280.43478260869568" y="-74.330357142857139"/>
+        </view>
+    </objects>
+    <resources>
+        <image name="check_headPhone" width="95" height="135"/>
+        <image name="cloud_blackboard" width="494" height="314"/>
+        <image name="cloud_music" width="735" height="206"/>
+        <image name="cloudback_white" width="32" height="32"/>
+        <image name="down_arrow" width="14" height="10"/>
+        <image name="emptyDot" width="30" height="30"/>
+        <image name="soundCheck_bg" width="812" height="375"/>
+        <image name="staff_C" width="985.5" height="286.5"/>
+        <systemColor name="systemBackgroundColor">
+            <color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+        </systemColor>
+    </resources>
+</document>

+ 38 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/SpeedSlider/KSSliderView.h

@@ -0,0 +1,38 @@
+//
+//  KSSliderView.h
+//  StudentDaya
+//
+//  Created by Kyle on 2021/12/13.
+//  Copyright © 2021 DayaMusic. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@protocol KSSliderDelegate <NSObject>
+
+@optional
+
+- (void)rateChangeAction:(NSInteger)rate;
+- (void)beginSwip;
+- (void)endSwip;
+
+@end
+
+@interface KSSliderView : UIView
+
+@property (nonatomic, assign) BOOL isVertical;
+
+@property (strong, nonatomic) NSNumberFormatter *numberFormatter;
+@property (nonatomic, assign) CGFloat minValue;
+@property (nonatomic, assign) CGFloat maxValue;
+@property (nonatomic, strong) UIColor *minimumTrackTintColor;
+@property (nonatomic, strong) UIColor *maxmumTrackTintColor;
+@property (nonatomic, assign) NSInteger rateValue;
+
+@property (nonatomic, unsafe_unretained)id<KSSliderDelegate>delegate;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 154 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/SpeedSlider/KSSliderView.m

@@ -0,0 +1,154 @@
+//
+//  KSSliderView.m
+//  StudentDaya
+//
+//  Created by Kyle on 2021/12/13.
+//  Copyright © 2021 DayaMusic. All rights reserved.
+//
+
+#import "KSSliderView.h"
+#import "KSTrackingSlider.h"
+
+@interface KSSliderView ()<KSTrackingSliderDelegate>
+
+@property (nonatomic, strong) UIButton *addButton; // 加
+
+@property (nonatomic, strong) UIButton *subtractButton; // 减
+
+@property (nonatomic, strong) KSTrackingSlider *sliderBar;
+
+@end
+
+@implementation KSSliderView
+
+- (instancetype)initWithFrame:(CGRect)frame {
+    self = [super initWithFrame:frame];
+    if (self) {
+        [self setUpWithFrame:frame];
+    }
+    return self;
+}
+
+- (void)setUpWithFrame:(CGRect)frame {
+    self.layer.cornerRadius = 5.0f;
+    NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
+    [formatter setNumberStyle:NSNumberFormatterDecimalStyle];
+    [formatter setRoundingMode:NSNumberFormatterRoundHalfUp];
+    [formatter setMaximumFractionDigits:0];
+    [formatter setMinimumFractionDigits:0];
+    _numberFormatter = formatter;
+    [self addButtons];
+    [self addSubview:self.sliderBar];
+}
+
+- (void)setRateValue:(NSInteger)rateValue {
+    _rateValue = rateValue;
+    self.sliderBar.value = rateValue;
+    if (self.delegate && [self.delegate respondsToSelector:@selector(rateChangeAction:)]) {
+        [self.delegate rateChangeAction:rateValue];
+    }
+}
+
+- (void)addButtons {
+    [self addSubview:self.addButton];
+    [self addSubview:self.subtractButton];
+}
+
+
+#pragma mark --- lazying
+
+- (UIButton *)addButton {
+    if (!_addButton) {
+        _addButton = [UIButton buttonWithType:UIButtonTypeCustom];
+        _addButton.frame = CGRectMake(5, 10, 40, 40);
+        [_addButton setImage:[UIImage imageNamed:@"img_speed_add"] forState:UIControlStateNormal];
+        [_addButton addTarget:self action:@selector(addValueAction:) forControlEvents:UIControlEventTouchUpInside];
+    }
+    return _addButton;
+}
+
+
+- (void)addValueAction:(UIButton *)sender {
+    self.rateValue = self.rateValue + 1;
+}
+
+- (UIButton *)subtractButton {
+    if (!_subtractButton) {
+        _subtractButton = [UIButton buttonWithType:UIButtonTypeCustom];
+        _subtractButton.frame = CGRectMake(5, CGRectGetHeight(self.frame) - 40, 40, 40);
+        [_subtractButton setImage:[UIImage imageNamed:@"img_speed_reduce"] forState:UIControlStateNormal];
+        [_subtractButton addTarget:self action:@selector(subtractAction:) forControlEvents:UIControlEventTouchUpInside];
+    }
+    return _subtractButton;
+}
+
+- (void)subtractAction:(UIButton *)sender {
+    self.rateValue = self.rateValue - 1;
+}
+
+- (KSTrackingSlider *)sliderBar {
+    if (!_sliderBar) {
+        CGFloat width = 160.0f;
+        CGFloat height = 50.0f;
+        _sliderBar = [[KSTrackingSlider alloc] initWithFrame:CGRectMake(height / 2.0f - width / 2.0f, width / 2.0f + 50 - height / 2.0f , width, height)];
+        _sliderBar.tintColor = HexRGB(0x01c1b5);
+        _sliderBar.delegate = self;
+        _sliderBar.minimumValue = 45;
+        _sliderBar.maximumValue = 270;
+    }
+    return _sliderBar;
+}
+- (void)currentValueOfSlider:(KSTrackingSlider *)slider {
+    self.rateValue = [_numberFormatter stringFromNumber:@(slider.value)].integerValue;
+}
+
+- (void)beginSwipSlider:(KSTrackingSlider *)slider {
+    if (self.delegate && [self.delegate respondsToSelector:@selector(beginSwip)]) {
+        [self.delegate beginSwip];
+    }
+}
+
+- (void)endSwipSlider:(KSTrackingSlider *)slider {
+    if (self.delegate && [self.delegate respondsToSelector:@selector(endSwip)]) {
+        [self.delegate endSwip];
+    }
+}
+
+- (void)setIsVertical:(BOOL)isVertical{
+    _isVertical = isVertical;
+    if (_isVertical == YES) {
+        self.sliderBar.transform = CGAffineTransformMakeRotation(-M_PI_2);
+
+    }else{
+        
+    }
+}
+- (void)setNumberFormatter:(NSNumberFormatter *)numberFormatter
+{
+    _numberFormatter = numberFormatter;
+}
+- (void)setMinValue:(CGFloat)minValue{
+    _minValue = minValue;
+    _sliderBar.minimumValue = minValue;
+}
+- (void)setMaxValue:(CGFloat)maxValue{
+    _maxValue = maxValue;
+    _sliderBar.maximumValue = maxValue;
+}
+- (void)setMinimumTrackTintColor:(UIColor *)minimumTrackTintColor{
+    _minimumTrackTintColor = minimumTrackTintColor;
+    _sliderBar.minimumTrackTintColor = minimumTrackTintColor;
+}
+- (void)setMaxmumTrackTintColor:(UIColor *)maxmumTrackTintColor{
+    _maxmumTrackTintColor = maxmumTrackTintColor;
+    _sliderBar.maximumTrackTintColor = maxmumTrackTintColor;
+}
+/*
+// Only override drawRect: if you perform custom drawing.
+// An empty implementation adversely affects performance during animation.
+- (void)drawRect:(CGRect)rect {
+    // Drawing code
+}
+*/
+
+@end

+ 29 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/SpeedSlider/KSTrackingSlider.h

@@ -0,0 +1,29 @@
+//
+//  KSTrackingSlider.h
+//  StudentDaya
+//
+//  Created by Kyle on 2021/12/13.
+//  Copyright © 2021 DayaMusic. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+@class KSTrackingSlider;
+
+@protocol KSTrackingSliderDelegate <NSObject>
+
+- (void)currentValueOfSlider:(KSTrackingSlider *_Nonnull)slider;
+- (void)beginSwipSlider:(KSTrackingSlider *_Nonnull)slider;
+- (void)endSwipSlider:(KSTrackingSlider *_Nonnull)slider;
+
+@end
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface KSTrackingSlider : UISlider
+
+@property (nonatomic, unsafe_unretained) id <KSTrackingSliderDelegate> delegate;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 70 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/SpeedSlider/KSTrackingSlider.m

@@ -0,0 +1,70 @@
+//
+//  KSTrackingSlider.m
+//  StudentDaya
+//
+//  Created by Kyle on 2021/12/13.
+//  Copyright © 2021 DayaMusic. All rights reserved.
+//
+
+#import "KSTrackingSlider.h"
+
+@implementation KSTrackingSlider
+
+- (instancetype)initWithFrame:(CGRect)frame {
+    self = [super initWithFrame:frame];
+    if (self) {
+        
+    }
+    return self;
+}
+
+#pragma mark --- subClassed
+
+- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
+    BOOL begin = [super beginTrackingWithTouch:touch withEvent:event];
+    if (begin) {
+        if ([self.delegate respondsToSelector:@selector(currentValueOfSlider:)]) {
+            [self.delegate currentValueOfSlider:self];
+        }
+        if ([self.delegate respondsToSelector:@selector(beginSwipSlider:)]) {
+            [self.delegate beginSwipSlider:self];
+        }
+    }
+    return begin;
+}
+
+- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
+{
+    BOOL continueTrack = [super continueTrackingWithTouch:touch withEvent:event];
+    if (continueTrack) {
+        if ([self.delegate respondsToSelector:@selector(currentValueOfSlider:)]) {
+            [self.delegate currentValueOfSlider:self];
+        }
+    }
+    return continueTrack;
+}
+- (void)cancelTrackingWithEvent:(UIEvent *)event
+{
+    [super cancelTrackingWithEvent:event];
+}
+
+- (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
+{
+    [super endTrackingWithTouch:touch withEvent:event];
+    if ([self.delegate respondsToSelector:@selector(currentValueOfSlider:)]) {
+        [self.delegate currentValueOfSlider:self];
+    }
+    if ([self.delegate respondsToSelector:@selector(endSwipSlider:)]) {
+        [self.delegate endSwipSlider:self];
+    }
+}
+
+/*
+// Only override drawRect: if you perform custom drawing.
+// An empty implementation adversely affects performance during animation.
+- (void)drawRect:(CGRect)rect {
+    // Drawing code
+}
+*/
+
+@end

+ 25 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/StaffView/CloudControlButton.h

@@ -0,0 +1,25 @@
+//
+//  CloudControlButton.h
+//  MidiPlayer
+//
+//  Created by Kyle on 2021/12/6.
+//
+
+#import <UIKit/UIKit.h>
+
+NS_ASSUME_NONNULL_BEGIN
+@class CloudControlButton;
+
+typedef void(^ControlAction)(CloudControlButton *sender, BOOL isSelected);
+
+@interface CloudControlButton : UIView
+
+@property (nonatomic, assign) BOOL isSelected;
+
++ (instancetype)createButtonWithImage:(NSString *)imageName selectImage:(NSString *)selectImage buttonTitle:(NSString *)buttonTitle selectTitle:(NSString *)selectTitle tag:(NSInteger)tag frame:(CGRect)frame callback:(ControlAction)callback;
+
+- (void)showSpeed:(NSInteger)speed;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 132 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/StaffView/CloudControlButton.m

@@ -0,0 +1,132 @@
+//
+//  CloudControlButton.m
+//  MidiPlayer
+//
+//  Created by Kyle on 2021/12/6.
+//
+
+#import "CloudControlButton.h"
+
+@interface CloudControlButton ()
+
+@property (nonatomic, strong) UILabel *titleLabel;
+
+@property (nonatomic, strong) UIButton *actionButton;
+
+@property (nonatomic, copy) ControlAction callback;
+
+@property (nonatomic, strong) NSString *buttonTitle;
+
+@property (nonatomic, strong) NSString *selectTitle;
+
+@property (nonatomic, strong) UILabel *speedLabel;
+@end
+
+@implementation CloudControlButton
+
++ (instancetype)createButtonWithImage:(NSString *)imageName selectImage:(NSString *)selectImage buttonTitle:(NSString *)buttonTitle selectTitle:(NSString *)selectTitle tag:(NSInteger)tag frame:(CGRect)frame callback:(ControlAction)callback {
+    
+    CloudControlButton *controlButton = [[CloudControlButton alloc] initWithFrame:frame];
+    controlButton.tag = tag;
+    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
+    button.backgroundColor = [UIColor clearColor];
+    button.frame = CGRectMake(0, 0, frame.size.width, frame.size.width);
+    [button addTarget:controlButton action:@selector(buttonClickAction:) forControlEvents:UIControlEventTouchUpInside];
+    [button setImage:[UIImage imageNamed:imageName] forState:UIControlStateNormal];
+    [button setImage:[UIImage imageNamed:selectImage] forState:UIControlStateSelected];
+    [controlButton addSubview:button];
+    
+    [controlButton addSubview:controlButton.titleLabel];
+    controlButton.titleLabel.frame = CGRectMake(0, CGRectGetMaxX(button.frame) + 2, frame.size.width, 24);
+    controlButton.actionButton = button;
+    controlButton.buttonTitle = buttonTitle;
+    controlButton.selectTitle = selectTitle;
+    
+    controlButton.isSelected = NO;
+    if (callback) {
+        controlButton.callback = callback;
+    }
+    
+    return controlButton;
+}
+
+- (void)showSpeed:(NSInteger)speed {
+    if (!_speedLabel) {
+        UIView *view = [[UIView alloc] initWithFrame:CGRectZero];
+        view.backgroundColor = UIColor.whiteColor;
+        view.layer.cornerRadius = 8.0f;
+        view.layer.borderColor = HexRGB(0x01c1b5).CGColor;
+        view.layer.borderWidth = 1.0f;
+        [view addSubview:self.speedLabel];
+        [self addSubview:view];
+        
+        [view mas_makeConstraints:^(MASConstraintMaker *make) {
+            make.left.mas_equalTo(self.actionButton.mas_right).offset(-8);
+            make.top.mas_equalTo(self.actionButton.mas_top);
+            make.height.mas_equalTo(16.0f);
+        }];
+        
+        [self.speedLabel mas_makeConstraints:^(MASConstraintMaker *make) {
+            make.left.mas_equalTo(view.mas_left).offset(5);
+            make.right.mas_equalTo(view.mas_right).offset(-5);
+            make.centerY.mas_equalTo(view.mas_centerY);
+        }];
+
+    }
+    else {
+        
+    }
+    self.speedLabel.text = [NSString stringWithFormat:@"%zd", speed];
+}
+
+- (void)buttonClickAction:(UIButton *)sender {
+    
+    self.isSelected = !self.isSelected;
+    if (self.callback) {
+        self.callback(self, self.isSelected);
+    }
+}
+
+#pragma mark ---- setter
+- (void)setIsSelected:(BOOL)isSelected {
+    
+    _isSelected = isSelected;
+    self.actionButton.selected = isSelected;
+    if (isSelected) {
+        self.titleLabel.text = self.selectTitle;
+    }
+    else {
+        self.titleLabel.text = self.buttonTitle;
+    }
+}
+#pragma mark ----- lazying
+- (UILabel *)titleLabel {
+    
+    if (!_titleLabel) {
+        _titleLabel = [[UILabel alloc] init];
+        _titleLabel.textColor = UIColor.whiteColor;
+        _titleLabel.font = [UIFont systemFontOfSize:12.0f weight:UIFontWeightMedium];
+        _titleLabel.textAlignment = NSTextAlignmentCenter;
+    }
+    return _titleLabel;
+}
+
+- (UILabel *)speedLabel {
+    if (!_speedLabel) {
+        _speedLabel = [[UILabel alloc] init];
+        [_speedLabel setFont:[UIFont systemFontOfSize:10.0f]];
+        _speedLabel.textColor = HexRGB(0x01c1b5);
+        _speedLabel.textAlignment = NSTextAlignmentCenter;
+    }
+    return _speedLabel;
+}
+
+/*
+// Only override drawRect: if you perform custom drawing.
+// An empty implementation adversely affects performance during animation.
+- (void)drawRect:(CGRect)rect {
+    // Drawing code
+}
+*/
+
+@end

+ 20 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/StaffView/ScoreAnimationView.h

@@ -0,0 +1,20 @@
+//
+//  ScoreAnimationView.h
+//  StudentDaya
+//
+//  Created by Kyle on 2021/12/10.
+//  Copyright © 2021 DayaMusic. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface ScoreAnimationView : UIView
+
+- (instancetype)initWithFrame:(CGRect)frame animitionDistance:(CGFloat)distance score:(NSInteger)score titleColor:(UIColor *)titleColor imageName:(NSString *)imageName;
+
+- (void)showInView:(UIView *)displayView;
+@end
+
+NS_ASSUME_NONNULL_END

+ 76 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/StaffView/ScoreAnimationView.m

@@ -0,0 +1,76 @@
+//
+//  ScoreAnimationView.m
+//  StudentDaya
+//
+//  Created by Kyle on 2021/12/10.
+//  Copyright © 2021 DayaMusic. All rights reserved.
+//
+
+#import "ScoreAnimationView.h"
+
+@interface ScoreAnimationView ()
+
+@property (nonatomic, assign) CGFloat distanceY;
+
+@property (nonatomic, assign) NSInteger score;
+
+@property (nonatomic, strong) UIColor *scoreColor;
+
+@property (nonatomic, strong) NSString *imgName;
+
+@end
+
+@implementation ScoreAnimationView
+
+
+- (instancetype)initWithFrame:(CGRect)frame animitionDistance:(CGFloat)distance score:(NSInteger)score titleColor:(UIColor *)titleColor imageName:(NSString *)imageName {
+    self = [super initWithFrame:frame];
+    if (self) {
+        self.distanceY = distance;
+        self.score = score;
+        self.scoreColor = titleColor;
+        self.imgName = imageName;
+        [self configView];
+    }
+    return self;
+}
+
+
+- (void)configView {
+    UIImage *image = [UIImage imageNamed:self.imgName];
+    CGFloat rate = image.size.width / image.size.height;
+    UIImageView *scoreImage = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, rate * 40, 40)];
+    [scoreImage setImage:image];
+    [self addSubview:scoreImage];
+    UILabel *scoreLabel = [[UILabel alloc] init];
+    scoreLabel.frame = CGRectMake(CGRectGetMaxX(scoreImage.frame), 0, 40, 40);
+    scoreLabel.textAlignment = NSTextAlignmentLeft;
+    [scoreLabel setText:[NSString stringWithFormat:@"%zd",self.score]];
+    [scoreLabel setTextColor:self.scoreColor];
+    [scoreLabel setFont:[UIFont systemFontOfSize:18.0f weight:UIFontWeightBold]];
+    [self addSubview:scoreLabel];
+    
+}
+
+- (void)showInView:(UIView *)displayView {
+    [displayView addSubview:self];
+    
+    [UIView animateWithDuration:0.5f animations:^{
+        CGRect frame = self.frame;
+        frame.origin.y -= (self.distanceY + frame.size.height);
+        self.frame = frame;
+    } completion:^(BOOL finished) {
+        [self removeFromSuperview];
+    }];
+}
+
+
+/*
+// Only override drawRect: if you perform custom drawing.
+// An empty implementation adversely affects performance during animation.
+- (void)drawRect:(CGRect)rect {
+    // Drawing code
+}
+*/
+
+@end

+ 51 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/StaffView/StaffImageDisplayView.h

@@ -0,0 +1,51 @@
+//
+//  StaffImageDisplayView.h
+//  MidiPlayer
+//
+//  Created by Kyle on 2021/12/2.
+//
+
+#import <UIKit/UIKit.h>
+
+
+@interface KSMeasureLocationModel : NSObject
+
+@property (nonatomic, assign) CGFloat positionX;
+
+@property (nonatomic, assign) CGFloat positionY;
+
+@property (nonatomic, assign) CGFloat measureWidth;
+
+@property (nonatomic, assign) CGFloat measureHeight;
+
+@property (nonatomic, assign) NSInteger pageIndex;  // 当前page
+
+@property (nonatomic, strong) UIColor * _Nonnull fillColor; // 填充颜色
+
+@end
+
+NS_ASSUME_NONNULL_BEGIN
+
+
+
+typedef void(^TapCallback)(CGFloat positionX, CGFloat positionY, NSInteger pageIndex);
+
+@interface StaffImageDisplayView : UIView
+
+@property (nonatomic, assign) NSInteger pageIndex;  // 当前page
+
+@property (nonatomic, strong) NSString *imageName;  // 图片名称
+
+- (instancetype)initWithImage:(NSString *)imageName pageIndex:(NSInteger)pageIndex frame:(CGRect)viewFrame;
+
+- (void)viewTapActionCallback:(TapCallback)callback;
+
+- (void)clearAllColorView;
+
+- (void)addColorView:(KSMeasureLocationModel *)model;
+
+- (void)drawColorView:(NSMutableArray *)viewLocatonArray;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 109 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/StaffView/StaffImageDisplayView.m

@@ -0,0 +1,109 @@
+//
+//  StaffImageDisplayView.m
+//  MidiPlayer
+//
+//  Created by Kyle on 2021/12/2.
+//
+
+#import "StaffImageDisplayView.h"
+
+@interface KSColorView : UIView
+
+@end
+
+@implementation KSColorView
+
+
+@end
+
+
+@implementation KSMeasureLocationModel
+
+@end
+
+
+@interface StaffImageDisplayView ()
+
+@property (nonatomic, strong) UIView *bgView;
+
+@property (nonatomic, strong) UIImageView *imageView;
+
+@property (nonatomic, copy) TapCallback callback;
+
+@end
+
+@implementation StaffImageDisplayView
+
+- (instancetype)initWithImage:(NSString *)imageName pageIndex:(NSInteger)pageIndex frame:(CGRect)viewFrame {
+    if (self = [super initWithFrame:viewFrame]) {
+        self.bgView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, viewFrame.size.width, viewFrame.size.height)];
+        self.bgView.backgroundColor = [UIColor clearColor];
+        [self addSubview:self.bgView];
+        
+        self.imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, viewFrame.size.width, viewFrame.size.height)];
+        self.imageView.userInteractionEnabled = YES;
+        [self addSubview:self.imageView];
+        
+        self.imageName = imageName;
+        self.pageIndex = pageIndex;
+    }
+    return self;
+}
+
+
+- (void)viewTapActionCallback:(TapCallback)callback {
+    if (callback) {
+        self.callback = callback;
+    }
+}
+
+- (void)clearAllColorView {
+    for (UIView *view in self.bgView.subviews) {
+        if ([view isKindOfClass:[KSColorView class]]) {
+            [view removeFromSuperview];
+        }
+    }
+}
+
+- (void)addColorView:(KSMeasureLocationModel *)model {
+    
+    KSColorView *colorView = [[KSColorView alloc] initWithFrame:CGRectMake(model.positionX, model.positionY, model.measureWidth, model.measureHeight)];
+    colorView.backgroundColor = model.fillColor;
+    [self.bgView addSubview:colorView];
+}
+
+- (void)drawColorView:(NSMutableArray *)viewLocatonArray {
+    for (KSMeasureLocationModel *model in viewLocatonArray) {
+        KSColorView *colorView = [[KSColorView alloc] initWithFrame:CGRectMake(model.positionX, model.positionY, model.measureWidth, model.measureHeight)];
+        colorView.backgroundColor = model.fillColor;
+        [self.bgView addSubview:colorView];
+    }
+}
+
+#pragma mark ------- setting
+- (void)setImageName:(NSString *)imageName {
+    _imageName = imageName;
+    [self.imageView setImage:[UIImage imageNamed:imageName]];
+}
+
+- (void)setPageIndex:(NSInteger)pageIndex {
+    _pageIndex = pageIndex;
+}
+
+- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
+    UITouch *touch = [touches anyObject];
+    CGPoint point = [touch locationInView:self];
+    if (self.callback) {
+        self.callback(point.x, point.y, self.pageIndex);
+    }
+}
+
+/*
+// Only override drawRect: if you perform custom drawing.
+// An empty implementation adversely affects performance during animation.
+- (void)drawRect:(CGRect)rect {
+    // Drawing code
+}
+*/
+
+@end

+ 39 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/TipsAlert/EvaluateResultAlert.h

@@ -0,0 +1,39 @@
+//
+//  EvaluateResultAlert.h
+//  StudentDaya
+//
+//  Created by Kyle on 2021/12/10.
+//  Copyright © 2021 DayaMusic. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+typedef NS_ENUM(NSInteger, EVALUATE_RESULT_ACTION) {
+    EVALUATE_RESULT_ACTION_CANCLE,  // 取消
+    EVALUATE_RESULT_ACTION_SUBMIT, // 上传到云端
+    EVALUATE_RESULT_ACTION_SHARE,  // 分享
+    EVALUATE_RESULT_ACTION_PRACTICE, // 练习
+    EVALUATE_RESULT_ACTION_TAYAGAIN, // 再试一次
+    EVALUATE_RESULT_ACTION_REPORT,   // 查看报告
+};
+
+typedef void(^EvaluateResultCallback)(EVALUATE_RESULT_ACTION action);
+
+@interface EvaluateResultAlert : UIView
+
++ (instancetype)shareInstance;
+
+/// 配置页面显示
+/// @param score 分数
+/// @param intonation 音准
+/// @param cadence 节奏
+/// @param integrity 完整度
+- (void)configWithScore:(NSInteger)score intonation:(NSInteger)intonation cadence:(NSInteger)cadence integrity:(NSInteger)integrity;
+
+- (void)resultCallback:(EvaluateResultCallback)callback;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 144 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/TipsAlert/EvaluateResultAlert.m

@@ -0,0 +1,144 @@
+//
+//  EvaluateResultAlert.m
+//  StudentDaya
+//
+//  Created by Kyle on 2021/12/10.
+//  Copyright © 2021 DayaMusic. All rights reserved.
+//
+
+#import "EvaluateResultAlert.h"
+
+@interface EvaluateResultAlert ()
+
+@property (weak, nonatomic) IBOutlet UILabel *scoreLabel;
+
+@property (weak, nonatomic) IBOutlet UILabel *scoreDesc;
+// 音准
+@property (weak, nonatomic) IBOutlet UIView *intonationView;
+@property (weak, nonatomic) IBOutlet UILabel *intonationScore;
+@property (weak, nonatomic) IBOutlet UIView *intonationValueView;
+@property (weak, nonatomic) IBOutlet NSLayoutConstraint *intonationWidth;
+
+// 节奏
+@property (weak, nonatomic) IBOutlet UIView *rhythmView;
+@property (weak, nonatomic) IBOutlet UILabel *rhythmLabel;
+@property (weak, nonatomic) IBOutlet UIView *rhyhtmVauleView;
+@property (weak, nonatomic) IBOutlet NSLayoutConstraint *rhythmWidth;
+
+// 完整性
+@property (weak, nonatomic) IBOutlet UIView *integrityView;
+@property (weak, nonatomic) IBOutlet UILabel *integrityLabel;
+@property (weak, nonatomic) IBOutlet UIView *integrityValueView;
+@property (weak, nonatomic) IBOutlet NSLayoutConstraint *integrityWidth;
+
+
+@property (weak, nonatomic) IBOutlet UILabel *reportDesc;
+
+@property (weak, nonatomic) IBOutlet UIView *lineView;
+
+@property (nonatomic, copy) EvaluateResultCallback callback;
+
+@property (nonatomic, assign) NSInteger score;
+
+@property (nonatomic, assign) NSInteger intonation;
+
+@property (nonatomic, assign) NSInteger cadence;
+
+@property (nonatomic, assign) NSInteger integrity;
+@end
+
+@implementation EvaluateResultAlert
+
++ (instancetype)shareInstance {
+    EvaluateResultAlert *view = [[[NSBundle mainBundle] loadNibNamed:@"EvaluateResultAlert" owner:nil options:nil] firstObject];
+    return view;
+}
+
+- (void)configWithScore:(NSInteger)score intonation:(NSInteger)intonation cadence:(NSInteger)cadence integrity:(NSInteger)integrity {
+    self.score = score;
+    self.intonation = intonation;
+    self.cadence = cadence;
+    self.integrity = integrity;
+}
+
+- (void)setScore:(NSInteger)score {
+    _score = score;
+    NSString *desc = @"";
+    NSString *reportMessage = @"";
+    if (score > 80) {
+        desc = @"你很棒";
+        reportMessage = @"你的演奏非常不错,音准的把握和节奏稍有瑕疵,完整性把握的很好~";
+    }
+    else if (score > 60) {
+        desc = @"展露头角";
+        reportMessage = @"你的演奏还不错,继续加油吧,加强音准,离完美就差一步啦~";
+    }
+    else if (score > 40) {
+        desc = @"突破自我";
+        reportMessage = @"你的演奏还不流畅,音准和节奏还需加强,科学的练习才能更完美哦~";
+    }
+    else if (score > 20) {
+        desc = @"还要加油哦~";
+        reportMessage = @"你的演奏还不熟练,音准和完整性还需加强,加紧训练才能有好成绩哦~";
+    }
+    else {
+        desc = @"敢于尝试";
+        reportMessage = @"你的演奏不太好,音准和完整性还需加强,再练一练吧~";
+    }
+    self.scoreLabel.text = [NSString stringWithFormat:@"%zd",score];
+    self.scoreDesc.text = desc;
+
+    NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
+    [paragraphStyle setLineSpacing:4];//调整行间距
+    NSMutableAttributedString *attrs = [[NSMutableAttributedString alloc] initWithString:reportMessage attributes:@{NSParagraphStyleAttributeName:paragraphStyle,NSFontAttributeName:[UIFont systemFontOfSize:14.0f],NSForegroundColorAttributeName:HexRGB(0x808080)}];
+    self.reportDesc.attributedText = attrs;
+    
+}
+
+- (void)setIntonation:(NSInteger)intonation {
+    _intonation = intonation;
+    self.intonationScore.text = [NSString stringWithFormat:@"%zd",intonation];
+    [UIView animateWithDuration:0.3f animations:^{
+        self.integrityWidth.constant = 180 * (intonation/100.0f);
+    }];
+}
+
+- (void)setCadence:(NSInteger)cadence {
+    _cadence = cadence;
+    self.rhythmLabel.text = [NSString stringWithFormat:@"%zd",cadence];
+    [UIView animateWithDuration:0.3f animations:^{
+        self.rhythmWidth.constant = 180 * (cadence/100.0f);
+    }];
+}
+
+- (void)setIntegrity:(NSInteger)integrity {
+    _integrity = integrity;
+    self.integrityLabel.text = [NSString stringWithFormat:@"%zd",integrity];
+    [UIView animateWithDuration:0.3f animations:^{
+        self.integrityWidth.constant = 180 * (integrity/100.0f);
+    }];
+}
+
+- (void)resultCallback:(EvaluateResultCallback)callback {
+    if (callback) {
+        self.callback = callback;
+    }
+}
+
+- (IBAction)buttonClickAction:(UIButton *)sender {
+    NSInteger index = sender.tag - 1000;
+    if (self.callback) {
+        self.callback(index);
+    }
+}
+
+
+/*
+// Only override drawRect: if you perform custom drawing.
+// An empty implementation adversely affects performance during animation.
+- (void)drawRect:(CGRect)rect {
+    // Drawing code
+}
+*/
+
+@end

+ 391 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/TipsAlert/EvaluateResultAlert.xib

@@ -0,0 +1,391 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
+    <device id="retina6_1" orientation="portrait" appearance="light"/>
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/>
+        <capability name="System colors in document resources" minToolsVersion="11.0"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <objects>
+        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
+        <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
+        <view contentMode="scaleToFill" id="iN0-l3-epB" customClass="EvaluateResultAlert">
+            <rect key="frame" x="0.0" y="0.0" width="736" height="454"/>
+            <autoresizingMask key="autoresizingMask"/>
+            <subviews>
+                <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="kev-3r-AV2">
+                    <rect key="frame" x="95.5" y="24" width="545" height="415"/>
+                    <subviews>
+                        <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="9ti-sU-Ve9">
+                            <rect key="frame" x="181.5" y="0.0" width="363.5" height="415"/>
+                            <subviews>
+                                <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="qHz-48-PaW">
+                                    <rect key="frame" x="18" y="26" width="4" height="18"/>
+                                    <color key="backgroundColor" red="0.0039215686269999999" green="0.75686274509999996" blue="0.70980392160000005" alpha="1" colorSpace="calibratedRGB"/>
+                                    <constraints>
+                                        <constraint firstAttribute="height" constant="18" id="DqZ-My-Ord"/>
+                                        <constraint firstAttribute="width" constant="4" id="hi7-S1-jpO"/>
+                                    </constraints>
+                                    <userDefinedRuntimeAttributes>
+                                        <userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
+                                            <real key="value" value="2"/>
+                                        </userDefinedRuntimeAttribute>
+                                    </userDefinedRuntimeAttributes>
+                                </view>
+                                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="智能评分" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="anF-LP-hlF">
+                                    <rect key="frame" x="30" y="24" width="74" height="22"/>
+                                    <fontDescription key="fontDescription" type="system" weight="medium" pointSize="18"/>
+                                    <color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
+                                    <nil key="highlightedColor"/>
+                                </label>
+                                <button opaque="NO" tag="1001" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="FA5-n2-FUc">
+                                    <rect key="frame" x="189.5" y="21" width="86" height="28"/>
+                                    <color key="backgroundColor" red="0.93725490196078431" green="0.93725490196078431" blue="0.93725490196078431" alpha="1" colorSpace="calibratedRGB"/>
+                                    <constraints>
+                                        <constraint firstAttribute="width" constant="86" id="Cww-TE-rnF"/>
+                                        <constraint firstAttribute="height" constant="28" id="fYt-SX-XQi"/>
+                                    </constraints>
+                                    <fontDescription key="fontDescription" type="system" weight="medium" pointSize="14"/>
+                                    <state key="normal" title="上传到云端">
+                                        <color key="titleColor" red="0.0039215686269999999" green="0.75686274509999996" blue="0.70980392160000005" alpha="1" colorSpace="calibratedRGB"/>
+                                    </state>
+                                    <userDefinedRuntimeAttributes>
+                                        <userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
+                                            <real key="value" value="14"/>
+                                        </userDefinedRuntimeAttribute>
+                                    </userDefinedRuntimeAttributes>
+                                    <connections>
+                                        <action selector="buttonClickAction:" destination="iN0-l3-epB" eventType="touchUpInside" id="r6c-Zo-YwX"/>
+                                    </connections>
+                                </button>
+                                <button opaque="NO" tag="1002" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="aFf-78-FUB">
+                                    <rect key="frame" x="283.5" y="21" width="66" height="28"/>
+                                    <color key="backgroundColor" red="0.93725490196078431" green="0.93725490196078431" blue="0.93725490196078431" alpha="1" colorSpace="calibratedRGB"/>
+                                    <constraints>
+                                        <constraint firstAttribute="height" constant="28" id="0kj-eU-0Lb"/>
+                                        <constraint firstAttribute="width" constant="66" id="bN8-Zz-1EC"/>
+                                    </constraints>
+                                    <fontDescription key="fontDescription" type="system" weight="medium" pointSize="14"/>
+                                    <state key="normal" title="分享">
+                                        <color key="titleColor" red="0.0039215686269999999" green="0.75686274509999996" blue="0.70980392160000005" alpha="1" colorSpace="calibratedRGB"/>
+                                    </state>
+                                    <userDefinedRuntimeAttributes>
+                                        <userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
+                                            <real key="value" value="14"/>
+                                        </userDefinedRuntimeAttribute>
+                                    </userDefinedRuntimeAttributes>
+                                    <connections>
+                                        <action selector="buttonClickAction:" destination="iN0-l3-epB" eventType="touchUpInside" id="oxR-3W-4P3"/>
+                                    </connections>
+                                </button>
+                                <button opaque="NO" tag="1003" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="N9k-Lb-OD5">
+                                    <rect key="frame" x="18" y="361" width="103" height="34"/>
+                                    <color key="backgroundColor" red="0.0039215686269999999" green="0.75686274509999996" blue="0.70980392160000005" alpha="1" colorSpace="calibratedRGB"/>
+                                    <constraints>
+                                        <constraint firstAttribute="height" constant="34" id="EAa-IF-fgH"/>
+                                    </constraints>
+                                    <fontDescription key="fontDescription" type="system" weight="medium" pointSize="14"/>
+                                    <state key="normal" title="去练习"/>
+                                    <userDefinedRuntimeAttributes>
+                                        <userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
+                                            <real key="value" value="17"/>
+                                        </userDefinedRuntimeAttribute>
+                                    </userDefinedRuntimeAttributes>
+                                    <connections>
+                                        <action selector="buttonClickAction:" destination="iN0-l3-epB" eventType="touchUpInside" id="sYQ-1S-l5u"/>
+                                    </connections>
+                                </button>
+                                <button opaque="NO" tag="1004" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="qqs-xN-SGQ">
+                                    <rect key="frame" x="127" y="361" width="102.5" height="34"/>
+                                    <color key="backgroundColor" red="0.96862745098039216" green="0.57647058823529407" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
+                                    <fontDescription key="fontDescription" type="system" weight="medium" pointSize="14"/>
+                                    <state key="normal" title="再试一次"/>
+                                    <userDefinedRuntimeAttributes>
+                                        <userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
+                                            <real key="value" value="17"/>
+                                        </userDefinedRuntimeAttribute>
+                                    </userDefinedRuntimeAttributes>
+                                    <connections>
+                                        <action selector="buttonClickAction:" destination="iN0-l3-epB" eventType="touchUpInside" id="ufF-E1-csh"/>
+                                    </connections>
+                                </button>
+                                <button opaque="NO" tag="1005" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="EOy-Rm-p6b">
+                                    <rect key="frame" x="235.5" y="361" width="103" height="34"/>
+                                    <color key="backgroundColor" red="0.96862745098039216" green="0.70980392156862748" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
+                                    <fontDescription key="fontDescription" type="system" weight="medium" pointSize="14"/>
+                                    <state key="normal" title="查看报告"/>
+                                    <userDefinedRuntimeAttributes>
+                                        <userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
+                                            <real key="value" value="17"/>
+                                        </userDefinedRuntimeAttribute>
+                                    </userDefinedRuntimeAttributes>
+                                    <connections>
+                                        <action selector="buttonClickAction:" destination="iN0-l3-epB" eventType="touchUpInside" id="Tgi-ZH-3lq"/>
+                                    </connections>
+                                </button>
+                                <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="B2m-Gc-bZ4">
+                                    <rect key="frame" x="22" y="342" width="316.5" height="1"/>
+                                    <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                                    <constraints>
+                                        <constraint firstAttribute="height" constant="1" id="jbf-7j-0Js"/>
+                                    </constraints>
+                                </view>
+                                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="音准" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="u9l-uA-IVQ">
+                                    <rect key="frame" x="24" y="86" width="42" height="17"/>
+                                    <fontDescription key="fontDescription" type="system" weight="semibold" pointSize="13"/>
+                                    <color key="textColor" red="0.0039215686269999999" green="0.75686274509999996" blue="0.70980392160000005" alpha="1" colorSpace="calibratedRGB"/>
+                                    <nil key="highlightedColor"/>
+                                </label>
+                                <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="lV9-fw-Fkw">
+                                    <rect key="frame" x="76" y="84" width="180" height="21"/>
+                                    <subviews>
+                                        <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="d2B-ck-SlX">
+                                            <rect key="frame" x="0.0" y="0.0" width="0.0" height="21"/>
+                                            <color key="backgroundColor" red="0.0039215686274509803" green="0.75686274509803919" blue="0.70980392156862748" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/>
+                                            <constraints>
+                                                <constraint firstAttribute="width" id="qbr-4x-9un"/>
+                                            </constraints>
+                                        </view>
+                                    </subviews>
+                                    <color key="backgroundColor" red="0.96078431372549022" green="0.95686274509803915" blue="0.94901960784313721" alpha="1" colorSpace="calibratedRGB"/>
+                                    <constraints>
+                                        <constraint firstItem="d2B-ck-SlX" firstAttribute="top" secondItem="lV9-fw-Fkw" secondAttribute="top" id="bDK-RK-k7r"/>
+                                        <constraint firstAttribute="height" constant="21" id="pQa-YV-oev"/>
+                                        <constraint firstItem="d2B-ck-SlX" firstAttribute="leading" secondItem="lV9-fw-Fkw" secondAttribute="leading" id="r2k-1S-L8y"/>
+                                        <constraint firstAttribute="width" constant="180" id="w7S-UX-ntU"/>
+                                        <constraint firstAttribute="bottom" secondItem="d2B-ck-SlX" secondAttribute="bottom" id="wvz-Vs-JQV"/>
+                                    </constraints>
+                                </view>
+                                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bCl-zp-NUb">
+                                    <rect key="frame" x="266" y="94.5" width="0.0" height="0.0"/>
+                                    <fontDescription key="fontDescription" type="boldSystem" pointSize="18"/>
+                                    <color key="textColor" red="0.0039215686269999999" green="0.75686274509999996" blue="0.70980392160000005" alpha="1" colorSpace="calibratedRGB"/>
+                                    <nil key="highlightedColor"/>
+                                </label>
+                                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="节奏" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Tzk-7k-87s">
+                                    <rect key="frame" x="24" y="131" width="42" height="17"/>
+                                    <fontDescription key="fontDescription" type="system" weight="semibold" pointSize="13"/>
+                                    <color key="textColor" red="0.0039215686269999999" green="0.75686274509999996" blue="0.70980392160000005" alpha="1" colorSpace="calibratedRGB"/>
+                                    <nil key="highlightedColor"/>
+                                </label>
+                                <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="aJS-TL-5oc">
+                                    <rect key="frame" x="76" y="129" width="180" height="21"/>
+                                    <subviews>
+                                        <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="zV2-Tw-oe3">
+                                            <rect key="frame" x="0.0" y="0.0" width="0.0" height="21"/>
+                                            <color key="backgroundColor" red="0.0039215686269999999" green="0.75686274509999996" blue="0.70980392160000005" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/>
+                                            <constraints>
+                                                <constraint firstAttribute="width" id="bs0-i5-6c0"/>
+                                            </constraints>
+                                        </view>
+                                    </subviews>
+                                    <color key="backgroundColor" red="0.96078431369999995" green="0.95686274510000002" blue="0.94901960780000005" alpha="1" colorSpace="calibratedRGB"/>
+                                    <constraints>
+                                        <constraint firstAttribute="bottom" secondItem="zV2-Tw-oe3" secondAttribute="bottom" id="7oR-mW-2GT"/>
+                                        <constraint firstItem="zV2-Tw-oe3" firstAttribute="top" secondItem="aJS-TL-5oc" secondAttribute="top" id="KC3-D9-GxU"/>
+                                        <constraint firstItem="zV2-Tw-oe3" firstAttribute="leading" secondItem="aJS-TL-5oc" secondAttribute="leading" id="fUK-ez-Zmq"/>
+                                    </constraints>
+                                </view>
+                                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="0PX-DQ-geK">
+                                    <rect key="frame" x="266" y="139.5" width="0.0" height="0.0"/>
+                                    <fontDescription key="fontDescription" type="boldSystem" pointSize="18"/>
+                                    <color key="textColor" red="0.0039215686269999999" green="0.75686274509999996" blue="0.70980392160000005" alpha="1" colorSpace="calibratedRGB"/>
+                                    <nil key="highlightedColor"/>
+                                </label>
+                                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="完整性" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="J7e-ik-yVX">
+                                    <rect key="frame" x="24" y="176" width="42" height="17"/>
+                                    <constraints>
+                                        <constraint firstAttribute="height" constant="17" id="ME6-xN-Kf8"/>
+                                        <constraint firstAttribute="width" constant="42" id="kzu-60-diG"/>
+                                    </constraints>
+                                    <fontDescription key="fontDescription" type="system" weight="semibold" pointSize="13"/>
+                                    <color key="textColor" red="0.0039215686269999999" green="0.75686274509999996" blue="0.70980392160000005" alpha="1" colorSpace="calibratedRGB"/>
+                                    <nil key="highlightedColor"/>
+                                </label>
+                                <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="qd3-W6-ODw">
+                                    <rect key="frame" x="76" y="174" width="180" height="21"/>
+                                    <subviews>
+                                        <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="aU3-L4-zQd">
+                                            <rect key="frame" x="0.0" y="0.0" width="0.0" height="21"/>
+                                            <color key="backgroundColor" red="0.0039215686269999999" green="0.75686274509999996" blue="0.70980392160000005" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/>
+                                            <constraints>
+                                                <constraint firstAttribute="width" id="adA-kZ-k6W"/>
+                                            </constraints>
+                                        </view>
+                                    </subviews>
+                                    <color key="backgroundColor" red="0.96078431369999995" green="0.95686274510000002" blue="0.94901960780000005" alpha="1" colorSpace="calibratedRGB"/>
+                                    <constraints>
+                                        <constraint firstAttribute="bottom" secondItem="aU3-L4-zQd" secondAttribute="bottom" id="fGq-9o-0er"/>
+                                        <constraint firstItem="aU3-L4-zQd" firstAttribute="leading" secondItem="qd3-W6-ODw" secondAttribute="leading" id="rz6-bU-tJv"/>
+                                        <constraint firstItem="aU3-L4-zQd" firstAttribute="top" secondItem="qd3-W6-ODw" secondAttribute="top" id="yYd-cO-9jL"/>
+                                    </constraints>
+                                </view>
+                                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="kw3-ma-jdO">
+                                    <rect key="frame" x="266" y="184.5" width="0.0" height="0.0"/>
+                                    <fontDescription key="fontDescription" type="boldSystem" pointSize="18"/>
+                                    <color key="textColor" red="0.0039215686269999999" green="0.75686274509999996" blue="0.70980392160000005" alpha="1" colorSpace="calibratedRGB"/>
+                                    <nil key="highlightedColor"/>
+                                </label>
+                                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ZnY-N0-3Om">
+                                    <rect key="frame" x="24" y="322" width="321.5" height="0.0"/>
+                                    <fontDescription key="fontDescription" type="system" pointSize="14"/>
+                                    <color key="textColor" red="0.50196078431372548" green="0.50196078431372548" blue="0.50196078431372548" alpha="1" colorSpace="calibratedRGB"/>
+                                    <nil key="highlightedColor"/>
+                                </label>
+                            </subviews>
+                            <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+                            <constraints>
+                                <constraint firstAttribute="trailing" secondItem="B2m-Gc-bZ4" secondAttribute="trailing" constant="25" id="0Ll-BW-cOb"/>
+                                <constraint firstItem="qqs-xN-SGQ" firstAttribute="width" secondItem="N9k-Lb-OD5" secondAttribute="width" id="0y5-Py-7qm"/>
+                                <constraint firstItem="aJS-TL-5oc" firstAttribute="height" secondItem="lV9-fw-Fkw" secondAttribute="height" id="1PQ-1o-fyY"/>
+                                <constraint firstItem="bCl-zp-NUb" firstAttribute="centerY" secondItem="u9l-uA-IVQ" secondAttribute="centerY" id="1mK-3w-dID"/>
+                                <constraint firstAttribute="trailing" secondItem="EOy-Rm-p6b" secondAttribute="trailing" constant="25" id="4FH-Df-3rl"/>
+                                <constraint firstItem="qqs-xN-SGQ" firstAttribute="height" secondItem="N9k-Lb-OD5" secondAttribute="height" id="5x1-sv-917"/>
+                                <constraint firstItem="qqs-xN-SGQ" firstAttribute="leading" secondItem="N9k-Lb-OD5" secondAttribute="trailing" constant="6" id="6xs-va-Kpg"/>
+                                <constraint firstItem="0PX-DQ-geK" firstAttribute="centerY" secondItem="Tzk-7k-87s" secondAttribute="centerY" id="9Ty-3v-Seg"/>
+                                <constraint firstItem="EOy-Rm-p6b" firstAttribute="leading" secondItem="qqs-xN-SGQ" secondAttribute="trailing" constant="6" id="AnC-5O-ZOv"/>
+                                <constraint firstItem="qHz-48-PaW" firstAttribute="top" secondItem="9ti-sU-Ve9" secondAttribute="top" constant="26" id="Bf4-ai-1BP"/>
+                                <constraint firstItem="qd3-W6-ODw" firstAttribute="leading" secondItem="lV9-fw-Fkw" secondAttribute="leading" id="D2p-Pj-34t"/>
+                                <constraint firstItem="aFf-78-FUB" firstAttribute="centerY" secondItem="anF-LP-hlF" secondAttribute="centerY" id="FCK-yo-Kp9"/>
+                                <constraint firstItem="Tzk-7k-87s" firstAttribute="top" secondItem="u9l-uA-IVQ" secondAttribute="bottom" constant="28" id="H7y-ps-gWy"/>
+                                <constraint firstItem="aFf-78-FUB" firstAttribute="leading" secondItem="FA5-n2-FUc" secondAttribute="trailing" constant="8" id="HUZ-wT-Le6"/>
+                                <constraint firstItem="aJS-TL-5oc" firstAttribute="width" secondItem="lV9-fw-Fkw" secondAttribute="width" id="HhG-Df-wZ4"/>
+                                <constraint firstItem="aJS-TL-5oc" firstAttribute="leading" secondItem="lV9-fw-Fkw" secondAttribute="leading" id="JaX-3z-5Ii"/>
+                                <constraint firstItem="kw3-ma-jdO" firstAttribute="centerY" secondItem="J7e-ik-yVX" secondAttribute="centerY" id="Jsv-0c-3NB"/>
+                                <constraint firstItem="lV9-fw-Fkw" firstAttribute="leading" secondItem="u9l-uA-IVQ" secondAttribute="trailing" constant="10" id="MY4-pt-KZB"/>
+                                <constraint firstItem="N9k-Lb-OD5" firstAttribute="top" secondItem="B2m-Gc-bZ4" secondAttribute="bottom" constant="18" id="MvV-Yg-H1P"/>
+                                <constraint firstItem="qqs-xN-SGQ" firstAttribute="bottom" secondItem="N9k-Lb-OD5" secondAttribute="bottom" id="NgX-Kg-1IT"/>
+                                <constraint firstAttribute="bottom" secondItem="N9k-Lb-OD5" secondAttribute="bottom" constant="20" id="P4h-rt-go9"/>
+                                <constraint firstItem="kw3-ma-jdO" firstAttribute="leading" secondItem="bCl-zp-NUb" secondAttribute="leading" id="Qwk-k1-LwN"/>
+                                <constraint firstItem="Tzk-7k-87s" firstAttribute="height" secondItem="u9l-uA-IVQ" secondAttribute="height" id="StV-Ao-1YB"/>
+                                <constraint firstItem="Tzk-7k-87s" firstAttribute="trailing" secondItem="u9l-uA-IVQ" secondAttribute="trailing" id="Tjh-Ib-jqQ"/>
+                                <constraint firstAttribute="trailing" secondItem="ZnY-N0-3Om" secondAttribute="trailing" constant="18" id="TsD-QX-8Vh"/>
+                                <constraint firstItem="J7e-ik-yVX" firstAttribute="top" secondItem="Tzk-7k-87s" secondAttribute="bottom" constant="28" id="V46-Hp-dso"/>
+                                <constraint firstItem="Tzk-7k-87s" firstAttribute="width" secondItem="u9l-uA-IVQ" secondAttribute="width" id="Vem-uQ-w6K"/>
+                                <constraint firstItem="J7e-ik-yVX" firstAttribute="trailing" secondItem="u9l-uA-IVQ" secondAttribute="trailing" id="YCn-51-m0o"/>
+                                <constraint firstItem="N9k-Lb-OD5" firstAttribute="leading" secondItem="9ti-sU-Ve9" secondAttribute="leading" constant="18" id="Ygk-CA-dmb"/>
+                                <constraint firstItem="anF-LP-hlF" firstAttribute="leading" secondItem="qHz-48-PaW" secondAttribute="trailing" constant="8" id="ZWk-LI-a0b"/>
+                                <constraint firstItem="bCl-zp-NUb" firstAttribute="leading" secondItem="lV9-fw-Fkw" secondAttribute="trailing" constant="10" id="a9o-Fa-wbA"/>
+                                <constraint firstItem="0PX-DQ-geK" firstAttribute="leading" secondItem="bCl-zp-NUb" secondAttribute="leading" id="aaD-pL-Tdx"/>
+                                <constraint firstItem="EOy-Rm-p6b" firstAttribute="width" secondItem="N9k-Lb-OD5" secondAttribute="width" id="b6b-G0-aw3"/>
+                                <constraint firstItem="EOy-Rm-p6b" firstAttribute="bottom" secondItem="N9k-Lb-OD5" secondAttribute="bottom" id="cew-ZA-UoY"/>
+                                <constraint firstItem="J7e-ik-yVX" firstAttribute="height" secondItem="u9l-uA-IVQ" secondAttribute="height" id="dUV-3d-78J"/>
+                                <constraint firstItem="qd3-W6-ODw" firstAttribute="height" secondItem="lV9-fw-Fkw" secondAttribute="height" id="dXq-wT-uiN"/>
+                                <constraint firstItem="u9l-uA-IVQ" firstAttribute="top" secondItem="anF-LP-hlF" secondAttribute="bottom" constant="40" id="eBH-Kl-b0w"/>
+                                <constraint firstItem="qd3-W6-ODw" firstAttribute="centerY" secondItem="J7e-ik-yVX" secondAttribute="centerY" id="fZs-U9-5oF"/>
+                                <constraint firstItem="B2m-Gc-bZ4" firstAttribute="top" secondItem="ZnY-N0-3Om" secondAttribute="bottom" constant="20" id="hee-RI-XsM"/>
+                                <constraint firstItem="qd3-W6-ODw" firstAttribute="width" secondItem="lV9-fw-Fkw" secondAttribute="width" id="ifa-aV-FqI"/>
+                                <constraint firstAttribute="trailing" secondItem="aFf-78-FUB" secondAttribute="trailing" constant="14" id="k3w-ZR-Clf"/>
+                                <constraint firstItem="anF-LP-hlF" firstAttribute="centerY" secondItem="qHz-48-PaW" secondAttribute="centerY" id="kY3-jz-wOn"/>
+                                <constraint firstItem="J7e-ik-yVX" firstAttribute="width" secondItem="u9l-uA-IVQ" secondAttribute="width" id="lnf-vz-BTx"/>
+                                <constraint firstItem="FA5-n2-FUc" firstAttribute="centerY" secondItem="anF-LP-hlF" secondAttribute="centerY" id="n3R-a3-rrN"/>
+                                <constraint firstItem="EOy-Rm-p6b" firstAttribute="height" secondItem="N9k-Lb-OD5" secondAttribute="height" id="nRz-Ve-yEn"/>
+                                <constraint firstItem="aJS-TL-5oc" firstAttribute="centerY" secondItem="Tzk-7k-87s" secondAttribute="centerY" id="o2E-z3-pga"/>
+                                <constraint firstItem="B2m-Gc-bZ4" firstAttribute="leading" secondItem="9ti-sU-Ve9" secondAttribute="leading" constant="22" id="qfD-HZ-Ru4"/>
+                                <constraint firstItem="lV9-fw-Fkw" firstAttribute="centerY" secondItem="u9l-uA-IVQ" secondAttribute="centerY" id="r6X-cJ-CxD"/>
+                                <constraint firstItem="J7e-ik-yVX" firstAttribute="leading" secondItem="9ti-sU-Ve9" secondAttribute="leading" constant="24" id="rj2-MB-Xkx"/>
+                                <constraint firstItem="ZnY-N0-3Om" firstAttribute="leading" secondItem="9ti-sU-Ve9" secondAttribute="leading" constant="24" id="vVX-Um-Wsa"/>
+                                <constraint firstItem="qHz-48-PaW" firstAttribute="leading" secondItem="9ti-sU-Ve9" secondAttribute="leading" constant="18" id="yQQ-hp-WN0"/>
+                            </constraints>
+                        </view>
+                        <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="evaluate_logo" translatesAutoresizingMaskIntoConstraints="NO" id="clo-Lu-5re">
+                            <rect key="frame" x="14" y="62" width="145.5" height="143.5"/>
+                            <constraints>
+                                <constraint firstAttribute="width" secondItem="clo-Lu-5re" secondAttribute="height" multiplier="145:143" id="R5I-oV-F9j"/>
+                            </constraints>
+                        </imageView>
+                        <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fjf-Cx-HBS">
+                            <rect key="frame" x="75" y="215.5" width="0.0" height="0.0"/>
+                            <fontDescription key="fontDescription" type="boldSystem" pointSize="38"/>
+                            <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                            <nil key="highlightedColor"/>
+                        </label>
+                        <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Qbk-ED-W1i">
+                            <rect key="frame" x="87" y="215.5" width="0.0" height="0.0"/>
+                            <fontDescription key="fontDescription" type="system" weight="semibold" pointSize="23"/>
+                            <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                            <nil key="highlightedColor"/>
+                        </label>
+                        <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="分" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="9eP-w3-Oz4">
+                            <rect key="frame" x="75" y="180.5" width="24" height="28"/>
+                            <fontDescription key="fontDescription" type="system" weight="semibold" pointSize="23"/>
+                            <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                            <nil key="highlightedColor"/>
+                        </label>
+                    </subviews>
+                    <color key="backgroundColor" red="0.0039215686274509803" green="0.75686274509803919" blue="0.70980392156862748" alpha="1" colorSpace="calibratedRGB"/>
+                    <constraints>
+                        <constraint firstItem="9eP-w3-Oz4" firstAttribute="leading" secondItem="fjf-Cx-HBS" secondAttribute="trailing" id="50u-Cc-Ao9"/>
+                        <constraint firstItem="clo-Lu-5re" firstAttribute="leading" secondItem="kev-3r-AV2" secondAttribute="leading" constant="14" id="9wJ-QD-J4c"/>
+                        <constraint firstAttribute="bottom" secondItem="9ti-sU-Ve9" secondAttribute="bottom" id="Eyh-rc-wKX"/>
+                        <constraint firstItem="clo-Lu-5re" firstAttribute="top" secondItem="kev-3r-AV2" secondAttribute="top" constant="62" id="F5F-QP-1Q4"/>
+                        <constraint firstItem="9ti-sU-Ve9" firstAttribute="leading" secondItem="clo-Lu-5re" secondAttribute="trailing" constant="22" id="HJO-Yz-Sj6"/>
+                        <constraint firstAttribute="width" constant="545" id="PBs-mS-Jsg"/>
+                        <constraint firstItem="9ti-sU-Ve9" firstAttribute="leading" secondItem="kev-3r-AV2" secondAttribute="centerX" multiplier="2/3" id="Pz9-QB-6ac"/>
+                        <constraint firstItem="Qbk-ED-W1i" firstAttribute="centerX" secondItem="clo-Lu-5re" secondAttribute="centerX" id="RSO-m9-77y"/>
+                        <constraint firstItem="fjf-Cx-HBS" firstAttribute="centerX" secondItem="clo-Lu-5re" secondAttribute="centerX" constant="-12" id="VRl-jd-YiN"/>
+                        <constraint firstItem="9eP-w3-Oz4" firstAttribute="bottom" secondItem="fjf-Cx-HBS" secondAttribute="bottom" constant="-7" id="bm0-jX-3Sx"/>
+                        <constraint firstItem="9ti-sU-Ve9" firstAttribute="top" secondItem="kev-3r-AV2" secondAttribute="top" id="h7j-uV-HOs"/>
+                        <constraint firstItem="Qbk-ED-W1i" firstAttribute="top" secondItem="fjf-Cx-HBS" secondAttribute="bottom" id="ioQ-H8-Dhi"/>
+                        <constraint firstAttribute="trailing" secondItem="9ti-sU-Ve9" secondAttribute="trailing" id="jdw-Gv-9To"/>
+                        <constraint firstItem="fjf-Cx-HBS" firstAttribute="top" secondItem="clo-Lu-5re" secondAttribute="bottom" constant="10" id="zL2-rC-Vbr"/>
+                    </constraints>
+                    <userDefinedRuntimeAttributes>
+                        <userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
+                            <real key="value" value="20"/>
+                        </userDefinedRuntimeAttribute>
+                    </userDefinedRuntimeAttributes>
+                </view>
+                <button opaque="NO" tag="1000" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ECe-lf-Wo6">
+                    <rect key="frame" x="640.5" y="2" width="44" height="44"/>
+                    <constraints>
+                        <constraint firstAttribute="height" constant="44" id="72Y-ys-SLx"/>
+                        <constraint firstAttribute="width" constant="44" id="BEV-8f-Nl5"/>
+                    </constraints>
+                    <state key="normal" image="cloud_cancle"/>
+                    <connections>
+                        <action selector="buttonClickAction:" destination="iN0-l3-epB" eventType="touchUpInside" id="rhv-1p-kya"/>
+                    </connections>
+                </button>
+            </subviews>
+            <color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.52000000000000002" colorSpace="custom" customColorSpace="calibratedRGB"/>
+            <constraints>
+                <constraint firstAttribute="bottom" secondItem="kev-3r-AV2" secondAttribute="bottom" constant="15" id="8Zy-2w-zN8"/>
+                <constraint firstItem="ECe-lf-Wo6" firstAttribute="centerY" secondItem="kev-3r-AV2" secondAttribute="top" id="DUE-TX-w7J"/>
+                <constraint firstItem="kev-3r-AV2" firstAttribute="centerX" secondItem="iN0-l3-epB" secondAttribute="centerX" id="QaD-fh-unz"/>
+                <constraint firstItem="kev-3r-AV2" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" constant="24" id="hYe-gN-0u7"/>
+                <constraint firstItem="ECe-lf-Wo6" firstAttribute="leading" secondItem="kev-3r-AV2" secondAttribute="trailing" id="tdE-sB-9hR"/>
+            </constraints>
+            <nil key="simulatedTopBarMetrics"/>
+            <nil key="simulatedBottomBarMetrics"/>
+            <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
+            <connections>
+                <outlet property="integrityLabel" destination="kw3-ma-jdO" id="7D9-hn-DGT"/>
+                <outlet property="integrityValueView" destination="aU3-L4-zQd" id="qik-rX-eB1"/>
+                <outlet property="integrityView" destination="qd3-W6-ODw" id="boe-4l-XqJ"/>
+                <outlet property="integrityWidth" destination="adA-kZ-k6W" id="tiT-Fe-MM1"/>
+                <outlet property="intonationScore" destination="bCl-zp-NUb" id="erm-vk-XsR"/>
+                <outlet property="intonationValueView" destination="d2B-ck-SlX" id="0Jy-Ok-czQ"/>
+                <outlet property="intonationView" destination="lV9-fw-Fkw" id="fYQ-vK-OgC"/>
+                <outlet property="intonationWidth" destination="qbr-4x-9un" id="59O-c4-FDQ"/>
+                <outlet property="lineView" destination="B2m-Gc-bZ4" id="dWv-E0-YIg"/>
+                <outlet property="reportDesc" destination="ZnY-N0-3Om" id="Mtw-T0-rVh"/>
+                <outlet property="rhyhtmVauleView" destination="zV2-Tw-oe3" id="kEh-Ed-MHt"/>
+                <outlet property="rhythmLabel" destination="0PX-DQ-geK" id="b3m-fm-MtQ"/>
+                <outlet property="rhythmView" destination="aJS-TL-5oc" id="7dF-52-tZH"/>
+                <outlet property="rhythmWidth" destination="bs0-i5-6c0" id="2MX-1r-sJX"/>
+                <outlet property="scoreDesc" destination="Qbk-ED-W1i" id="BUa-zv-opz"/>
+                <outlet property="scoreLabel" destination="fjf-Cx-HBS" id="cyy-jn-IRU"/>
+            </connections>
+            <point key="canvasLocation" x="-49.275362318840585" y="-4.0178571428571423"/>
+        </view>
+    </objects>
+    <resources>
+        <image name="cloud_cancle" width="26" height="26"/>
+        <image name="evaluate_logo" width="166" height="143"/>
+        <systemColor name="systemBackgroundColor">
+            <color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+        </systemColor>
+    </resources>
+</document>

+ 22 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/TipsAlert/NoWiredTipsAlert.h

@@ -0,0 +1,22 @@
+//
+//  NoWiredTipsAlert.h
+//  StudentDaya
+//
+//  Created by Kyle on 2021/12/12.
+//  Copyright © 2021 DayaMusic. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+NS_ASSUME_NONNULL_BEGIN
+typedef void(^CheckWiredCallback)(void);
+
+@interface NoWiredTipsAlert : UIView
+
++ (instancetype)shareInstance;
+
+- (void)showAlertInView:(UIView *)displayView callback:(CheckWiredCallback)callback;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 49 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/TipsAlert/NoWiredTipsAlert.m

@@ -0,0 +1,49 @@
+//
+//  NoWiredTipsAlert.m
+//  StudentDaya
+//
+//  Created by Kyle on 2021/12/12.
+//  Copyright © 2021 DayaMusic. All rights reserved.
+//
+
+#import "NoWiredTipsAlert.h"
+
+@interface NoWiredTipsAlert ()
+
+@property (nonatomic, copy) CheckWiredCallback callback;
+
+@end
+
+@implementation NoWiredTipsAlert
+
++ (instancetype)shareInstance {
+    NoWiredTipsAlert *alert = [[[NSBundle mainBundle] loadNibNamed:@"NoWiredTipsAlert" owner:nil options:nil] firstObject];
+    return alert;
+}
+
+- (void)showAlertInView:(UIView *)displayView callback:(nonnull CheckWiredCallback)callback {
+    if (callback) {
+        self.callback = callback;
+    }
+    [displayView addSubview:self];
+}
+
+- (void)hiddenView {
+    [self removeFromSuperview];
+}
+- (IBAction)hiddenAlert:(id)sender {
+    [self hiddenView];
+    if (self.callback) {
+        self.callback();
+    }
+}
+
+/*
+// Only override drawRect: if you perform custom drawing.
+// An empty implementation adversely affects performance during animation.
+- (void)drawRect:(CGRect)rect {
+    // Drawing code
+}
+*/
+
+@end

+ 121 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/TipsAlert/NoWiredTipsAlert.xib

@@ -0,0 +1,121 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="19529" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
+    <device id="retina6_1" orientation="portrait" appearance="light"/>
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19519"/>
+        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
+        <capability name="System colors in document resources" minToolsVersion="11.0"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <objects>
+        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
+        <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
+        <view contentMode="scaleToFill" id="iN0-l3-epB" customClass="NoWiredTipsAlert">
+            <rect key="frame" x="0.0" y="0.0" width="663" height="423"/>
+            <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+            <subviews>
+                <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ah5-xM-cyD">
+                    <rect key="frame" x="199.5" y="86.5" width="264" height="250"/>
+                    <subviews>
+                        <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="evaluate_logo" translatesAutoresizingMaskIntoConstraints="NO" id="qZY-An-3Kg">
+                            <rect key="frame" x="49" y="30" width="166" height="143"/>
+                            <constraints>
+                                <constraint firstAttribute="height" constant="143" id="3in-lr-fgk"/>
+                                <constraint firstAttribute="width" constant="166" id="IFQ-Q4-07P"/>
+                            </constraints>
+                        </imageView>
+                        <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="请佩戴耳机以保证测评准确率~" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Ww5-Ot-JpL">
+                            <rect key="frame" x="32" y="169" width="200" height="17"/>
+                            <fontDescription key="fontDescription" type="system" pointSize="14"/>
+                            <color key="textColor" red="0.50196078430000002" green="0.50196078430000002" blue="0.50196078430000002" alpha="1" colorSpace="calibratedRGB"/>
+                            <nil key="highlightedColor"/>
+                        </label>
+                        <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="psJ-YC-Q92">
+                            <rect key="frame" x="74.5" y="198" width="115" height="34"/>
+                            <color key="backgroundColor" red="0.0039215686269999999" green="0.75686274509999996" blue="0.70980392160000005" alpha="1" colorSpace="calibratedRGB"/>
+                            <constraints>
+                                <constraint firstAttribute="height" constant="34" id="Zgp-9F-Slx"/>
+                                <constraint firstAttribute="width" constant="115" id="pxl-hG-rB3"/>
+                            </constraints>
+                            <fontDescription key="fontDescription" type="system" weight="medium" pointSize="14"/>
+                            <state key="normal" title="我知道了"/>
+                            <userDefinedRuntimeAttributes>
+                                <userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
+                                    <real key="value" value="17"/>
+                                </userDefinedRuntimeAttribute>
+                            </userDefinedRuntimeAttributes>
+                            <connections>
+                                <action selector="hiddenAlert:" destination="iN0-l3-epB" eventType="touchUpInside" id="3Qo-d6-rzW"/>
+                            </connections>
+                        </button>
+                        <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="M3R-Ao-jkS">
+                            <rect key="frame" x="224" y="0.0" width="40" height="40"/>
+                            <constraints>
+                                <constraint firstAttribute="height" constant="40" id="2jV-nA-GNI"/>
+                                <constraint firstAttribute="width" constant="40" id="YUL-Dj-pqb"/>
+                            </constraints>
+                            <state key="normal" image="check_cancle"/>
+                            <connections>
+                                <action selector="hiddenAlert:" destination="iN0-l3-epB" eventType="touchUpInside" id="e5e-J3-AiO"/>
+                            </connections>
+                        </button>
+                    </subviews>
+                    <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+                    <constraints>
+                        <constraint firstItem="qZY-An-3Kg" firstAttribute="top" secondItem="ah5-xM-cyD" secondAttribute="top" constant="30" id="De8-6b-L70"/>
+                        <constraint firstAttribute="height" constant="250" id="KeG-CA-jys"/>
+                        <constraint firstItem="psJ-YC-Q92" firstAttribute="top" secondItem="Ww5-Ot-JpL" secondAttribute="bottom" constant="12" id="Qgm-um-k6z"/>
+                        <constraint firstAttribute="bottom" secondItem="psJ-YC-Q92" secondAttribute="bottom" constant="18" id="UCm-R8-q2q"/>
+                        <constraint firstAttribute="width" constant="264" id="bhW-0f-M08"/>
+                        <constraint firstItem="qZY-An-3Kg" firstAttribute="centerX" secondItem="ah5-xM-cyD" secondAttribute="centerX" id="jQI-en-d6F"/>
+                        <constraint firstAttribute="trailing" secondItem="M3R-Ao-jkS" secondAttribute="trailing" id="rNj-l9-iuh"/>
+                        <constraint firstItem="psJ-YC-Q92" firstAttribute="centerX" secondItem="ah5-xM-cyD" secondAttribute="centerX" id="uAS-Rq-Oqx"/>
+                        <constraint firstItem="M3R-Ao-jkS" firstAttribute="top" secondItem="ah5-xM-cyD" secondAttribute="top" id="w48-wT-LqO"/>
+                        <constraint firstItem="Ww5-Ot-JpL" firstAttribute="centerX" secondItem="ah5-xM-cyD" secondAttribute="centerX" id="zJm-80-LOi"/>
+                    </constraints>
+                    <userDefinedRuntimeAttributes>
+                        <userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
+                            <real key="value" value="20"/>
+                        </userDefinedRuntimeAttribute>
+                    </userDefinedRuntimeAttributes>
+                </view>
+                <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="image_alertTips" translatesAutoresizingMaskIntoConstraints="NO" id="8aD-n1-PzY">
+                    <rect key="frame" x="277.5" y="82.5" width="108" height="31"/>
+                    <constraints>
+                        <constraint firstAttribute="width" constant="108" id="1s5-7T-qTb"/>
+                        <constraint firstAttribute="height" constant="31" id="mJd-Tf-sEM"/>
+                    </constraints>
+                </imageView>
+                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="提示" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="yjg-xn-pKL">
+                    <rect key="frame" x="315" y="88" width="33" height="20"/>
+                    <fontDescription key="fontDescription" type="system" weight="medium" pointSize="16"/>
+                    <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                    <nil key="highlightedColor"/>
+                </label>
+            </subviews>
+            <viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
+            <color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.5" colorSpace="custom" customColorSpace="sRGB"/>
+            <constraints>
+                <constraint firstItem="8aD-n1-PzY" firstAttribute="top" secondItem="ah5-xM-cyD" secondAttribute="top" constant="-4" id="0PS-g0-dfi"/>
+                <constraint firstItem="yjg-xn-pKL" firstAttribute="centerX" secondItem="8aD-n1-PzY" secondAttribute="centerX" id="Py6-YQ-Ghs"/>
+                <constraint firstItem="ah5-xM-cyD" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="centerY" id="Uql-9x-IBE"/>
+                <constraint firstItem="yjg-xn-pKL" firstAttribute="centerY" secondItem="8aD-n1-PzY" secondAttribute="centerY" id="b07-Ty-0MM"/>
+                <constraint firstItem="8aD-n1-PzY" firstAttribute="centerX" secondItem="iN0-l3-epB" secondAttribute="centerX" id="nAr-bf-6o6"/>
+                <constraint firstItem="ah5-xM-cyD" firstAttribute="centerX" secondItem="iN0-l3-epB" secondAttribute="centerX" id="qI8-qZ-zsf"/>
+            </constraints>
+            <nil key="simulatedTopBarMetrics"/>
+            <nil key="simulatedBottomBarMetrics"/>
+            <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
+            <point key="canvasLocation" x="136.95652173913044" y="-88.727678571428569"/>
+        </view>
+    </objects>
+    <resources>
+        <image name="check_cancle" width="14" height="14"/>
+        <image name="evaluate_logo" width="166" height="143"/>
+        <image name="image_alertTips" width="108" height="31"/>
+        <systemColor name="systemBackgroundColor">
+            <color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+        </systemColor>
+    </resources>
+</document>

+ 34 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/beat/KSCloudBeatView.h

@@ -0,0 +1,34 @@
+//
+//  KSCloudBeatView.h
+//  StudentDaya
+//
+//  Created by Kyle on 2021/12/15.
+//  Copyright © 2021 DayaMusic. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+// 节拍器节拍选择
+typedef NS_ENUM(NSInteger, MetronomeType) {
+    MetronomeType1V4 = 0,     // 1/4
+    MetronomeType2V4,        // 2/4
+    MetronomeType3V4,        // 3/4
+    MetronomeType4V4,        // 4/4
+    MetronomeType3V8,        // 3/8
+    MetronomeType6V8         // 6/8
+};
+
+NS_ASSUME_NONNULL_BEGIN
+
+typedef void(^CloudBeatCallback)(BOOL isCancle);
+
+@interface KSCloudBeatView : UIView
+
++ (instancetype)shareInstanceWithBeatType:(MetronomeType)type speed:(NSInteger)speed repeatCount:(NSInteger)repeatCount supplement:(NSInteger)supplement;
+
+- (void)startPlayWithEndCallback:(CloudBeatCallback)callback;
+
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 427 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/beat/KSCloudBeatView.m

@@ -0,0 +1,427 @@
+//
+//  KSCloudBeatView.m
+//  StudentDaya
+//
+//  Created by Kyle on 2021/12/15.
+//  Copyright © 2021 DayaMusic. All rights reserved.
+//
+
+#import "KSCloudBeatView.h"
+#import <AVFoundation/AVFoundation.h>
+
+#define SPOT_WHDTH (16)
+
+@interface KSCloudBeatView ()
+
+@property (nonatomic, assign) NSInteger repeatCount;
+
+@property (nonatomic, assign) NSInteger currentNo;
+
+@property (nonatomic, assign) NSInteger totalNoCount;
+
+@property (nonatomic, assign) MetronomeType beatType;
+
+@property (nonatomic, assign) NSInteger speed;
+
+@property (nonatomic, copy) CloudBeatCallback callback;
+
+/** 定时器 */
+@property (nonatomic, strong) NSTimer *timer;
+
+/** 定时器多少秒循环一次 */
+@property (nonatomic, assign) float timerLength;
+
+/** 发出"嘀"声的播放器 */
+@property (nonatomic, strong) AVAudioPlayer *audioPlayDI;
+
+/** 发出"咚"声的播放器 */
+@property (nonatomic, strong) AVAudioPlayer *audioPlayDONG;
+
+/// 补充节拍
+@property (nonatomic, assign) NSInteger supplement;
+
+@end
+
+@implementation KSCloudBeatView
+
++ (instancetype)shareInstanceWithBeatType:(MetronomeType)type speed:(NSInteger)speed repeatCount:(NSInteger)repeatCount supplement:(NSInteger)supplement {
+    KSCloudBeatView *view = [[[NSBundle mainBundle] loadNibNamed:@"KSCloudBeatView" owner:nil options:nil] firstObject];
+    view.frame = CGRectMake(0, 0, KLandscapeWidth, KLandscapeHeight);
+    view.supplement = supplement;
+    view.repeatCount = repeatCount;
+    view.beatType = type;
+    view.speed = speed;
+    return view;
+}
+
+- (void)setBeatType:(MetronomeType)beatType {
+    _beatType = beatType;
+    // 创建节拍器的页面
+    [self createSpotImageView:beatType];
+}
+
+- (void)createSpotImageView:(MetronomeType)beatType {
+    CGFloat xSpace = 20.0f;
+    if (beatType == MetronomeType1V4) {
+        self.totalNoCount = 1 * self.repeatCount;
+        UIImageView *spotView = [self creatSpotImageView];
+        spotView.tag = 100;
+        [self addSubview:spotView];
+        [spotView mas_makeConstraints:^(MASConstraintMaker *make) {
+            make.centerX.mas_equalTo(self.mas_centerX);
+            make.centerY.mas_equalTo(self.mas_centerY);
+            make.width.height.mas_equalTo(SPOT_WHDTH);
+        }];
+    }
+    else if (beatType == MetronomeType2V4) {
+        self.totalNoCount = 2 * self.repeatCount;
+        UIImageView *spotView = [self creatSpotImageView];
+        spotView.tag = 100;
+        [self addSubview:spotView];
+        [spotView mas_makeConstraints:^(MASConstraintMaker *make) {
+            make.right.mas_equalTo(self.mas_centerX).offset(-xSpace/2);
+            make.centerY.mas_equalTo(self.mas_centerY);
+            make.width.height.mas_equalTo(SPOT_WHDTH);
+        }];
+        
+        UIImageView *spotView1 = [self createWhiteImageView];
+        spotView1.tag = 101;
+        [self addSubview:spotView1];
+        
+        [spotView1 mas_makeConstraints:^(MASConstraintMaker *make) {
+            make.left.mas_equalTo(self.mas_centerX).offset(xSpace/2);
+            make.centerY.mas_equalTo(self.mas_centerY);
+            make.width.height.mas_equalTo(SPOT_WHDTH);
+        }];
+    }
+    else if (beatType == MetronomeType3V4) {
+        self.totalNoCount = 3 * self.repeatCount;
+        UIImageView *spotView1 = [self createWhiteImageView];
+        spotView1.tag = 101;
+        [self addSubview:spotView1];
+        
+        [spotView1 mas_makeConstraints:^(MASConstraintMaker *make) {
+            make.centerX.mas_equalTo(self.mas_centerX);
+            make.centerY.mas_equalTo(self.mas_centerY);
+            make.width.height.mas_equalTo(SPOT_WHDTH);
+        }];
+        
+        UIImageView *spotView = [self creatSpotImageView];
+        spotView.tag = 100;
+        [self addSubview:spotView];
+        [spotView mas_makeConstraints:^(MASConstraintMaker *make) {
+            make.right.mas_equalTo(spotView1.mas_left).offset(-xSpace);
+            make.centerY.mas_equalTo(self.mas_centerY);
+            make.width.height.mas_equalTo(SPOT_WHDTH);
+        }];
+        
+        UIImageView *spotView2 = [self createWhiteImageView];
+        spotView2.tag = 102;
+        [self addSubview:spotView2];
+        [spotView2 mas_makeConstraints:^(MASConstraintMaker *make) {
+            make.left.mas_equalTo(spotView1.mas_right).offset(xSpace);
+            make.centerY.mas_equalTo(self.mas_centerY);
+            make.width.height.mas_equalTo(SPOT_WHDTH);
+        }];
+        
+    }
+    else if (beatType == MetronomeType4V4) {
+        self.totalNoCount = 4 * self.repeatCount;
+        UIImageView *spotView1 = [self createWhiteImageView];
+        spotView1.tag = 101;
+        [self addSubview:spotView1];
+        
+        [spotView1 mas_makeConstraints:^(MASConstraintMaker *make) {
+            make.right.mas_equalTo(self.mas_centerX).offset(-xSpace/2);
+            make.centerY.mas_equalTo(self.mas_centerY);
+            make.width.height.mas_equalTo(SPOT_WHDTH);
+        }];
+        
+        UIImageView *spotView = [self creatSpotImageView];
+        spotView.tag = 100;
+        [self addSubview:spotView];
+        [spotView mas_makeConstraints:^(MASConstraintMaker *make) {
+            make.right.mas_equalTo(spotView1.mas_left).offset(-xSpace);
+            make.centerY.mas_equalTo(self.mas_centerY);
+            make.width.height.mas_equalTo(SPOT_WHDTH);
+        }];
+        
+        UIImageView *spotView2 = [self createWhiteImageView];
+        spotView2.tag = 102;
+        [self addSubview:spotView2];
+        [spotView2 mas_makeConstraints:^(MASConstraintMaker *make) {
+            make.left.mas_equalTo(self.mas_centerX).offset(xSpace/2);
+            make.centerY.mas_equalTo(self.mas_centerY);
+            make.width.height.mas_equalTo(SPOT_WHDTH);
+        }];
+        
+        UIImageView *spotView3 = [self createWhiteImageView];
+        spotView3.tag = 103;
+        [self addSubview:spotView3];
+        [spotView3 mas_makeConstraints:^(MASConstraintMaker *make) {
+            make.left.mas_equalTo(spotView2.mas_right).offset(xSpace);
+            make.centerY.mas_equalTo(self.mas_centerY);
+            make.width.height.mas_equalTo(SPOT_WHDTH);
+        }];
+    }
+    else if (beatType == MetronomeType3V8) {
+        self.totalNoCount = 3 * self.repeatCount;
+        UIImageView *spotView1 = [self createWhiteImageView];
+        spotView1.tag = 101;
+        [self addSubview:spotView1];
+        
+        [spotView1 mas_makeConstraints:^(MASConstraintMaker *make) {
+            make.centerX.mas_equalTo(self.mas_centerX);
+            make.centerY.mas_equalTo(self.mas_centerY);
+            make.width.height.mas_equalTo(SPOT_WHDTH);
+        }];
+        
+        UIImageView *spotView = [self creatSpotImageView];
+        spotView.tag = 100;
+        [self addSubview:spotView];
+        [spotView mas_makeConstraints:^(MASConstraintMaker *make) {
+            make.right.mas_equalTo(spotView1.mas_left).offset(-xSpace);
+            make.centerY.mas_equalTo(self.mas_centerY);
+            make.width.height.mas_equalTo(SPOT_WHDTH);
+        }];
+        
+        UIImageView *spotView2 = [self createWhiteImageView];
+        spotView2.tag = 102;
+        [self addSubview:spotView2];
+        [spotView2 mas_makeConstraints:^(MASConstraintMaker *make) {
+            make.left.mas_equalTo(spotView1.mas_right).offset(xSpace);
+            make.centerY.mas_equalTo(self.mas_centerY);
+            make.width.height.mas_equalTo(SPOT_WHDTH);
+        }];
+    }
+    else if (beatType == MetronomeType6V8) {
+        self.totalNoCount = 6 * self.repeatCount;
+        UIImageView *spotView2 = [self createWhiteImageView];
+        spotView2.tag = 102;
+        [self addSubview:spotView2];
+        
+        [spotView2 mas_makeConstraints:^(MASConstraintMaker *make) {
+            make.right.mas_equalTo(self.mas_centerX).offset(-xSpace/2);
+            make.centerY.mas_equalTo(self.mas_centerY);
+            make.width.height.mas_equalTo(SPOT_WHDTH);
+        }];
+        
+        UIImageView *spotView1 = [self createWhiteImageView];
+        spotView1.tag = 101;
+        [self addSubview:spotView1];
+        
+        [spotView1 mas_makeConstraints:^(MASConstraintMaker *make) {
+            make.right.mas_equalTo(spotView2.mas_left).offset(-xSpace);
+            make.centerY.mas_equalTo(self.mas_centerY);
+            make.width.height.mas_equalTo(SPOT_WHDTH);
+        }];
+        
+        UIImageView *spotView = [self creatSpotImageView];
+        spotView.tag = 100;
+        [self addSubview:spotView];
+        
+        [spotView mas_makeConstraints:^(MASConstraintMaker *make) {
+            make.right.mas_equalTo(spotView1.mas_left).offset(-xSpace);
+            make.centerY.mas_equalTo(self.mas_centerY);
+            make.width.height.mas_equalTo(SPOT_WHDTH);
+        }];
+        
+        UIImageView *spotView3 = [self createWhiteImageView];
+        spotView3.tag = 103;
+        [self addSubview:spotView3];
+        
+        [spotView3 mas_makeConstraints:^(MASConstraintMaker *make) {
+            make.left.mas_equalTo(self.mas_centerX).offset(xSpace/2);
+            make.centerY.mas_equalTo(self.mas_centerY);
+            make.width.height.mas_equalTo(SPOT_WHDTH);
+        }];
+        
+        UIImageView *spotView4 = [self createWhiteImageView];
+        spotView4.tag = 104;
+        [self addSubview:spotView4];
+        
+        [spotView4 mas_makeConstraints:^(MASConstraintMaker *make) {
+            make.left.mas_equalTo(spotView3.mas_right).offset(xSpace);
+            make.centerY.mas_equalTo(self.mas_centerY);
+            make.width.height.mas_equalTo(SPOT_WHDTH);
+        }];
+        
+        UIImageView *spotView5 = [self createWhiteImageView];
+        spotView5.tag = 105;
+        [self addSubview:spotView5];
+        
+        [spotView5 mas_makeConstraints:^(MASConstraintMaker *make) {
+            make.left.mas_equalTo(spotView4.mas_right).offset(xSpace);
+            make.centerY.mas_equalTo(self.mas_centerY);
+            make.width.height.mas_equalTo(SPOT_WHDTH);
+        }];
+    }
+    if (self.supplement) { // 添加补充节拍
+        self.totalNoCount += self.supplement;
+    }
+}
+
+
+
+- (void)updateSpotViewHeightState:(NSInteger)currentTotalNo {
+    //    当前圆个数
+    NSInteger SingleBeatCount = (self.totalNoCount - self.supplement) / self.repeatCount;
+    UIImageView *currentImageView = [self viewWithTag:currentTotalNo % SingleBeatCount +100];
+    currentImageView.highlighted = YES;
+    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
+        currentImageView.highlighted = NO;
+    });
+}
+
+
+
+- (void)startPlayWithEndCallback:(CloudBeatCallback)callback {
+    if (callback) {
+        self.callback = callback;
+    }
+    [self startPlayAction];
+    
+}
+
+- (void)startPlayAction {
+    // 开始播放
+    [self.timer setFireDate:[NSDate distantPast]];
+}
+
+- (void)stopPlay {
+    [self removeAll];
+    [self removeFromSuperview];
+}
+
+- (IBAction)canclePlay:(id)sender {
+    [self stopPlay];
+    if (self.callback) {
+        self.callback(YES);
+    }
+}
+
+#pragma mark -- 创建大圆View
+- (UIImageView *)creatSpotImageView{
+    
+    UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"nomal_spot"] highlightedImage:[UIImage imageNamed:@"highlight_spot"]];
+    return imageView;
+}
+
+- (UIImageView *)createWhiteImageView {
+    UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"nomal_spot"] highlightedImage:[UIImage imageNamed:@"white_spot"]];
+    return imageView;
+}
+
+- (void)setSpeed:(NSInteger)speed {
+    _speed = speed;
+    CGFloat rateFloat = speed;
+    self.timerLength = 1 / (rateFloat / 60);
+}
+
+- (AVAudioPlayer *)audioPlayDI{
+    
+    if (!_audioPlayDI) {
+        
+        NSString *soundPath = [[NSBundle mainBundle]pathForResource:@"tick" ofType:@"wav"];
+        NSURL *soundUrl = [NSURL fileURLWithPath:soundPath];
+        //        初始化播放器对象
+        self.audioPlayDI = [[AVAudioPlayer alloc]initWithContentsOfURL:soundUrl error:nil];
+        //        设置循环次数,如果为负数,就是无限循环
+        self.audioPlayDI.numberOfLoops = 0;
+        //        是否可以更改播放速率
+        //        self.audioPlay.enableRate = YES;
+        
+    }
+    return _audioPlayDI;
+}
+
+- (AVAudioPlayer *)audioPlayDONG{
+    
+    if (!_audioPlayDONG) {
+        
+        NSString *soundPath = [[NSBundle mainBundle]pathForResource:@"tock" ofType:@"wav"];
+        NSURL *soundUrl = [NSURL fileURLWithPath:soundPath];
+        self.audioPlayDONG = [[AVAudioPlayer alloc]initWithContentsOfURL:soundUrl error:nil];
+        self.audioPlayDONG.numberOfLoops = 0;
+        
+    }
+    return _audioPlayDONG;
+}
+
+- (NSTimer *)timer{
+    
+    if (!_timer) {
+        _timer = [NSTimer scheduledTimerWithTimeInterval:self.timerLength target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
+        [[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];
+    }
+    return _timer;
+}
+
+- (void)timerAction {
+    
+    if (self.currentNo == self.totalNoCount) {
+        // 停止定时器
+        [self stopPlay];
+        if (self.callback) {
+            self.callback(NO);
+        }
+        return;
+    }
+    
+    if (_beatType == MetronomeType1V4) {
+        [self.audioPlayDI play];
+    }
+    else {
+        
+        if (_beatType == MetronomeType2V4 && self.currentNo %2 == 0) {
+            [self.audioPlayDI play];
+        }else if (_beatType == MetronomeType3V4 && self.currentNo %3 == 0){
+            [self.audioPlayDI play];
+        }else if (_beatType == MetronomeType4V4 && self.currentNo %4 == 0){
+            [self.audioPlayDI play];
+        }else if (_beatType == MetronomeType3V8 && self.currentNo %3 == 0){
+            [self.audioPlayDI play];
+        }else if (_beatType == MetronomeType6V8 && self.currentNo %6 == 0){
+            [self.audioPlayDI play];
+        }else{
+            [self.audioPlayDONG play];
+        }
+    }
+    
+    [self updateSpotViewHeightState:self.currentNo];
+    self.currentNo++;
+}
+
+
+#pragma mark -- 重置定时器
+- (void)resetTimer{
+    [_timer invalidate];
+    _timer = nil;
+    [self.timer setFireDate:[NSDate distantPast]];
+    
+}
+
+- (void)removeAll{
+    
+    if (_timer) {
+        [_timer invalidate];
+        _timer = nil;
+    }
+    
+    if (_audioPlayDI) {
+        _audioPlayDI = nil;
+    }
+    
+    if (_audioPlayDONG) {
+        _audioPlayDONG = nil;
+    }
+}
+/*
+// Only override drawRect: if you perform custom drawing.
+// An empty implementation adversely affects performance during animation.
+- (void)drawRect:(CGRect)rect {
+    // Drawing code
+}
+*/
+
+@end

+ 42 - 0
KulexiuForStudent/KulexiuForStudent/Module/CloudEngine/View/beat/KSCloudBeatView.xib

@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
+    <device id="retina6_1" orientation="portrait" appearance="light"/>
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <objects>
+        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
+        <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
+        <view contentMode="scaleToFill" id="iN0-l3-epB" customClass="KSCloudBeatView">
+            <rect key="frame" x="0.0" y="0.0" width="962" height="447"/>
+            <autoresizingMask key="autoresizingMask"/>
+            <subviews>
+                <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Nw6-6b-dtX">
+                    <rect key="frame" x="898" y="383" width="44" height="44"/>
+                    <constraints>
+                        <constraint firstAttribute="height" constant="44" id="50I-5Q-KEi"/>
+                        <constraint firstAttribute="width" constant="44" id="Fq3-2D-x47"/>
+                    </constraints>
+                    <state key="normal" image="cancle_button"/>
+                    <connections>
+                        <action selector="canclePlay:" destination="iN0-l3-epB" eventType="touchUpInside" id="A8a-hZ-y7x"/>
+                    </connections>
+                </button>
+            </subviews>
+            <color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.52000000000000002" colorSpace="custom" customColorSpace="sRGB"/>
+            <constraints>
+                <constraint firstAttribute="trailing" secondItem="Nw6-6b-dtX" secondAttribute="trailing" constant="20" id="0tM-AI-iEc"/>
+                <constraint firstAttribute="bottom" secondItem="Nw6-6b-dtX" secondAttribute="bottom" constant="20" id="jly-MO-n7X"/>
+            </constraints>
+            <nil key="simulatedTopBarMetrics"/>
+            <nil key="simulatedBottomBarMetrics"/>
+            <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
+            <point key="canvasLocation" x="473.91304347826093" y="-50.558035714285715"/>
+        </view>
+    </objects>
+    <resources>
+        <image name="cancle_button" width="20" height="20"/>
+    </resources>
+</document>

+ 2 - 0
KulexiuForStudent/KulexiuForStudent/Module/Course/Controller/CourseViewController.m

@@ -86,6 +86,8 @@
     }
 }
 
+
+
 - (void)configUI {
     [self.view addSubview:self.navHeadView];
     [self.navHeadView mas_makeConstraints:^(MASConstraintMaker *make) {

+ 20 - 15
KulexiuForStudent/KulexiuForStudent/Module/Home/Controller/HomeViewController.m

@@ -394,9 +394,11 @@
 }
 
 - (void)requestCourseInfo {
+    NSLog(@"-----1");
     [self showhud];
     [KSNetworkingManager homeQueryLiveAndVideo:KS_GET success:^(NSDictionary * _Nonnull dic) {
         [self removehub];
+        NSLog(@"-----2");
         [self.tableView.mj_header endRefreshing];
         if ([dic integerValueForKey:@"code"] == 200 && [dic boolValueForKey:@"status"]) {
             NSDictionary *sourceDic = [dic dictionaryValueForKey:@"data"];
@@ -461,9 +463,11 @@
         self.courseCount = self.liveCourseArray.count >= self.videoCourseArray.count ? self.videoCourseArray.count : self.liveCourseArray.count;
         // 根据课程返回数据显示高度
         self.courseViewHeight = [self.courseView getViewHeightWithCount:self.courseCount];
+        self.courseView.hidden = NO;
     }
     else {
         self.courseViewHeight = CGFLOAT_MIN;
+        self.courseView.hidden = YES;
     }
     [self.courseView mas_updateConstraints:^(MASConstraintMaker *make) {
         make.height.mas_equalTo(self.courseViewHeight);
@@ -1251,21 +1255,22 @@
 
 - (void)showNewsWithSource:(HomeMessageModel *)sourceModel {
     // 登录之后才弹窗
-    if ([self checkIsLoginToLoginView:NO]) {
-        NSString *useId = UserDefault(UIDKey);
-        NSMutableArray *newArray = UserDefault(useId);
-        if (newArray.count) {
-            if ([newArray containsObject:sourceModel.coverImage]) {
-                return;
-            }
-            else {
-                [self displayAlert:sourceModel];
-            }
-        }
-        else {
-            [self displayAlert:sourceModel];
-        }
-    }
+//    if ([self checkIsLoginToLoginView:NO]) {
+//        NSString *useId = UserDefault(UIDKey);
+//        NSMutableArray *newArray = UserDefault(useId);
+//        if (newArray.count) {
+//            if ([newArray containsObject:sourceModel.coverImage]) {
+//                return;
+//            }
+//            else {
+//                [self displayAlert:sourceModel];
+//            }
+//        }
+//        else {
+//            [self displayAlert:sourceModel];
+//        }
+//    }
+    [self displayAlert:sourceModel];
 }
 
 - (void)displayAlert:(HomeMessageModel *)sourceModel {

+ 17 - 1
KulexiuForStudent/KulexiuForStudent/Module/Live/Controller/LiveVideoRoomViewController.m

@@ -28,7 +28,7 @@ typedef NS_ENUM(NSInteger, MICSTATUS) {
     MICSTATUS_CONNECTING, // 连麦中
 };
 
-@interface LiveVideoRoomViewController ()<RCRTCRoomEventDelegate,RCRTCStatusReportDelegate,UIGestureRecognizerDelegate,KSChatInputBarControlDelegate,UITableViewDataSource,UITableViewDelegate,LiveroomTimeManagerDelegate>
+@interface LiveVideoRoomViewController ()<RCRTCEngineEventDelegate,RCRTCRoomEventDelegate,RCRTCStatusReportDelegate,UIGestureRecognizerDelegate,KSChatInputBarControlDelegate,UITableViewDataSource,UITableViewDelegate,LiveroomTimeManagerDelegate>
 
 @property (nonatomic, strong) LiveroomTimeManager *timeManager;
 // 音频配置
@@ -1245,6 +1245,21 @@ static int clickPraiseBtnTimes  = 0;
         }
     }
 }
+
+#pragma mark ----- RCRTCEngineEventDelegate
+- (void)didRTCConnectionStateChanged:(RCRTCConnectionState)state {
+    if (state == RCRTCConnectionStateConnecting) {
+        NSLog(@"--------RCRTCConnectionStateConnecting");
+    }
+    else if (state == RCRTCConnectionStateConnected) {
+        NSLog(@"--------RCRTCConnectionStateConnected");
+    }
+}
+
+#pragma mark ----- RCRTCStatusReportDelegate
+- (void)didReportStatusForm:(RCRTCStatusForm *)form {
+    NSLog(@"%@",form.description);
+}
 #pragma mark --- lazying
 
 - (KSChatVideoView *)videoView {
@@ -1368,6 +1383,7 @@ static int clickPraiseBtnTimes  = 0;
     if (!_engine) {
         _engine = [RCRTCEngine sharedInstance];
         [_engine setStatusReportDelegate:self];
+        [_engine setDelegate:self];
     }
     return _engine;
 }

+ 3 - 0
KulexiuForStudent/KulexiuForStudent/Module/Mine/Controller/MineViewController.m

@@ -130,6 +130,9 @@
             break;
         case MINEVIEWTYPE_MUSIC:
         {
+            KSBaseWKWebViewController *ctrl = [[KSBaseWKWebViewController alloc] init];
+            ctrl.url = [NSString stringWithFormat:@"%@%@",WEBHOST,@"/#/music-personal"];
+            [self.navigationController pushViewController:ctrl animated:YES];
             
         }
             break;

+ 2 - 2
KulexiuForStudent/KulexiuForStudent/Module/Mine/Setting/View/AboutUsBodyView.m

@@ -20,8 +20,8 @@
 - (void)awakeFromNib {
     [super awakeFromNib];
     self.versionLabel.text = [NSString stringWithFormat:@"版本号%@",[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"]];
-    self.phoneLabel.text = @"";
-    self.emailLabel.text = @"dyme2002@dayaedu.com";
+    self.phoneLabel.text = @"4008851569";
+    self.emailLabel.text = @"753761527@qq.com";
 }
 
 + (instancetype)shareInstance {

+ 13 - 2
KulexiuForStudent/KulexiuForStudent/Module/Mine/Setting/View/AboutUsBodyView.xib

@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="19529" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
     <device id="retina6_1" orientation="portrait" appearance="light"/>
     <dependencies>
         <deployment identifier="iOS"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19519"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
     </dependencies>
     <objects>
@@ -109,12 +109,23 @@
                         </userDefinedRuntimeAttribute>
                     </userDefinedRuntimeAttributes>
                 </view>
+                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="w7n-r0-QfZ">
+                    <rect key="frame" x="50" y="335" width="314" height="29"/>
+                    <string key="text">Copyright©2021-2022
+酷乐秀 colexiu.com 版权所有</string>
+                    <fontDescription key="fontDescription" type="system" pointSize="12"/>
+                    <color key="textColor" red="0.59999999999999998" green="0.59999999999999998" blue="0.59999999999999998" alpha="1" colorSpace="calibratedRGB"/>
+                    <nil key="highlightedColor"/>
+                </label>
             </subviews>
             <color key="backgroundColor" red="0.96470588235294119" green="0.97254901960784312" blue="0.97647058823529409" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
             <constraints>
+                <constraint firstAttribute="trailing" secondItem="w7n-r0-QfZ" secondAttribute="trailing" constant="50" id="3se-CV-iN8"/>
                 <constraint firstItem="QYL-JY-4ew" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" constant="15" id="6Se-bm-pwg"/>
+                <constraint firstItem="w7n-r0-QfZ" firstAttribute="top" secondItem="QYL-JY-4ew" secondAttribute="bottom" constant="20" id="ETt-dR-gmw"/>
                 <constraint firstItem="QYL-JY-4ew" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="14" id="FG5-oZ-Yh6"/>
                 <constraint firstAttribute="trailing" secondItem="QYL-JY-4ew" secondAttribute="trailing" constant="14" id="JEg-YC-TgD"/>
+                <constraint firstItem="w7n-r0-QfZ" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="50" id="ldb-VQ-sub"/>
             </constraints>
             <nil key="simulatedTopBarMetrics"/>
             <nil key="simulatedBottomBarMetrics"/>

二进制
KulexiuForStudent/KulexiuForStudent/SoundFontFile/synthgms.sf2


二进制
KulexiuForStudent/build/Debug-iphonesimulator/AFNetworking/AFNetworking.framework/AFNetworking


二进制
KulexiuForStudent/build/Debug-iphonesimulator/AFNetworking/AFNetworking.framework/Info.plist


+ 1 - 1
KulexiuForStudent/build/Debug-iphonesimulator/AFNetworking/AFNetworking.framework/_CodeSignature/CodeResources

@@ -82,7 +82,7 @@
 		</data>
 		<key>Info.plist</key>
 		<data>
-		9vKeNOewfuEdQqyz4IJF0GNe8No=
+		8ltcqMhY7y5p/JK1ZvlCyHZ057Q=
 		</data>
 		<key>Modules/module.modulemap</key>
 		<data>

二进制
KulexiuForStudent/build/Debug-iphonesimulator/CHIPageControl/CHIPageControl.framework/CHIPageControl


二进制
KulexiuForStudent/build/Debug-iphonesimulator/CHIPageControl/CHIPageControl.framework/Info.plist


+ 1 - 1
KulexiuForStudent/build/Debug-iphonesimulator/CHIPageControl/CHIPageControl.framework/_CodeSignature/CodeResources

@@ -14,7 +14,7 @@
 		</data>
 		<key>Info.plist</key>
 		<data>
-		BwZJEa7TgsPbybc60jO/6sy2Oec=
+		34t6v65wi914Xv7f88pnHPOSTGc=
 		</data>
 		<key>Modules/CHIPageControl.swiftmodule/Project/x86_64-apple-ios-simulator.swiftsourceinfo</key>
 		<data>

二进制
KulexiuForStudent/build/Debug-iphonesimulator/IQKeyboardManager/IQKeyboardManager.framework/IQKeyboardManager


二进制
KulexiuForStudent/build/Debug-iphonesimulator/IQKeyboardManager/IQKeyboardManager.framework/Info.plist


+ 1 - 1
KulexiuForStudent/build/Debug-iphonesimulator/IQKeyboardManager/IQKeyboardManager.framework/_CodeSignature/CodeResources

@@ -62,7 +62,7 @@
 		</data>
 		<key>Info.plist</key>
 		<data>
-		Scni0Dr0SiBj+LXlY3W7kMUiCtc=
+		9Bv+VvzClTsjAG9HsNVnmT3mnOI=
 		</data>
 		<key>Modules/module.modulemap</key>
 		<data>

二进制
KulexiuForStudent/build/Debug-iphonesimulator/JXCategoryView/JXCategoryView.framework/Info.plist


二进制
KulexiuForStudent/build/Debug-iphonesimulator/JXCategoryView/JXCategoryView.framework/JXCategoryView


+ 1 - 1
KulexiuForStudent/build/Debug-iphonesimulator/JXCategoryView/JXCategoryView.framework/_CodeSignature/CodeResources

@@ -182,7 +182,7 @@
 		</data>
 		<key>Info.plist</key>
 		<data>
-		9truIsRwDOXnlMCZ64X32OMwUxw=
+		2NNshvHx4PVNuI4fPthtOS53vOE=
 		</data>
 		<key>Modules/module.modulemap</key>
 		<data>

二进制
KulexiuForStudent/build/Debug-iphonesimulator/JXPagingView/JXPagingView.framework/Info.plist


二进制
KulexiuForStudent/build/Debug-iphonesimulator/JXPagingView/JXPagingView.framework/JXPagingView


部分文件因为文件数量过多而无法显示