Просмотр исходного кода

Merge branch 'feature/ios_ws_im_image' into 'develop'

ios IM消息增加语音消息、图片消息等功能

See merge request o2oa/o2oa!749
楼国栋 5 лет назад
Родитель
Сommit
a230661b87
65 измененных файлов с 3294 добавлено и 202 удалено
  1. 130 53
      o2ios/O2Platform.xcodeproj/project.pbxproj
  2. 4 6
      o2ios/O2Platform/App/Cms-内容管理/c/CMSItemDetailViewController.swift
  3. 47 28
      o2ios/O2Platform/App/File-云盘/c/FileBSImagePickerViewController.swift
  4. 2 3
      o2ios/O2Platform/App/File-云盘/c/MainFileViewController.swift
  5. 333 23
      o2ios/O2Platform/App/IM-聊天/IMChatViewController.swift
  6. 60 5
      o2ios/O2Platform/App/IM-聊天/IMChatViewController.xib
  7. 11 12
      o2ios/O2Platform/App/IM-聊天/IMConversationListViewController.swift
  8. 19 0
      o2ios/O2Platform/App/IM-聊天/IMViewModel.swift
  9. 6 0
      o2ios/O2Platform/App/IM-聊天/Model/IMConversationInfo.swift
  10. 71 0
      o2ios/O2Platform/App/IM-聊天/O2IM.swift
  11. 31 0
      o2ios/O2Platform/App/IM-聊天/View/IMAudioView.swift
  12. 57 0
      o2ios/O2Platform/App/IM-聊天/View/IMAudioView.xib
  13. 96 0
      o2ios/O2Platform/App/IM-聊天/View/IMChatAudioView.swift
  14. 57 0
      o2ios/O2Platform/App/IM-聊天/View/IMChatAudioView.xib
  15. 102 7
      o2ios/O2Platform/App/IM-聊天/View/IMChatMessageSendViewCell.swift
  16. 130 28
      o2ios/O2Platform/App/IM-聊天/View/IMChatMessageViewCell.swift
  17. 4 3
      o2ios/O2Platform/App/IM-聊天/View/IMConversationItemCell.swift
  18. 1 1
      o2ios/O2Platform/App/IM-聊天/View/IMConversationItemCell.xib
  19. 14 7
      o2ios/O2Platform/App/Work-工作/c/TodoTaskDetailViewController.swift
  20. 6 0
      o2ios/O2Platform/Assets.xcassets/im/Contents.json
  21. 22 0
      o2ios/O2Platform/Assets.xcassets/im/chat_audio_play_Normal.imageset/Contents.json
  22. BIN
      o2ios/O2Platform/Assets.xcassets/im/chat_audio_play_Normal.imageset/chat_icon_audio_play_Normal@2x.png
  23. BIN
      o2ios/O2Platform/Assets.xcassets/im/chat_audio_play_Normal.imageset/chat_icon_audio_play_Normal@3x.png
  24. 0 0
      o2ios/O2Platform/Assets.xcassets/im/chat_bubble_incomming.imageset/Contents.json
  25. 0 0
      o2ios/O2Platform/Assets.xcassets/im/chat_bubble_incomming.imageset/chat_bubble_incomming@2x.png
  26. 0 0
      o2ios/O2Platform/Assets.xcassets/im/chat_bubble_incomming.imageset/chat_bubble_incomming@3x.png
  27. 0 0
      o2ios/O2Platform/Assets.xcassets/im/chat_bubble_outgoing.imageset/Contents.json
  28. 0 0
      o2ios/O2Platform/Assets.xcassets/im/chat_bubble_outgoing.imageset/chat_bubble_outgoing@2x.png
  29. 0 0
      o2ios/O2Platform/Assets.xcassets/im/chat_bubble_outgoing.imageset/chat_bubble_outgoing@3x.png
  30. 21 0
      o2ios/O2Platform/Assets.xcassets/im/chat_camera.imageset/Contents.json
  31. BIN
      o2ios/O2Platform/Assets.xcassets/im/chat_camera.imageset/chat_camera.png
  32. 21 0
      o2ios/O2Platform/Assets.xcassets/im/chat_image.imageset/Contents.json
  33. BIN
      o2ios/O2Platform/Assets.xcassets/im/chat_image.imageset/chat_image@2x.png
  34. 21 0
      o2ios/O2Platform/Assets.xcassets/im/chat_img.imageset/Contents.json
  35. BIN
      o2ios/O2Platform/Assets.xcassets/im/chat_img.imageset/chat_img.png
  36. 21 0
      o2ios/O2Platform/Assets.xcassets/im/chat_location.imageset/Contents.json
  37. BIN
      o2ios/O2Platform/Assets.xcassets/im/chat_location.imageset/chat_location.png
  38. 21 0
      o2ios/O2Platform/Assets.xcassets/im/chat_mic.imageset/Contents.json
  39. BIN
      o2ios/O2Platform/Assets.xcassets/im/chat_mic.imageset/chat_mic.png
  40. 0 0
      o2ios/O2Platform/Assets.xcassets/im/group_default.imageset/Contents.json
  41. 0 0
      o2ios/O2Platform/Assets.xcassets/im/group_default.imageset/group_default@2x.png
  42. 15 2
      o2ios/O2Platform/Extension/UIViewController+Extension.swift
  43. 2 5
      o2ios/O2Platform/Framework/JMessage/ChatModule/Chat/ViewController/JCChatViewController.swift
  44. 23 2
      o2ios/O2Platform/Framework/O2API/Communicate/CommunicateAPI.swift
  45. 6 2
      o2ios/O2Platform/Framework/Utils/FileUtil.swift
  46. 17 0
      o2ios/O2Platform/Framework/lame/AmrCodec.h
  47. 15 0
      o2ios/O2Platform/Framework/lame/ConvertMp3.h
  48. 66 0
      o2ios/O2Platform/Framework/lame/ConvertMp3.m
  49. 1323 0
      o2ios/O2Platform/Framework/lame/lame.h
  50. BIN
      o2ios/O2Platform/Framework/lame/libmp3lame.a
  51. BIN
      o2ios/O2Platform/Framework/lame/opencore-amr/lib/libopencore-amrnb.a
  52. BIN
      o2ios/O2Platform/Framework/lame/opencore-amr/lib/libopencore-amrwb.a
  53. 34 0
      o2ios/O2Platform/Framework/lame/opencore-amr/opencore-amrnb/interf_dec.h
  54. 50 0
      o2ios/O2Platform/Framework/lame/opencore-amr/opencore-amrnb/interf_enc.h
  55. 36 0
      o2ios/O2Platform/Framework/lame/opencore-amr/opencore-amrwb/dec_if.h
  56. 33 0
      o2ios/O2Platform/Framework/lame/opencore-amr/opencore-amrwb/if_rom.h
  57. BIN
      o2ios/O2Platform/Framework/lame/vo-amrwbenc/lib/libvo-amrwbenc.a
  58. 34 0
      o2ios/O2Platform/Framework/lame/vo-amrwbenc/vo-amrwbenc/enc_if.h
  59. 78 0
      o2ios/O2Platform/Manager/AudioPlayerManager.swift
  60. 235 0
      o2ios/O2Platform/Manager/O2RecordVoiceManager.swift
  61. 4 0
      o2ios/O2Platform/O2Platform-Bridging-Header.h
  62. 2 2
      o2ios/O2Platform/UI/o2JsApi/O2BaseJsMessageHandler.swift
  63. 8 0
      o2ios/O2Platform/config/O2URLContext.swift
  64. 1 1
      o2ios/Podfile
  65. 4 12
      o2ios/Podfile.lock

+ 130 - 53
o2ios/O2Platform.xcodeproj/project.pbxproj

@@ -142,6 +142,17 @@
 		B14E0C0C2484F1F0008AF6AE /* O2WebsocketManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B14E0C0B2484F1F0008AF6AE /* O2WebsocketManager.swift */; };
 		B1534E3F21F712EA00CC8C35 /* O2DemoAlertView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1534E3E21F712EA00CC8C35 /* O2DemoAlertView.swift */; };
 		B158E95E215DD3F500AB2727 /* AIConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = B158E95D215DD3F500AB2727 /* AIConstants.swift */; };
+		B15BE0BE2499BCEF008CD1DB /* O2RecordVoiceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B15BE0BD2499BCEF008CD1DB /* O2RecordVoiceManager.swift */; };
+		B15BE0EE2499DD7E008CD1DB /* IMChatAudioView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B15BE0ED2499DD7E008CD1DB /* IMChatAudioView.swift */; };
+		B15BE0F02499DD90008CD1DB /* IMChatAudioView.xib in Resources */ = {isa = PBXBuildFile; fileRef = B15BE0EF2499DD90008CD1DB /* IMChatAudioView.xib */; };
+		B15BE106249A06BF008CD1DB /* libvo-amrwbenc.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B15BE0F7249A06BE008CD1DB /* libvo-amrwbenc.a */; };
+		B15BE107249A06BF008CD1DB /* ConvertMp3.m in Sources */ = {isa = PBXBuildFile; fileRef = B15BE0F8249A06BE008CD1DB /* ConvertMp3.m */; };
+		B15BE108249A06BF008CD1DB /* libmp3lame.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B15BE0F9249A06BE008CD1DB /* libmp3lame.a */; };
+		B15BE109249A06BF008CD1DB /* libopencore-amrwb.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B15BE100249A06BE008CD1DB /* libopencore-amrwb.a */; };
+		B15BE10A249A06BF008CD1DB /* libopencore-amrnb.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B15BE101249A06BE008CD1DB /* libopencore-amrnb.a */; };
+		B15BE10C249A0EFE008CD1DB /* IMAudioView.xib in Resources */ = {isa = PBXBuildFile; fileRef = B15BE10B249A0EFE008CD1DB /* IMAudioView.xib */; };
+		B15BE10E249A107C008CD1DB /* IMAudioView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B15BE10D249A107C008CD1DB /* IMAudioView.swift */; };
+		B15BE111249A6002008CD1DB /* AudioPlayerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B15BE110249A6002008CD1DB /* AudioPlayerManager.swift */; };
 		B15D26EC235EF7480092F8B8 /* genstrings.sh in Resources */ = {isa = PBXBuildFile; fileRef = B15D26EB235EF7480092F8B8 /* genstrings.sh */; };
 		B15F80F3210EB93000B81F35 /* OOCalendarMainMonthViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B15F80F2210EB93000B81F35 /* OOCalendarMainMonthViewController.swift */; };
 		B15F8129210EF15E00B81F35 /* OOCalendarEventViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B15F8128210EF15E00B81F35 /* OOCalendarEventViewController.swift */; };
@@ -1030,13 +1041,6 @@
 			remoteGlobalIDString = 5F72A30590D0D18EC2C2BBC2D902C6D1;
 			remoteInfo = BetterSegmentedControl;
 		};
-		B10AC59221058DAE00179587 /* PBXContainerItemProxy */ = {
-			isa = PBXContainerItemProxy;
-			containerPortal = E4E7755421017DB4006ED7FC /* Pods.xcodeproj */;
-			proxyType = 2;
-			remoteGlobalIDString = E18B1C0DEBD04E12A5DC8B40B7BF5150;
-			remoteInfo = BSGridCollectionViewLayout;
-		};
 		B10AC59421058DAE00179587 /* PBXContainerItemProxy */ = {
 			isa = PBXContainerItemProxy;
 			containerPortal = E4E7755421017DB4006ED7FC /* Pods.xcodeproj */;
@@ -1044,13 +1048,6 @@
 			remoteGlobalIDString = D13C7C5DC1991163C85D47D9B72B42DD;
 			remoteInfo = BSImagePicker;
 		};
-		B10AC59621058DAE00179587 /* PBXContainerItemProxy */ = {
-			isa = PBXContainerItemProxy;
-			containerPortal = E4E7755421017DB4006ED7FC /* Pods.xcodeproj */;
-			proxyType = 2;
-			remoteGlobalIDString = 797083F5F27AFF01045BE4809698B5C1;
-			remoteInfo = "BSImagePicker-BSImagePicker";
-		};
 		B10AC59A21058DAE00179587 /* PBXContainerItemProxy */ = {
 			isa = PBXContainerItemProxy;
 			containerPortal = E4E7755421017DB4006ED7FC /* Pods.xcodeproj */;
@@ -1261,13 +1258,6 @@
 			remoteGlobalIDString = 85979644862830BD1DFFD1D60F0D64AE;
 			remoteInfo = RxAtomic;
 		};
-		B1BC8CD5216B1F0B00AF571F /* PBXContainerItemProxy */ = {
-			isa = PBXContainerItemProxy;
-			containerPortal = E4E7755421017DB4006ED7FC /* Pods.xcodeproj */;
-			proxyType = 2;
-			remoteGlobalIDString = 8B824035EFF627D7343420A8E0BC044B;
-			remoteInfo = BSImageView;
-		};
 		B1F79C77233B5F9B004D0AEE /* PBXContainerItemProxy */ = {
 			isa = PBXContainerItemProxy;
 			containerPortal = E4E7755421017DB4006ED7FC /* Pods.xcodeproj */;
@@ -1469,6 +1459,25 @@
 		B14E0C0B2484F1F0008AF6AE /* O2WebsocketManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = O2WebsocketManager.swift; sourceTree = "<group>"; };
 		B1534E3E21F712EA00CC8C35 /* O2DemoAlertView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = O2DemoAlertView.swift; sourceTree = "<group>"; };
 		B158E95D215DD3F500AB2727 /* AIConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AIConstants.swift; sourceTree = "<group>"; };
+		B15BE0BD2499BCEF008CD1DB /* O2RecordVoiceManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = O2RecordVoiceManager.swift; sourceTree = "<group>"; };
+		B15BE0ED2499DD7E008CD1DB /* IMChatAudioView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IMChatAudioView.swift; sourceTree = "<group>"; };
+		B15BE0EF2499DD90008CD1DB /* IMChatAudioView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = IMChatAudioView.xib; sourceTree = "<group>"; };
+		B15BE0F2249A06BE008CD1DB /* AmrCodec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AmrCodec.h; sourceTree = "<group>"; };
+		B15BE0F5249A06BE008CD1DB /* enc_if.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = enc_if.h; sourceTree = "<group>"; };
+		B15BE0F7249A06BE008CD1DB /* libvo-amrwbenc.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libvo-amrwbenc.a"; sourceTree = "<group>"; };
+		B15BE0F8249A06BE008CD1DB /* ConvertMp3.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ConvertMp3.m; sourceTree = "<group>"; };
+		B15BE0F9249A06BE008CD1DB /* libmp3lame.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libmp3lame.a; sourceTree = "<group>"; };
+		B15BE0FA249A06BE008CD1DB /* ConvertMp3.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ConvertMp3.h; sourceTree = "<group>"; };
+		B15BE0FD249A06BE008CD1DB /* if_rom.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = if_rom.h; sourceTree = "<group>"; };
+		B15BE0FE249A06BE008CD1DB /* dec_if.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = dec_if.h; sourceTree = "<group>"; };
+		B15BE100249A06BE008CD1DB /* libopencore-amrwb.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libopencore-amrwb.a"; sourceTree = "<group>"; };
+		B15BE101249A06BE008CD1DB /* libopencore-amrnb.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libopencore-amrnb.a"; sourceTree = "<group>"; };
+		B15BE103249A06BE008CD1DB /* interf_dec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = interf_dec.h; sourceTree = "<group>"; };
+		B15BE104249A06BE008CD1DB /* interf_enc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = interf_enc.h; sourceTree = "<group>"; };
+		B15BE105249A06BE008CD1DB /* lame.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = lame.h; sourceTree = "<group>"; };
+		B15BE10B249A0EFE008CD1DB /* IMAudioView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = IMAudioView.xib; sourceTree = "<group>"; };
+		B15BE10D249A107C008CD1DB /* IMAudioView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IMAudioView.swift; sourceTree = "<group>"; };
+		B15BE110249A6002008CD1DB /* AudioPlayerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayerManager.swift; sourceTree = "<group>"; };
 		B15D26BF235EF0850092F8B8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/CloudFile.storyboard; sourceTree = "<group>"; };
 		B15D26EA235EF0940092F8B8 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/CloudFile.strings; sourceTree = "<group>"; };
 		B15D26EB235EF7480092F8B8 /* genstrings.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = genstrings.sh; sourceTree = "<group>"; };
@@ -2393,6 +2402,7 @@
 				B1BC8CDA216B3D5F00AF571F /* libc++.tbd in Frameworks */,
 				B130E648223B774D00B68354 /* image_picker.framework in Frameworks */,
 				E4D23119209EF74D00837868 /* libssl.a in Frameworks */,
+				B15BE106249A06BF008CD1DB /* libvo-amrwbenc.a in Frameworks */,
 				E4C24A222080A44300E426B0 /* CFNetwork.framework in Frameworks */,
 				B1AE4E2621A3DCC100183FCD /* O2OA_Auth_SDK.framework in Frameworks */,
 				B130E64A223B774D00B68354 /* shared_preferences.framework in Frameworks */,
@@ -2403,13 +2413,16 @@
 				E40958C7208051DE000FECC3 /* CoreTelephony.framework in Frameworks */,
 				E4418C5F1DDC1AC80066348D /* CoreGraphics.framework in Frameworks */,
 				E4375B12207CABAB0065A880 /* SystemConfiguration.framework in Frameworks */,
+				B15BE108249A06BF008CD1DB /* libmp3lame.a in Frameworks */,
 				B19BCE8A2228272500BD454C /* App.framework in Frameworks */,
 				B130E649223B774D00B68354 /* path_provider.framework in Frameworks */,
 				E4375B1A207CABE90065A880 /* AudioToolbox.framework in Frameworks */,
+				B15BE10A249A06BF008CD1DB /* libopencore-amrnb.a in Frameworks */,
 				E4375B14207CABB30065A880 /* AddressBook.framework in Frameworks */,
 				E4375B18207CABD20065A880 /* AssetsLibrary.framework in Frameworks */,
 				E4375B16207CABC90065A880 /* CoreMotion.framework in Frameworks */,
 				E4375B10207CABA30065A880 /* CoreLocation.framework in Frameworks */,
+				B15BE109249A06BF008CD1DB /* libopencore-amrwb.a in Frameworks */,
 				E4375B0E207CAB980065A880 /* CoreData.framework in Frameworks */,
 				E4375B0C207CAB860065A880 /* libz.tbd in Frameworks */,
 				E4375B08207CAB770065A880 /* libsqlite3.0.tbd in Frameworks */,
@@ -2534,10 +2547,7 @@
 				B10AC58D21058DAE00179587 /* AlamofireNetworkActivityIndicator.framework */,
 				B10AC58F21058DAE00179587 /* AlamofireObjectMapper.framework */,
 				B10AC59121058DAE00179587 /* BetterSegmentedControl.framework */,
-				B10AC59321058DAE00179587 /* BSGridCollectionViewLayout.framework */,
 				B10AC59521058DAE00179587 /* BSImagePicker.framework */,
-				B10AC59721058DAE00179587 /* BSImagePicker.bundle */,
-				B1BC8CD6216B1F0B00AF571F /* BSImageView.framework */,
 				B10AC59B21058DAE00179587 /* Charts.framework */,
 				B1F79C78233B5F9B004D0AEE /* Chrysan.framework */,
 				B1F79C7A233B5F9B004D0AEE /* Chrysan.bundle */,
@@ -2621,6 +2631,10 @@
 				B1489CAD2491FF13009EE9FD /* IMChatEmojiBarView.xib */,
 				B1489CAF2492045D009EE9FD /* IMChatEmojiItemCell.swift */,
 				B1489CB02492045D009EE9FD /* IMChatEmojiItemCell.xib */,
+				B15BE0ED2499DD7E008CD1DB /* IMChatAudioView.swift */,
+				B15BE0EF2499DD90008CD1DB /* IMChatAudioView.xib */,
+				B15BE10B249A0EFE008CD1DB /* IMAudioView.xib */,
+				B15BE10D249A107C008CD1DB /* IMAudioView.swift */,
 			);
 			path = View;
 			sourceTree = "<group>";
@@ -2692,6 +2706,82 @@
 			path = vm;
 			sourceTree = "<group>";
 		};
+		B15BE0F1249A06BE008CD1DB /* lame */ = {
+			isa = PBXGroup;
+			children = (
+				B15BE0F2249A06BE008CD1DB /* AmrCodec.h */,
+				B15BE0F3249A06BE008CD1DB /* vo-amrwbenc */,
+				B15BE0F8249A06BE008CD1DB /* ConvertMp3.m */,
+				B15BE0F9249A06BE008CD1DB /* libmp3lame.a */,
+				B15BE0FA249A06BE008CD1DB /* ConvertMp3.h */,
+				B15BE0FB249A06BE008CD1DB /* opencore-amr */,
+				B15BE105249A06BE008CD1DB /* lame.h */,
+			);
+			path = lame;
+			sourceTree = "<group>";
+		};
+		B15BE0F3249A06BE008CD1DB /* vo-amrwbenc */ = {
+			isa = PBXGroup;
+			children = (
+				B15BE0F4249A06BE008CD1DB /* vo-amrwbenc */,
+				B15BE0F6249A06BE008CD1DB /* lib */,
+			);
+			path = "vo-amrwbenc";
+			sourceTree = "<group>";
+		};
+		B15BE0F4249A06BE008CD1DB /* vo-amrwbenc */ = {
+			isa = PBXGroup;
+			children = (
+				B15BE0F5249A06BE008CD1DB /* enc_if.h */,
+			);
+			path = "vo-amrwbenc";
+			sourceTree = "<group>";
+		};
+		B15BE0F6249A06BE008CD1DB /* lib */ = {
+			isa = PBXGroup;
+			children = (
+				B15BE0F7249A06BE008CD1DB /* libvo-amrwbenc.a */,
+			);
+			path = lib;
+			sourceTree = "<group>";
+		};
+		B15BE0FB249A06BE008CD1DB /* opencore-amr */ = {
+			isa = PBXGroup;
+			children = (
+				B15BE0FC249A06BE008CD1DB /* opencore-amrwb */,
+				B15BE0FF249A06BE008CD1DB /* lib */,
+				B15BE102249A06BE008CD1DB /* opencore-amrnb */,
+			);
+			path = "opencore-amr";
+			sourceTree = "<group>";
+		};
+		B15BE0FC249A06BE008CD1DB /* opencore-amrwb */ = {
+			isa = PBXGroup;
+			children = (
+				B15BE0FD249A06BE008CD1DB /* if_rom.h */,
+				B15BE0FE249A06BE008CD1DB /* dec_if.h */,
+			);
+			path = "opencore-amrwb";
+			sourceTree = "<group>";
+		};
+		B15BE0FF249A06BE008CD1DB /* lib */ = {
+			isa = PBXGroup;
+			children = (
+				B15BE100249A06BE008CD1DB /* libopencore-amrwb.a */,
+				B15BE101249A06BE008CD1DB /* libopencore-amrnb.a */,
+			);
+			path = lib;
+			sourceTree = "<group>";
+		};
+		B15BE102249A06BE008CD1DB /* opencore-amrnb */ = {
+			isa = PBXGroup;
+			children = (
+				B15BE103249A06BE008CD1DB /* interf_dec.h */,
+				B15BE104249A06BE008CD1DB /* interf_enc.h */,
+			);
+			path = "opencore-amrnb";
+			sourceTree = "<group>";
+		};
 		B165CC9D2241DB9F00373B66 /* DatePickerDialogSwift */ = {
 			isa = PBXGroup;
 			children = (
@@ -2942,6 +3032,8 @@
 				B1E0BAA02378FC01001D741F /* O2JPushManager.swift */,
 				B1ABAA0B237E498C0027EC48 /* O2VersionManager.swift */,
 				B14E0C0B2484F1F0008AF6AE /* O2WebsocketManager.swift */,
+				B15BE0BD2499BCEF008CD1DB /* O2RecordVoiceManager.swift */,
+				B15BE110249A6002008CD1DB /* AudioPlayerManager.swift */,
 			);
 			path = Manager;
 			sourceTree = "<group>";
@@ -3922,6 +4014,7 @@
 		E4B887821D9D48F1002E1A46 /* Framework */ = {
 			isa = PBXGroup;
 			children = (
+				B15BE0F1249A06BE008CD1DB /* lame */,
 				E4B69721207602840062F6E8 /* O2API */,
 				E4B8892E1D9D6308002E1A46 /* Utils */,
 				B142B04E230FB53E00E7D127 /* MagicBytesMimeType */,
@@ -5197,13 +5290,6 @@
 			remoteRef = B10AC59021058DAE00179587 /* PBXContainerItemProxy */;
 			sourceTree = BUILT_PRODUCTS_DIR;
 		};
-		B10AC59321058DAE00179587 /* BSGridCollectionViewLayout.framework */ = {
-			isa = PBXReferenceProxy;
-			fileType = wrapper.framework;
-			path = BSGridCollectionViewLayout.framework;
-			remoteRef = B10AC59221058DAE00179587 /* PBXContainerItemProxy */;
-			sourceTree = BUILT_PRODUCTS_DIR;
-		};
 		B10AC59521058DAE00179587 /* BSImagePicker.framework */ = {
 			isa = PBXReferenceProxy;
 			fileType = wrapper.framework;
@@ -5211,13 +5297,6 @@
 			remoteRef = B10AC59421058DAE00179587 /* PBXContainerItemProxy */;
 			sourceTree = BUILT_PRODUCTS_DIR;
 		};
-		B10AC59721058DAE00179587 /* BSImagePicker.bundle */ = {
-			isa = PBXReferenceProxy;
-			fileType = wrapper.cfbundle;
-			path = BSImagePicker.bundle;
-			remoteRef = B10AC59621058DAE00179587 /* PBXContainerItemProxy */;
-			sourceTree = BUILT_PRODUCTS_DIR;
-		};
 		B10AC59B21058DAE00179587 /* Charts.framework */ = {
 			isa = PBXReferenceProxy;
 			fileType = wrapper.framework;
@@ -5428,13 +5507,6 @@
 			remoteRef = B1AE4E6321A3F53A00183FCD /* PBXContainerItemProxy */;
 			sourceTree = BUILT_PRODUCTS_DIR;
 		};
-		B1BC8CD6216B1F0B00AF571F /* BSImageView.framework */ = {
-			isa = PBXReferenceProxy;
-			fileType = wrapper.framework;
-			path = BSImageView.framework;
-			remoteRef = B1BC8CD5216B1F0B00AF571F /* PBXContainerItemProxy */;
-			sourceTree = BUILT_PRODUCTS_DIR;
-		};
 		B1F79C78233B5F9B004D0AEE /* Chrysan.framework */ = {
 			isa = PBXReferenceProxy;
 			fileType = wrapper.framework;
@@ -5477,6 +5549,7 @@
 				E46E6CA01DD41F5D00AB7561 /* ZSSrightjustify@2x.png in Resources */,
 				E46E6CA91DD41F5D00AB7561 /* ZSStextcolor.png in Resources */,
 				E4C24C1F2085849500E426B0 /* o2Theme.bundle in Resources */,
+				B15BE0F02499DD90008CD1DB /* IMChatAudioView.xib in Resources */,
 				E4B781671DF8E556007B58A9 /* information.storyboard in Resources */,
 				E46E6CAA1DD41F5D00AB7561 /* ZSStextcolor@2x.png in Resources */,
 				E40E24C020B7DA3C009F8BE7 /* OOMeetingRoomTableHeaderView.xib in Resources */,
@@ -5661,6 +5734,7 @@
 				E4C24C49208D7EDE00E426B0 /* OOContactSearchSectionHeaderView.xib in Resources */,
 				E46E6C691DD41F5D00AB7561 /* ZSSbgcolor.png in Resources */,
 				E428AF4620A95FE800D964B9 /* OOAttanceTotalController.xib in Resources */,
+				B15BE10C249A0EFE008CD1DB /* IMAudioView.xib in Resources */,
 				B165CD6B2242093500373B66 /* Info.plist in Resources */,
 				E4C24C48208D7EDE00E426B0 /* OOContactGroupHeaderView.xib in Resources */,
 				E4B888DB1D9D48F1002E1A46 /* 隶变体.ttf in Resources */,
@@ -5722,9 +5796,7 @@
 				"${BUILT_PRODUCTS_DIR}/AlamofireImage/AlamofireImage.framework",
 				"${BUILT_PRODUCTS_DIR}/AlamofireNetworkActivityIndicator/AlamofireNetworkActivityIndicator.framework",
 				"${BUILT_PRODUCTS_DIR}/AlamofireObjectMapper/AlamofireObjectMapper.framework",
-				"${BUILT_PRODUCTS_DIR}/BSGridCollectionViewLayout/BSGridCollectionViewLayout.framework",
 				"${BUILT_PRODUCTS_DIR}/BSImagePicker/BSImagePicker.framework",
-				"${BUILT_PRODUCTS_DIR}/BSImageView/BSImageView.framework",
 				"${BUILT_PRODUCTS_DIR}/BetterSegmentedControl/BetterSegmentedControl.framework",
 				"${BUILT_PRODUCTS_DIR}/Charts/Charts.framework",
 				"${BUILT_PRODUCTS_DIR}/Chrysan/Chrysan.framework",
@@ -5765,9 +5837,7 @@
 				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AlamofireImage.framework",
 				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AlamofireNetworkActivityIndicator.framework",
 				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AlamofireObjectMapper.framework",
-				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/BSGridCollectionViewLayout.framework",
 				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/BSImagePicker.framework",
-				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/BSImageView.framework",
 				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/BetterSegmentedControl.framework",
 				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Charts.framework",
 				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Chrysan.framework",
@@ -6036,6 +6106,7 @@
 				E4C24BB420844F3C00E426B0 /* JCMessageContentViewType.swift in Sources */,
 				E4C24B9420844F3C00E426B0 /* JCGroupSettingCell.swift in Sources */,
 				E40E24D320B7DA3C009F8BE7 /* OOMeetingPersonSelectHeaderView.swift in Sources */,
+				B15BE0BE2499BCEF008CD1DB /* O2RecordVoiceManager.swift in Sources */,
 				B1DB6EA5237E6F2C00D7BA94 /* O2VersionInfoModel.swift in Sources */,
 				E4C24B9E20844F3C00E426B0 /* JCUpdateMemberCell.swift in Sources */,
 				09E02E9A1F1745E700579887 /* ZLTextView.swift in Sources */,
@@ -6098,6 +6169,7 @@
 				B1EE2CD02281771600842F48 /* O2JsApiUtil.swift in Sources */,
 				E4B6975020762EA00062F6E8 /* OOPasswordTextField.swift in Sources */,
 				E491664C1E03C09C006133C5 /* O2Account.swift in Sources */,
+				B15BE111249A6002008CD1DB /* AudioPlayerManager.swift in Sources */,
 				E46E6CBA1DD41F5D00AB7561 /* CYRToken.m in Sources */,
 				E4C24C53208D7EDE00E426B0 /* OOContactGroupHeaderView.swift in Sources */,
 				E4C24BD120844F3C00E426B0 /* JCNoteNameViewController.swift in Sources */,
@@ -6245,6 +6317,7 @@
 				09E02E021F12736C00579887 /* PersonV2.swift in Sources */,
 				B1B0102F23586B34002BF874 /* CFFileTableViewCell.swift in Sources */,
 				E40E24AE20B7DA3C009F8BE7 /* OOMeetingAcceptController.swift in Sources */,
+				B15BE107249A06BF008CD1DB /* ConvertMp3.m in Sources */,
 				09E02E041F127CAF00579887 /* IdentityV2.swift in Sources */,
 				E42E41CE1E6ED13C00C5B5C7 /* CMS_PublishInfo.swift in Sources */,
 				E4B888801D9D48F1002E1A46 /* File.swift in Sources */,
@@ -6265,6 +6338,7 @@
 				E4B888C41D9D48F1002E1A46 /* CollectDeviceData.swift in Sources */,
 				E4B888731D9D48F1002E1A46 /* Company.swift in Sources */,
 				E4C24B9B20844F3C00E426B0 /* JCNetworkTipsCell.swift in Sources */,
+				B15BE10E249A107C008CD1DB /* IMAudioView.swift in Sources */,
 				E45DA8E91DACC11600E0735D /* BaseWebViewUIViewController.swift in Sources */,
 				E424BAD61EE105BB0078DB47 /* O2Logger.swift in Sources */,
 				E491664A1E038D09006133C5 /* O2AlamofireSource.swift in Sources */,
@@ -6316,6 +6390,7 @@
 				E40E24D220B7DA3C009F8BE7 /* OOFormDateIntervalItemView.swift in Sources */,
 				E4184B251DB4F28800FCC907 /* SMobileChangeViewController.swift in Sources */,
 				E40E24B320B7DA3C009F8BE7 /* OOMeetingMeetingRoomManageController.swift in Sources */,
+				B15BE0EE2499DD7E008CD1DB /* IMChatAudioView.swift in Sources */,
 				E40E24D820B7DA3C009F8BE7 /* OOMeetingPersonFooterView.swift in Sources */,
 				B14E078A230141AC00AE85A0 /* ContactGroupPickerViewController.swift in Sources */,
 				E4B697C520764A2D0062F6E8 /* O2InformationAPI.swift in Sources */,
@@ -6787,6 +6862,9 @@
 					"$(PROJECT_DIR)/O2Platform/framework/thirdlibs",
 					"$(PROJECT_DIR)/O2Platform/framework/MegviiLicMgr-iOS-SDK",
 					"$(PROJECT_DIR)/O2Platform/framework/O2OA_Flutter_SDK",
+					"$(PROJECT_DIR)/O2Platform/Framework/lame/vo-amrwbenc/lib",
+					"$(PROJECT_DIR)/O2Platform/Framework/lame",
+					"$(PROJECT_DIR)/O2Platform/Framework/lame/opencore-amr/lib",
 				);
 				MARKETING_VERSION = 5.0.7;
 				OTHER_LDFLAGS = (
@@ -6801,8 +6879,6 @@
 					"-framework",
 					"\"AlamofireObjectMapper\"",
 					"-framework",
-					"\"BSGridCollectionViewLayout\"",
-					"-framework",
 					"\"BSImagePicker\"",
 					"-framework",
 					"\"Charts\"",
@@ -6902,6 +6978,9 @@
 					"$(PROJECT_DIR)/O2Platform/framework/thirdlibs",
 					"$(PROJECT_DIR)/O2Platform/framework/MegviiLicMgr-iOS-SDK",
 					"$(PROJECT_DIR)/O2Platform/framework/O2OA_Flutter_SDK",
+					"$(PROJECT_DIR)/O2Platform/Framework/lame/vo-amrwbenc/lib",
+					"$(PROJECT_DIR)/O2Platform/Framework/lame",
+					"$(PROJECT_DIR)/O2Platform/Framework/lame/opencore-amr/lib",
 				);
 				MARKETING_VERSION = 5.0.7;
 				OTHER_LDFLAGS = (
@@ -6916,8 +6995,6 @@
 					"-framework",
 					"\"AlamofireObjectMapper\"",
 					"-framework",
-					"\"BSGridCollectionViewLayout\"",
-					"-framework",
 					"\"BSImagePicker\"",
 					"-framework",
 					"\"Charts\"",

+ 4 - 6
o2ios/O2Platform/App/Cms-内容管理/c/CMSItemDetailViewController.swift

@@ -306,9 +306,8 @@ extension CMSItemDetailViewController: O2WKScriptMessageHandlerImplement {
         self.uploadAttachment(site, uploadURL: updloadURL!)
     }
     private func uploadAttachment(_ site:String,uploadURL url:String){
-        let vc = FileBSImagePickerViewController()
-        bs_presentImagePickerController(vc, animated: true,
-                                        select: { (asset: PHAsset) -> Void in
+        let vc = FileBSImagePickerViewController().bsImagePicker()
+        presentImagePicker(vc, select: { (asset: PHAsset) -> Void in
                                             // User selected an asset.
                                             // Do something with it, start upload perhaps?
         }, deselect: { (asset: PHAsset) -> Void in
@@ -509,9 +508,8 @@ extension CMSItemDetailViewController: O2WKScriptMessageHandlerImplement {
     
     
     private func replaceAttachment(_ site:String,_ attachmentId:String,replaceURL url:String){
-        let vc = FileBSImagePickerViewController()
-        bs_presentImagePickerController(vc, animated: true,
-                                        select: { (asset: PHAsset) -> Void in
+        let vc = FileBSImagePickerViewController().bsImagePicker()
+        presentImagePicker(vc, select: { (asset: PHAsset) -> Void in
                                             // User selected an asset.
                                             // Do something with it, start upload perhaps?
         }, deselect: { (asset: PHAsset) -> Void in

+ 47 - 28
o2ios/O2Platform/App/File-云盘/c/FileBSImagePickerViewController.swift

@@ -9,34 +9,53 @@
 import UIKit
 import BSImagePicker
 
-class FileBSImagePickerViewController: BSImagePickerViewController {
- 
-    var defaultmaxNumberOfSelections = 1
-    var defaultTakePhotos = true
-    override func viewDidLoad() {
-        super.viewDidLoad()
-        self.navigationBar.isTranslucent = false
-        self.navigationBar.barTintColor = navbar_barTint_color
-        self.navigationBar.tintColor = navbar_tint_color
-        self.navigationBar.titleTextAttributes = [NSAttributedString.Key.font:navbar_text_font,NSAttributedString.Key.foregroundColor:navbar_tint_color]
-        self.albumButton.setTitleColor(navbar_tint_color, for: .normal)
-        self.settings.maxNumberOfSelections = defaultmaxNumberOfSelections
-        self.settings.takePhotos = defaultTakePhotos
-        
-        
-        //隐藏返回按钮文字
-        let barItem = UIBarButtonItem.appearance()
-        let offset = UIOffset(horizontal: -200, vertical: 0)
-        barItem.setBackButtonTitlePositionAdjustment(offset, for: .default)
-        barItem.setTitleTextAttributes([NSAttributedString.Key.font:navbar_item_font,NSAttributedString.Key.foregroundColor:navbar_tint_color], for:UIControl.State())
-
-        // Do any additional setup after loading the view.
-    }
+struct FileBSImagePickerViewController {
     
-    override var preferredStatusBarStyle : UIStatusBarStyle {
-        return .lightContent
+    func bsImagePicker() -> BSImagePicker.ImagePickerController {
+        let picker = BSImagePicker.ImagePickerController()
+        picker.albumButton.setTitleColor(navbar_tint_color, for: .normal)
+        picker.settings.selection.max = 1
+        picker.settings.theme.selectionStyle = .checked
+        picker.settings.selection.unselectOnReachingMax = true
+        picker.navigationBar.isTranslucent = false
+        picker.navigationBar.barTintColor = navbar_barTint_color
+        picker.navigationBar.tintColor = navbar_tint_color
+        picker.navigationBar.titleTextAttributes = [NSAttributedString.Key.font:navbar_text_font,NSAttributedString.Key.foregroundColor:navbar_tint_color]
+        
+        return picker
     }
-    
-    
-
 }
+
+//class FileBSImagePickerViewController: BSImagePicker.ImagePickerController {
+//
+//    var defaultmaxNumberOfSelections = 1
+//    var defaultTakePhotos = true
+//    override func viewDidLoad() {
+//        super.viewDidLoad()
+//        self.navigationBar.isTranslucent = false
+//        self.navigationBar.barTintColor = navbar_barTint_color
+//        self.navigationBar.tintColor = navbar_tint_color
+//        self.navigationBar.titleTextAttributes = [NSAttributedString.Key.font:navbar_text_font,NSAttributedString.Key.foregroundColor:navbar_tint_color]
+//
+////        self.albumButton.setTitleColor(navbar_tint_color, for: .normal)
+////        self.settings.maxNumberOfSelections = defaultmaxNumberOfSelections
+////        self.settings.takePhotos = defaultTakePhotos
+//
+//
+//
+//        //隐藏返回按钮文字
+//        let barItem = UIBarButtonItem.appearance()
+//        let offset = UIOffset(horizontal: -200, vertical: 0)
+//        barItem.setBackButtonTitlePositionAdjustment(offset, for: .default)
+//        barItem.setTitleTextAttributes([NSAttributedString.Key.font:navbar_item_font,NSAttributedString.Key.foregroundColor:navbar_tint_color], for:UIControl.State())
+//
+//        // Do any additional setup after loading the view.
+//    }
+//
+//    override var preferredStatusBarStyle : UIStatusBarStyle {
+//        return .lightContent
+//    }
+//
+//
+//
+//}

+ 2 - 3
o2ios/O2Platform/App/File-云盘/c/MainFileViewController.swift

@@ -285,7 +285,6 @@ class MainFileViewController: UIViewController {
     }
     
     func uploadFile(){
-        let vc = FileBSImagePickerViewController()
         var url = ""
         if self.folderQueue.count == 0 {
             url = AppDelegate.o2Collect.generateURLWithAppContextKey(FileContext.fileContextKey, query: FileContext.fileUploadTopQuery, parameter: nil,coverted: true)!
@@ -293,8 +292,8 @@ class MainFileViewController: UIViewController {
             url = AppDelegate.o2Collect.generateURLWithAppContextKey(FileContext.fileContextKey, query: FileContext.fileUploadSubQuery, parameter: ["##id##":(self.folderQueue.last?.id!)! as AnyObject],coverted: true)!
         }
         url = url.addingPercentEncoding(withAllowedCharacters: .urlFragmentAllowed)!
-        bs_presentImagePickerController(vc, animated: true,
-                                        select: { (asset: PHAsset) -> Void in
+        let vc = FileBSImagePickerViewController().bsImagePicker()
+        presentImagePicker(vc, select: { (asset: PHAsset) -> Void in
                                             // User selected an asset.
                                             // Do something with it, start upload perhaps?
         }, deselect: { (asset: PHAsset) -> Void in

+ 333 - 23
o2ios/O2Platform/App/IM-聊天/IMChatViewController.swift

@@ -9,6 +9,12 @@
 import UIKit
 import CocoaLumberjack
 import O2OA_Auth_SDK
+import BSImagePicker
+import Photos
+import Alamofire
+import AlamofireImage
+import SwiftyJSON
+import QuickLook
 
 class IMChatViewController: UIViewController {
 
@@ -21,13 +27,24 @@ class IMChatViewController: UIViewController {
     @IBOutlet weak var bottomBarHeightConstraint: NSLayoutConstraint!
     //底部工具栏
     @IBOutlet weak var bottomBar: UIView!
-    
-    private let emojiBarHeight = 256
+
+    private let emojiBarHeight = 196
     //表情窗口
     private lazy var emojiBar: IMChatEmojiBarView = {
-       let view = Bundle.main.loadNibNamed("IMChatEmojiBarView", owner: self, options: nil)?.first as! IMChatEmojiBarView
+        let view = Bundle.main.loadNibNamed("IMChatEmojiBarView", owner: self, options: nil)?.first as! IMChatEmojiBarView
+        view.frame = CGRect(x: 0, y: 0, width: SCREEN_WIDTH, height: emojiBarHeight.toCGFloat)
+        return view
+    }()
+    //语音录制按钮
+    private lazy var audioBtnView: IMChatAudioView = {
+        let view = Bundle.main.loadNibNamed("IMChatAudioView", owner: self, options: nil)?.first as! IMChatAudioView
         view.frame = CGRect(x: 0, y: 0, width: SCREEN_WIDTH, height: emojiBarHeight.toCGFloat)
-       return view
+        view.delegate = self
+        return view
+    }()
+    //预览文件
+    private lazy var previewVC: CloudFilePreviewController = {
+        return CloudFilePreviewController()
     }()
 
     private lazy var viewModel: IMViewModel = {
@@ -39,7 +56,9 @@ class IMChatViewController: UIViewController {
     private var chatMessageList: [IMMessageInfo] = []
     private var page = 1
     private var isShowEmoji = false
-    private var bottomBarHeight = 64
+    private var isShowAudioView = false
+    private var bottomBarHeight = 64 //底部输入框 表情按钮 的高度
+    private let bottomToolbarHeight = 46 //底部工具栏 麦克风 相册 相机等按钮的位置
 
 
     // MARK: - functions
@@ -55,7 +74,7 @@ class IMChatViewController: UIViewController {
         self.messageInputView.delegate = self
 
         //底部安全距离 老机型没有
-        self.bottomBarHeight = Int(iPhoneX ? 64 + IPHONEX_BOTTOM_SAFE_HEIGHT: 64)
+        self.bottomBarHeight = Int(iPhoneX ? 64 + IPHONEX_BOTTOM_SAFE_HEIGHT: 64) + self.bottomToolbarHeight
         self.bottomBarHeightConstraint.constant = self.bottomBarHeight.toCGFloat
         self.bottomBar.topBorder(width: 1, borderColor: base_gray_color.alpha(0.5))
         self.messageInputView.backgroundColor = base_gray_color
@@ -65,7 +84,7 @@ class IMChatViewController: UIViewController {
             if let c = self.conversation {
                 var person = ""
                 c.personList?.forEach({ (p) in
-                    if  p != O2AuthSDK.shared.myInfo()?.distinguishedName {
+                    if p != O2AuthSDK.shared.myInfo()?.distinguishedName {
                         person = p
                     }
                 })
@@ -73,7 +92,7 @@ class IMChatViewController: UIViewController {
                     self.title = person.split("@").first ?? ""
                 }
             }
-        }else {
+        } else {
             self.title = self.conversation?.title
         }
         //获取聊天数据
@@ -81,15 +100,15 @@ class IMChatViewController: UIViewController {
         //阅读
         self.viewModel.readConversation(conversationId: self.conversation?.id)
     }
-    
+
     override func viewWillAppear(_ animated: Bool) {
-         NotificationCenter.default.addObserver(self, selector: #selector(receiveMessageFromWs(notice:)), name: OONotification.websocket.notificationName, object: nil)
+        NotificationCenter.default.addObserver(self, selector: #selector(receiveMessageFromWs(notice:)), name: OONotification.websocket.notificationName, object: nil)
     }
     override func viewWillDisappear(_ animated: Bool) {
         NotificationCenter.default.removeObserver(self)
     }
-    
-    
+
+
     @objc private func receiveMessageFromWs(notice: Notification) {
         DDLogDebug("接收到websocket im 消息")
         if let message = notice.object as? IMMessageInfo {
@@ -117,11 +136,11 @@ class IMChatViewController: UIViewController {
         DispatchQueue.main.async {
             self.tableView.reloadData()
             if self.chatMessageList.count > 0 {
-                self.tableView.scrollToRow(at: IndexPath(row: self.chatMessageList.count-1, section: 0), at: .bottom, animated: true)
+                self.tableView.scrollToRow(at: IndexPath(row: self.chatMessageList.count - 1, section: 0), at: .bottom, animated: true)
             }
         }
     }
-    
+
     //发送文本消息
     private func sendTextMessage() {
         guard let msg = self.messageInputView.text else {
@@ -140,7 +159,7 @@ class IMChatViewController: UIViewController {
         body.body = emoji
         sendMessage(body: body)
     }
-    
+
     //发送消息到服务器
     private func sendMessage(body: IMMessageBodyInfo) {
         let message = IMMessageInfo()
@@ -154,12 +173,172 @@ class IMChatViewController: UIViewController {
         self.scrollMessageToBottom()
         //发送消息到服务器
         self.viewModel.sendMsg(msg: message)
-            .then { (result)  in
+            .then { (result) in
                 DDLogDebug("发送消息成功 \(result)")
                 self.viewModel.readConversation(conversationId: self.conversation?.id)
-        }.catch { (error) in
-            DDLogError(error.localizedDescription)
-            self.showError(title: "发送消息失败!")
+            }.catch { (error) in
+                DDLogError(error.localizedDescription)
+                self.showError(title: "发送消息失败!")
+        }
+    }
+
+    //选择照片
+    private func chooseImage() {
+        let vc = FileBSImagePickerViewController().bsImagePicker()
+        vc.settings.fetch.assets.supportedMediaTypes = [.image]
+
+        presentImagePicker(vc, select: { (asset) in
+            //选中一个
+        }, deselect: { (asset) in
+                //取消选中一个
+            }, cancel: { (assets) in
+                //取消
+            }, finish: { (assets) in
+                //结果
+                if assets.count > 0 {
+                    switch assets[0].mediaType {
+                    case .image:
+                        let options = PHImageRequestOptions()
+                        options.isSynchronous = true
+                        options.deliveryMode = .fastFormat
+                        options.resizeMode = .none
+                        PHImageManager.default().requestImageData(for: assets[0], options: options) { (imageData, result, imageOrientation, dict) in
+                            guard let data = imageData else {
+                                return
+                            }
+                            var newData = data
+                            //处理图片旋转的问题
+                            if imageOrientation != UIImage.Orientation.up {
+                                let newImage = UIImage(data: data)?.fixOrientation()
+                                if newImage != nil {
+                                    newData = newImage!.pngData()!
+                                }
+                            }
+                            var fileName = ""
+                            if dict?["PHImageFileURLKey"] != nil {
+                                let fileURL = dict?["PHImageFileURLKey"] as! URL
+                                fileName = fileURL.lastPathComponent
+                            } else {
+                                fileName = "\(UUID().uuidString).png"
+                            }
+                            let localFilePath = self.storageLocalImage(imageData: newData, fileName: fileName)
+                            let msgId = self.prepareForSendImageMsg(filePath: localFilePath)
+                            self.uploadImageAndSendMsg(messageId: msgId, imageData: newData, fileName: fileName)
+                        }
+                        break
+                    default:
+                        //
+                        DDLogError("不支持的类型")
+                        self.showError(title: "不支持的类型!")
+                        break
+                    }
+                }
+            }, completion: nil)
+    }
+    //临时存储本地
+    private func storageLocalImage(imageData: Data, fileName: String) -> String {
+        let fileTempPath = FileUtil.share.cacheDir().appendingPathComponent(fileName)
+        do {
+            try imageData.write(to: fileTempPath)
+            return fileTempPath.path
+        } catch {
+            print(error.localizedDescription)
+            return fileTempPath.path
+        }
+    }
+    //发送消息前 先载入界面
+    private func prepareForSendImageMsg(filePath: String) -> String {
+        let body = IMMessageBodyInfo()
+        body.type = o2_im_msg_type_image
+        body.body = o2_im_msg_body_image
+        body.fileTempPath = filePath
+        let message = IMMessageInfo()
+        let msgId = UUID().uuidString
+        message.body = body.toJSONString()
+        message.id = msgId
+        message.conversationId = self.conversation?.id
+        message.createPerson = O2AuthSDK.shared.myInfo()?.distinguishedName
+        message.createTime = Date().formatterDate(formatter: "yyyy-MM-dd HH:mm:ss")
+        //添加到界面
+        self.chatMessageList.append(message)
+        self.scrollMessageToBottom()
+        return msgId
+    }
+    
+    //发送消息前 先载入界面
+    private func prepareForSendFileMsg(tempMessage: IMMessageBodyInfo) -> String {
+        let message = IMMessageInfo()
+        let msgId = UUID().uuidString
+        message.body = tempMessage.toJSONString()
+        message.id = msgId
+        message.conversationId = self.conversation?.id
+        message.createPerson = O2AuthSDK.shared.myInfo()?.distinguishedName
+        message.createTime = Date().formatterDate(formatter: "yyyy-MM-dd HH:mm:ss")
+        //添加到界面
+        self.chatMessageList.append(message)
+        self.scrollMessageToBottom()
+        return msgId
+    }
+
+    //上传图片到服务器并发送消息
+    private func uploadImageAndSendMsg(messageId: String, imageData: Data, fileName: String) {
+        guard let cId = self.conversation?.id else {
+            return
+        }
+        self.viewModel.uploadFile(conversationId: cId, type: o2_im_msg_type_image, fileName: fileName, file: imageData).then { attachId in
+            DDLogDebug("上传图片成功: \(attachId)")
+            guard let message = self.chatMessageList.first (where: { (info) -> Bool in
+                return info.id == messageId
+            }) else {
+                DDLogDebug("没有找到对应的消息")
+                return
+            }
+            let body = IMMessageBodyInfo.deserialize(from: message.body)
+            body?.fileId = attachId
+            body?.fileTempPath = nil
+            message.body = body?.toJSONString()
+            //发送消息到服务器
+            self.viewModel.sendMsg(msg: message)
+                .then { (result) in
+                    DDLogDebug("图片消息 发送成功 \(result)")
+                    self.viewModel.readConversation(conversationId: self.conversation?.id)
+                }.catch { (error) in
+                    DDLogError(error.localizedDescription)
+                    self.showError(title: "发送消息失败!")
+            }
+        }.catch { err in
+            self.showError(title: "上传错误,\(err.localizedDescription)")
+        }
+    }
+    
+    //上传图片 音频 等文件到服务器并发送消息
+    private func uploadFileAndSendMsg(messageId: String, data: Data, fileName: String, type: String) {
+        guard let cId = self.conversation?.id else {
+            return
+        }
+        self.viewModel.uploadFile(conversationId: cId, type: type, fileName: fileName, file: data).then { attachId in
+            DDLogDebug("上传图片成功: \(attachId)")
+            guard let message = self.chatMessageList.first (where: { (info) -> Bool in
+                return info.id == messageId
+            }) else {
+                DDLogDebug("没有找到对应的消息")
+                return
+            }
+            let body = IMMessageBodyInfo.deserialize(from: message.body)
+            body?.fileId = attachId
+            body?.fileTempPath = nil
+            message.body = body?.toJSONString()
+            //发送消息到服务器
+            self.viewModel.sendMsg(msg: message)
+                .then { (result) in
+                    DDLogDebug("图片消息 发送成功 \(result)")
+                    self.viewModel.readConversation(conversationId: self.conversation?.id)
+                }.catch { (error) in
+                    DDLogError(error.localizedDescription)
+                    self.showError(title: "发送消息失败!")
+            }
+        }.catch { err in
+            self.showError(title: "上传错误,\(err.localizedDescription)")
         }
     }
 
@@ -170,6 +349,10 @@ class IMChatViewController: UIViewController {
         self.isShowEmoji.toggle()
         self.view.endEditing(true)
         if self.isShowEmoji {
+            //audio view 先关闭
+            self.isShowAudioView = false
+            self.audioBtnView.removeFromSuperview()
+            //开始添加emojiBar
             self.bottomBarHeightConstraint.constant = self.bottomBarHeight.toCGFloat + self.emojiBarHeight.toCGFloat
             self.emojiBar.delegate = self
             self.emojiBar.translatesAutoresizingMaskIntoConstraints = false
@@ -185,10 +368,134 @@ class IMChatViewController: UIViewController {
         self.view.layoutIfNeeded()
     }
 
+    @IBAction func micBtnClick(_ sender: UIButton) {
+        DDLogDebug("点击了麦克风按钮")
+        self.isShowAudioView.toggle()
+        self.view.endEditing(true)
+        if self.isShowAudioView {
+            //emoji view 先关闭
+            self.isShowEmoji = false
+            self.emojiBar.removeFromSuperview()
+            //开始添加emojiBar
+            self.bottomBarHeightConstraint.constant = self.bottomBarHeight.toCGFloat + self.emojiBarHeight.toCGFloat
+            self.audioBtnView.translatesAutoresizingMaskIntoConstraints = false
+            self.bottomBar.addSubview(self.audioBtnView)
+            let top = NSLayoutConstraint(item: self.audioBtnView, attribute: .top, relatedBy: .equal, toItem: self.audioBtnView.superview!, attribute: .top, multiplier: 1, constant: CGFloat(self.bottomBarHeight))
+            let width = NSLayoutConstraint(item: self.audioBtnView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: SCREEN_WIDTH)
+            let height = NSLayoutConstraint(item: self.audioBtnView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: self.emojiBarHeight.toCGFloat)
+            NSLayoutConstraint.activate([top, width, height])
+        }else {
+            self.bottomBarHeightConstraint.constant = self.bottomBarHeight.toCGFloat
+            self.audioBtnView.removeFromSuperview()
+        }
+        self.view.layoutIfNeeded()
+    }
+
+    @IBAction func imgBtnClick(_ sender: UIButton) {
+        DDLogDebug("点击了图片按钮")
+        self.chooseImage()
+    }
+    @IBAction func cameraBtnClick(_ sender: UIButton) {
+        DDLogDebug("点击了相机按钮")
+        self.takePhoto(delegate: self)
+    }
+    @IBAction func locationBtnClick(_ sender: UIButton) {
+        DDLogDebug("点击了位置按钮")
+    }
+
+
+}
+
+// MARK: - 录音delegate
+extension IMChatViewController: IMChatAudioViewDelegate {
+    func sendVoice(path: String, voice: Data, duration: String) {
+        let msg = IMMessageBodyInfo()
+        msg.fileTempPath = path
+        msg.body = o2_im_msg_body_audio
+        msg.type = o2_im_msg_type_audio
+        msg.audioDuration = duration
+        let msgId = self.prepareForSendFileMsg(tempMessage: msg)
+        let fileName = path.split("/").last ?? "MySound.ilbc"
+        DDLogDebug("音频文件:\(fileName)")
+        self.uploadFileAndSendMsg(messageId: msgId, data: voice, fileName: fileName, type: o2_im_msg_type_audio)
+    }
+}
+
+// MARK: - 拍照delegate
+extension IMChatViewController: UIImagePickerControllerDelegate & UINavigationControllerDelegate {
 
+    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
+        if let image = info[.editedImage] as? UIImage {
+            let fileName = "\(UUID().uuidString).png"
+            let newData = image.pngData()!
+            let localFilePath = self.storageLocalImage(imageData: newData, fileName: fileName)
+            let msgId = self.prepareForSendImageMsg(filePath: localFilePath)
+            self.uploadImageAndSendMsg(messageId: msgId, imageData: newData, fileName: fileName)
+        } else {
+            DDLogError("没有选择到图片!")
+        }
+        picker.dismiss(animated: true, completion: nil)
+//        var newData = data
+//        //处理图片旋转的问题
+//        if imageOrientation != UIImage.Orientation.up {
+//            let newImage = UIImage(data: data)?.fixOrientation()
+//            if newImage != nil {
+//                newData = newImage!.pngData()!
+//            }
+//        }
+//        var fileName = ""
+//        if dict?["PHImageFileURLKey"] != nil {
+//            let fileURL = dict?["PHImageFileURLKey"] as! URL
+//            fileName = fileURL.lastPathComponent
+//        } else {
+//            fileName = "\(UUID().uuidString).png"
+//        }
+    }
 
 }
 
+// MARK: - 图片消息点击 delegate
+extension IMChatViewController: IMChatMessageDelegate {
+    func clickImageMessage(fileId: String?, tempPath: String?) {
+        if let id = fileId {
+            self.showLoading()
+            O2IMFileManager.shared
+                .getFileLocalUrl(fileId: id)
+                .always {
+                    self.hideLoading()
+                }.then { (path) in
+                    let currentURL = NSURL(fileURLWithPath: path.path)
+                    DDLogDebug(currentURL.description)
+                    DDLogDebug(path.path)
+                    if QLPreviewController.canPreview(currentURL) {
+                        self.previewVC.currentFileURLS.removeAll()
+                        self.previewVC.currentFileURLS.append(currentURL)
+                        self.previewVC.reloadData()
+                        self.pushVC(self.previewVC)
+                    } else {
+                        self.showError(title: "当前文件类型不支持预览!")
+                    }
+                }
+                .catch { (error) in
+                    DDLogError(error.localizedDescription)
+                    self.showError(title: "获取文件异常!")
+            }
+        }else if let temp = tempPath {
+            let currentURL = NSURL(fileURLWithPath: temp)
+            DDLogDebug(currentURL.description)
+            DDLogDebug(temp)
+            if QLPreviewController.canPreview(currentURL) {
+                self.previewVC.currentFileURLS.removeAll()
+                self.previewVC.currentFileURLS.append(currentURL)
+                self.previewVC.reloadData()
+                self.pushVC(self.previewVC)
+            } else {
+                self.showError(title: "当前文件类型不支持预览!")
+            }
+        }
+    }
+}
+
 // MARK: - 表情点击 delegate
 extension IMChatViewController: IMChatEmojiBarClickDelegate {
     func clickEmoji(emoji: String) {
@@ -209,17 +516,19 @@ extension IMChatViewController: UITableViewDelegate, UITableViewDataSource {
         if msg.createPerson == O2AuthSDK.shared.myInfo()?.distinguishedName { //发送者
             if let cell = tableView.dequeueReusableCell(withIdentifier: "IMChatMessageSendViewCell", for: indexPath) as? IMChatMessageSendViewCell {
                 cell.setContent(item: self.chatMessageList[indexPath.row])
+                cell.delegate = self
                 return cell
             }
-        }else {
+        } else {
             if let cell = tableView.dequeueReusableCell(withIdentifier: "IMChatMessageViewCell", for: indexPath) as? IMChatMessageViewCell {
                 cell.setContent(item: self.chatMessageList[indexPath.row])
+                cell.delegate = self
                 return cell
             }
         }
         return UITableViewCell()
     }
-    
+
     func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
         tableView.deselectRow(at: indexPath, animated: false)
     }
@@ -230,12 +539,13 @@ extension IMChatViewController: UITableViewDelegate, UITableViewDataSource {
 extension IMChatViewController: UITextFieldDelegate {
     func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
         DDLogDebug("准备开始输入......")
-        closeEmoji()
+        closeOtherView()
         return true
     }
 
-    private func closeEmoji() {
+    private func closeOtherView() {
         self.isShowEmoji = false
+        self.isShowAudioView = false
         self.bottomBarHeightConstraint.constant = self.bottomBarHeight.toCGFloat
         self.view.layoutIfNeeded()
     }

+ 60 - 5
o2ios/O2Platform/App/IM-聊天/IMChatViewController.xib

@@ -22,11 +22,6 @@
             <rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
             <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
             <subviews>
-                <tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="ZIb-3p-hnq">
-                    <rect key="frame" x="0.0" y="44" width="414" height="754"/>
-                    <color key="backgroundColor" red="0.95294117649999999" green="0.95294117649999999" blue="0.95294117649999999" alpha="1" colorSpace="calibratedRGB"/>
-                    <color key="sectionIndexBackgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
-                </tableView>
                 <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="sDg-us-Ed9">
                     <rect key="frame" x="0.0" y="798" width="414" height="98"/>
                     <subviews>
@@ -49,17 +44,73 @@
                                 <action selector="clickEmojiBtn:" destination="-1" eventType="touchUpInside" id="hZk-xk-g8t"/>
                             </connections>
                         </button>
+                        <stackView opaque="NO" contentMode="scaleToFill" distribution="fillEqually" alignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="H2g-NL-0Ps">
+                            <rect key="frame" x="14" y="53" width="386" height="36"/>
+                            <subviews>
+                                <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="dw7-Gt-B9S">
+                                    <rect key="frame" x="0.0" y="2" width="96.5" height="32"/>
+                                    <constraints>
+                                        <constraint firstAttribute="height" constant="32" id="L8w-G8-QcX"/>
+                                    </constraints>
+                                    <state key="normal" image="chat_mic"/>
+                                    <connections>
+                                        <action selector="micBtnClick:" destination="-1" eventType="touchUpInside" id="qN7-d7-8AE"/>
+                                    </connections>
+                                </button>
+                                <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="maW-x1-6ob">
+                                    <rect key="frame" x="96.5" y="2" width="96.5" height="32"/>
+                                    <constraints>
+                                        <constraint firstAttribute="height" constant="32" id="NCL-G0-g4l"/>
+                                    </constraints>
+                                    <state key="normal" image="chat_img"/>
+                                    <connections>
+                                        <action selector="imgBtnClick:" destination="-1" eventType="touchUpInside" id="Jbr-NQ-U7b"/>
+                                    </connections>
+                                </button>
+                                <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="zj7-bo-0tf">
+                                    <rect key="frame" x="193" y="2" width="96.5" height="32"/>
+                                    <constraints>
+                                        <constraint firstAttribute="height" constant="32" id="mDx-JV-z4Q"/>
+                                    </constraints>
+                                    <state key="normal" image="chat_camera"/>
+                                    <connections>
+                                        <action selector="cameraBtnClick:" destination="-1" eventType="touchUpInside" id="v70-6D-pKx"/>
+                                    </connections>
+                                </button>
+                                <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="eXj-l2-Ioo">
+                                    <rect key="frame" x="289.5" y="2" width="96.5" height="32"/>
+                                    <constraints>
+                                        <constraint firstAttribute="height" constant="32" id="Fae-3Q-Qrq"/>
+                                    </constraints>
+                                    <state key="normal" image="chat_location"/>
+                                    <connections>
+                                        <action selector="locationBtnClick:" destination="-1" eventType="touchUpInside" id="Jwe-pL-EpP"/>
+                                    </connections>
+                                </button>
+                            </subviews>
+                            <constraints>
+                                <constraint firstAttribute="height" constant="36" id="xmi-XZ-ezm"/>
+                            </constraints>
+                        </stackView>
                     </subviews>
                     <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                     <constraints>
+                        <constraint firstItem="H2g-NL-0Ps" firstAttribute="top" secondItem="8BW-XG-rBx" secondAttribute="bottom" constant="5" id="1hG-6h-KgV"/>
                         <constraint firstAttribute="trailing" secondItem="oTD-nU-xkE" secondAttribute="trailing" constant="12" id="BtK-YW-2fb"/>
                         <constraint firstItem="oTD-nU-xkE" firstAttribute="top" secondItem="sDg-us-Ed9" secondAttribute="top" constant="12" id="JKE-h8-gll"/>
                         <constraint firstItem="oTD-nU-xkE" firstAttribute="leading" secondItem="8BW-XG-rBx" secondAttribute="trailing" constant="24" id="Mzm-dM-gqy"/>
+                        <constraint firstItem="H2g-NL-0Ps" firstAttribute="leading" secondItem="sDg-us-Ed9" secondAttribute="leading" constant="14" id="Nkm-xr-kRS"/>
                         <constraint firstItem="8BW-XG-rBx" firstAttribute="top" secondItem="sDg-us-Ed9" secondAttribute="top" constant="12" id="hnf-bY-DYH"/>
                         <constraint firstItem="8BW-XG-rBx" firstAttribute="leading" secondItem="sDg-us-Ed9" secondAttribute="leading" constant="14" id="kAO-OU-1mN"/>
+                        <constraint firstAttribute="trailing" secondItem="H2g-NL-0Ps" secondAttribute="trailing" constant="14" id="nDu-Mf-6te"/>
                         <constraint firstAttribute="height" constant="98" id="qiu-7O-AwO"/>
                     </constraints>
                 </view>
+                <tableView clipsSubviews="YES" contentMode="scaleToFill" misplaced="YES" alwaysBounceVertical="YES" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="ZIb-3p-hnq">
+                    <rect key="frame" x="0.0" y="24" width="414" height="720"/>
+                    <color key="backgroundColor" red="0.95294117649999999" green="0.95294117649999999" blue="0.95294117649999999" alpha="1" colorSpace="calibratedRGB"/>
+                    <color key="sectionIndexBackgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                </tableView>
             </subviews>
             <color key="backgroundColor" systemColor="systemGray6Color" red="0.94901960780000005" green="0.94901960780000005" blue="0.96862745100000003" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
             <constraints>
@@ -76,6 +127,10 @@
         </view>
     </objects>
     <resources>
+        <image name="chat_camera" width="42" height="32"/>
         <image name="chat_emoji" width="24" height="24"/>
+        <image name="chat_img" width="32" height="32"/>
+        <image name="chat_location" width="41" height="32"/>
+        <image name="chat_mic" width="32" height="32"/>
     </resources>
 </document>

+ 11 - 12
o2ios/O2Platform/App/IM-聊天/IMConversationListViewController.swift

@@ -198,22 +198,21 @@ extension IMConversationListViewController: UITableViewDelegate, UITableViewData
     }
 
     func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
-        return tableView.dequeueReusableCell(withIdentifier: "IMConversationItemCell", for: indexPath)
-    }
-    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
-        guard let c = cell as? IMConversationItemCell else {
-            return
-        }
-        if self.instantMsgList.count > 0 {
-            if indexPath.row == 0 {
-                c.setInstantContent(item: self.instantMsgList.last!)
+        if let cell = tableView.dequeueReusableCell(withIdentifier: "IMConversationItemCell", for: indexPath) as? IMConversationItemCell {
+            if self.instantMsgList.count > 0 {
+                if indexPath.row == 0 {
+                    cell.setInstantContent(item: self.instantMsgList.last!)
+                }else {
+                    cell.bindConversation(conversation: self.conversationList[indexPath.row - 1])
+                }
             }else {
-                c.bindConversation(conversation: self.conversationList[indexPath.row - 1])
+                cell.bindConversation(conversation: self.conversationList[indexPath.row])
             }
-        }else {
-            c.bindConversation(conversation: self.conversationList[indexPath.row])
+            return cell
         }
+        return UITableViewCell()
     }
+
     func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
         return 64
     }

+ 19 - 0
o2ios/O2Platform/App/IM-聊天/IMViewModel.swift

@@ -75,6 +75,25 @@ extension IMViewModel {
                 })
         }
     }
+    
+    //上传文件
+    func uploadFile(conversationId: String, type: String, fileName: String, file: Data) -> Promise<String> {
+       return Promise { fulfill, reject in
+           self.communicateAPI.request(.imUploadFile(conversationId, type, fileName, file), completion: { (result) in
+               let response = OOResult<BaseModelClass<OOCommonIdModel>>(result)
+               if response.isResultSuccess() {
+                   if let id = response.model?.data {
+                       fulfill(id.id ?? "")
+                   }else {
+                       reject(OOAppError.apiEmptyResultError)
+                   }
+               }else {
+                   reject(response.error!)
+               }
+           })
+       }
+   }
+   
 
     //查询会话列表
     func myConversationList() -> Promise<[IMConversationInfo]> {

+ 6 - 0
o2ios/O2Platform/App/IM-聊天/Model/IMConversationInfo.swift

@@ -62,6 +62,12 @@ class IMMessageBodyInfo: NSObject, DataModel {
     @objc var id: String?
     @objc var type: String?
     @objc var body: String?
+    @objc var fileId: String? //文件id
+    @objc var fileTempPath: String? //本地临时文件地址
+    @objc var audioDuration: String? // 音频文件时长
+    @objc var address: String? //type=location的时候位置信息
+    var latitude: Double?//type=location的时候位置信息
+    var longitude: Double?//type=location的时候位置信息
 
 
     required override init() { }

+ 71 - 0
o2ios/O2Platform/App/IM-聊天/O2IM.swift

@@ -7,6 +7,8 @@
 //
 
 import Foundation
+import Promises
+import CocoaLumberjack
 
 //心跳消息
 let o2_im_ws_heartbeat = "heartbeat"
@@ -15,8 +17,16 @@ let o2_im_ws_heartbeat = "heartbeat"
 let o2_im_conversation_type_single = "single"
 let o2_im_conversation_type_group = "group"
 
+//消息分类
 let o2_im_msg_type_text = "text"
 let o2_im_msg_type_emoji = "emoji"
+let o2_im_msg_type_image = "image"
+let o2_im_msg_type_audio = "audio"
+
+//消息body
+let o2_im_msg_body_image = "[图片]"
+let o2_im_msg_body_audio = "[语音]"
+let o2_im_msg_body_video = "[视频]"
 
 
 //表情的字符串转化为O2Emoji.bundle里面的图片路径 [01] -> im_emotion_01
@@ -27,3 +37,64 @@ func o2ImEmojiPath(emojiBody: String) -> String {
     }
     return ""
 }
+
+class O2IMFileManager {
+    static let shared: O2IMFileManager = {
+        return O2IMFileManager()
+    }()
+
+    private let communicateAPI = {
+        return OOMoyaProvider<CommunicateAPI>()
+    }()
+
+    private init() { }
+    //根据id下载文件,并返回文件的本地url
+    func getFileLocalUrl(fileId: String) -> Promise<URL> {
+        return Promise { fulfill, reject in
+            let url = self.localFilePath(fileId: fileId)
+            if FileUtil.share.fileExist(filePath: url.path) {
+                fulfill(url)
+            } else {
+                self.communicateAPI.request(.imDownloadFullFile(fileId), completion: { result in
+                        switch result {
+                        case .success(_):
+                            DDLogError("下载成功。。。。。\(fileId)")
+                            fulfill(url)
+                            break
+                        case .failure(let err):
+                            DDLogError(err.localizedDescription)
+                            reject(err)
+                            break
+                        }
+                    })
+            }
+        }
+
+    }
+
+    func localFilePath(fileId: String) -> URL {
+        return FileUtil.share.cacheDir().appendingPathComponent("\(fileId).png")
+    }
+    
+    //音频文件存储地址
+    func getRecorderPath(type: RecordType) -> String {
+        var recorderPath = FileUtil.share.cacheDir()
+        recorderPath.appendPathComponent("o2im")
+        //目录不存在就创建
+        DDLogDebug("开始创建目录\(recorderPath.path)")
+        FileUtil.share.createDirectory(path: recorderPath.path)
+        let now:Date = Date()
+        let dateFormatter = DateFormatter()
+        dateFormatter.dateFormat = "yyyy-MM-dd-hh-mm-ss"
+        let fileName = (type == RecordType.Caf) ? "\(dateFormatter.string(from: now))-MySound.caf" : "\(dateFormatter.string(from: now))-MySound.mp3"
+        recorderPath.appendPathComponent(fileName)
+        return recorderPath.path
+    }
+    
+}
+
+
+enum RecordType :String {
+    case Caf = "caf"
+    case MP3 = "mp3"
+}

+ 31 - 0
o2ios/O2Platform/App/IM-聊天/View/IMAudioView.swift

@@ -0,0 +1,31 @@
+//
+//  IMAudioView.swift
+//  O2Platform
+//
+//  Created by FancyLou on 2020/6/17.
+//  Copyright © 2020 zoneland. All rights reserved.
+//
+
+import UIKit
+
+class IMAudioView: UIView {
+    
+    static let IMAudioView_width: CGFloat = 76
+    static let IMAudioView_height: CGFloat = 36
+    
+    @IBOutlet weak var playImageView: UIImageView!
+    @IBOutlet weak var durationLabel: UILabel!
+    
+    private var playUrl: String? = nil
+    
+    override func awakeFromNib() {
+        
+    }
+    
+    func setPlayUrl(url: String?) {
+        self.playUrl = url
+    }
+    func setDuration(duration: String) {
+        self.durationLabel.text = "\(duration)\""
+    }
+}

+ 57 - 0
o2ios/O2Platform/App/IM-聊天/View/IMAudioView.xib

@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="16097" 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="16087"/>
+        <capability name="Safe area layout guides" minToolsVersion="9.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="IMAudioView" customModule="O2Platform" customModuleProvider="target">
+            <rect key="frame" x="0.0" y="0.0" width="76" height="36"/>
+            <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+            <subviews>
+                <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="chat_audio_play_Normal" translatesAutoresizingMaskIntoConstraints="NO" id="a6h-yn-SMe">
+                    <rect key="frame" x="5" y="4" width="28" height="28"/>
+                    <constraints>
+                        <constraint firstAttribute="height" constant="28" id="jsR-4F-M7B"/>
+                        <constraint firstAttribute="width" constant="28" id="kKV-bU-3mr"/>
+                    </constraints>
+                </imageView>
+                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="9“" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Pe3-7R-eCP">
+                    <rect key="frame" x="43" y="9.5" width="23" height="17"/>
+                    <fontDescription key="fontDescription" type="system" pointSize="14"/>
+                    <color key="textColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                    <nil key="highlightedColor"/>
+                </label>
+            </subviews>
+            <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+            <constraints>
+                <constraint firstItem="Pe3-7R-eCP" firstAttribute="leading" secondItem="a6h-yn-SMe" secondAttribute="trailing" constant="10" id="6nk-nX-y34"/>
+                <constraint firstItem="a6h-yn-SMe" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="5" id="A7P-7Y-jsE"/>
+                <constraint firstItem="Pe3-7R-eCP" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="centerY" id="f3M-cW-QZa"/>
+                <constraint firstAttribute="trailing" secondItem="Pe3-7R-eCP" secondAttribute="trailing" constant="10" id="oO3-qw-7Xl"/>
+                <constraint firstItem="vUN-kp-3ea" firstAttribute="bottom" secondItem="a6h-yn-SMe" secondAttribute="bottom" constant="4" id="qjF-vI-47S"/>
+                <constraint firstItem="a6h-yn-SMe" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" constant="4" id="r1h-0b-1er"/>
+            </constraints>
+            <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
+            <viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
+            <userDefinedRuntimeAttributes>
+                <userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
+                    <real key="value" value="5"/>
+                </userDefinedRuntimeAttribute>
+            </userDefinedRuntimeAttributes>
+            <connections>
+                <outlet property="durationLabel" destination="Pe3-7R-eCP" id="9pj-ZS-n3s"/>
+                <outlet property="playImageView" destination="a6h-yn-SMe" id="6N0-qd-tPk"/>
+            </connections>
+            <point key="canvasLocation" x="131.8840579710145" y="233.70535714285714"/>
+        </view>
+    </objects>
+    <resources>
+        <image name="chat_audio_play_Normal" width="18" height="18"/>
+    </resources>
+</document>

+ 96 - 0
o2ios/O2Platform/App/IM-聊天/View/IMChatAudioView.swift

@@ -0,0 +1,96 @@
+//
+//  IMChatAudioView.swift
+//  O2Platform
+//
+//  Created by FancyLou on 2020/6/17.
+//  Copyright © 2020 zoneland. All rights reserved.
+//
+
+import UIKit
+import CocoaLumberjack
+
+
+protocol IMChatAudioViewDelegate {
+    func sendVoice(path: String, voice: Data, duration: String)
+}
+class IMChatAudioView: UIView {
+    
+    @IBOutlet weak var audioViewTitle: UILabel!
+    @IBOutlet weak var audioRecordBtn: UIButton!
+    
+    private var isCancel = false
+    
+    private lazy var recordManager: O2RecordVoiceManager = {
+        let rm = O2RecordVoiceManager()
+        rm.delegate = self
+       return rm
+    }()
+    
+    var delegate: IMChatAudioViewDelegate?
+    
+    override func awakeFromNib() {
+        audioRecordBtn.addTarget(self, action: #selector(startRecord), for: .touchDown)
+        audioRecordBtn.addTarget(self, action: #selector(cancelRecord), for: .touchDragExit)
+        audioRecordBtn.addTarget(self, action: #selector(finishRecord), for: .touchUpInside)
+    }
+     
+    
+    @objc private func startRecord() {
+        DDLogError("startRecord record...................")
+        self.isCancel = false
+        self.audioViewTitle.text = "上滑取消发送"
+        //开始录音
+        recordManager.stopRecordCompletion = {
+            DDLogDebug("结束录音!!")
+        }
+        recordManager.cancelledDeleteCompletion = {
+            DDLogDebug("取消录音!")
+        }
+        recordManager.startRecordingWithPath(O2IMFileManager.shared.getRecorderPath(type: .Caf)) {
+            DDLogDebug("开始录音!!!")
+        }
+    }
+    
+    @objc private func cancelRecord() {
+        DDLogError("cancelRecord record...................")
+        self.audioViewTitle.text = "按住说话"
+        self.isCancel = true
+        //取消录音
+        recordManager.cancelledDeleteWithCompletion()
+    }
+    @objc private func finishRecord() {
+        DDLogError("finish record...................")
+        self.audioViewTitle.text = "按住说话"
+        if !self.isCancel {
+            //录音结束
+            recordManager.finishRecordingCompletion()
+            if (recordManager.recordDuration! as NSString).floatValue < 1 {
+                DispatchQueue.main.async {
+                    self.chrysan.show(.error, message: "说话时间太短", hideDelay: 1)
+                }
+                return
+            }
+            let filePath = O2IMFileManager.shared.getRecorderPath(type: .MP3)
+            recordManager.convertCafToMp3(cafPath: recordManager.recordPath!, mp3Path: filePath)
+            let data = try! Data(contentsOf: URL(fileURLWithPath: filePath))
+            delegate?.sendVoice(path: filePath, voice: data, duration: recordManager.recordDuration!)
+        }
+    }
+}
+
+extension IMChatAudioView: O2RecordVoiceDelegate {
+    func beyondLimit(_ time: TimeInterval) {
+        //录音结束
+        recordManager.finishRecordingCompletion()
+        if (recordManager.recordDuration! as NSString).floatValue < 1 {
+            DispatchQueue.main.async {
+                self.chrysan.show(.error, message: "说话时间太短", hideDelay: 1)
+            }
+            return
+        }
+        let filePath = O2IMFileManager.shared.getRecorderPath(type: .MP3)
+        recordManager.convertCafToMp3(cafPath: recordManager.recordPath!, mp3Path: filePath)
+        let data = try! Data(contentsOf: URL(fileURLWithPath: filePath))
+        delegate?.sendVoice(path: filePath, voice: data, duration: recordManager.recordDuration!)
+    }
+}

+ 57 - 0
o2ios/O2Platform/App/IM-聊天/View/IMChatAudioView.xib

@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="16097" 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="16087"/>
+        <capability name="Safe area layout guides" minToolsVersion="9.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="IMChatAudioView" customModule="O2Platform" customModuleProvider="target">
+            <rect key="frame" x="0.0" y="0.0" width="414" height="196"/>
+            <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+            <subviews>
+                <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="wVh-0l-AFu">
+                    <rect key="frame" x="158" y="49" width="98" height="98"/>
+                    <color key="backgroundColor" red="0.95294117649999999" green="0.95294117649999999" blue="0.95294117649999999" alpha="1" colorSpace="calibratedRGB"/>
+                    <constraints>
+                        <constraint firstAttribute="width" constant="98" id="4e9-hO-ccV"/>
+                        <constraint firstAttribute="height" constant="98" id="wuH-tw-j4k"/>
+                    </constraints>
+                    <state key="normal" image="chat_mic"/>
+                    <userDefinedRuntimeAttributes>
+                        <userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
+                            <real key="value" value="49"/>
+                        </userDefinedRuntimeAttribute>
+                    </userDefinedRuntimeAttributes>
+                </button>
+                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="按住说话" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Ofc-kF-lNX">
+                    <rect key="frame" x="182" y="14" width="50" height="15"/>
+                    <fontDescription key="fontDescription" type="system" pointSize="12"/>
+                    <color key="textColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                    <nil key="highlightedColor"/>
+                </label>
+            </subviews>
+            <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
+            <constraints>
+                <constraint firstItem="wVh-0l-AFu" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="centerY" id="6cB-Al-ASy"/>
+                <constraint firstItem="Ofc-kF-lNX" firstAttribute="centerX" secondItem="iN0-l3-epB" secondAttribute="centerX" id="SHl-b2-znG"/>
+                <constraint firstItem="wVh-0l-AFu" firstAttribute="centerX" secondItem="iN0-l3-epB" secondAttribute="centerX" id="tyc-Iy-wZR"/>
+                <constraint firstItem="Ofc-kF-lNX" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" constant="14" id="wVW-4b-Vj0"/>
+            </constraints>
+            <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
+            <viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
+            <connections>
+                <outlet property="audioRecordBtn" destination="wVh-0l-AFu" id="85j-KR-mnf"/>
+                <outlet property="audioViewTitle" destination="Ofc-kF-lNX" id="K6i-qG-CmP"/>
+            </connections>
+            <point key="canvasLocation" x="-113" y="-1"/>
+        </view>
+    </objects>
+    <resources>
+        <image name="chat_mic" width="32" height="32"/>
+    </resources>
+</document>

+ 102 - 7
o2ios/O2Platform/App/IM-聊天/View/IMChatMessageSendViewCell.swift

@@ -7,6 +7,7 @@
 //
 
 import UIKit
+import CocoaLumberjack
 
 class IMChatMessageSendViewCell: UITableViewCell {
     @IBOutlet weak var timeLabel: UILabel!
@@ -16,7 +17,13 @@ class IMChatMessageSendViewCell: UITableViewCell {
     @IBOutlet weak var messageBgWidth: NSLayoutConstraint!
     @IBOutlet weak var messageBgHeight: NSLayoutConstraint!
     
+    private lazy var audioView: IMAudioView = {
+        let view = Bundle.main.loadNibNamed("IMAudioView", owner: self, options: nil)?.first as! IMAudioView
+        view.frame = CGRect(x: 0, y: 0, width: IMAudioView.IMAudioView_width, height: IMAudioView.IMAudioView_height)
+        return view
+    }()
     
+    var delegate: IMChatMessageDelegate?
     
     override func awakeFromNib() {
         super.awakeFromNib()
@@ -48,14 +55,106 @@ class IMChatMessageSendViewCell: UITableViewCell {
         }
         self.messageBackgroundView.removeSubviews()
         if let jsonBody = item.body, let body = parseJson(msg: jsonBody) {
-            if body.type == o2_im_msg_type_emoji {
+            if o2_im_msg_type_emoji == body.type {
                 emojiMsgRender(emoji: body.body!)
-            }else {
+            }else if o2_im_msg_type_image == body.type {
+                imageMsgRender(info: body)
+            }else if o2_im_msg_type_audio == body.type {
+                audioMsgRender(info: body)
+            } else {
                 textMsgRender(msg: body.body!)
             }
         }
     }
     
+    //音频消息
+    private func audioMsgRender(info: IMMessageBodyInfo) {
+        self.messageBgWidth.constant = IMAudioView.IMAudioView_width + 20
+        self.messageBgHeight.constant = IMAudioView.IMAudioView_height + 20
+        self.audioView.translatesAutoresizingMaskIntoConstraints = false
+        self.messageBackgroundView.addSubview(self.audioView)
+        self.audioView.setDuration(duration: info.audioDuration ?? "0")
+        //音频文件
+        if let fileId = info.fileId {
+            let urlStr = AppDelegate.o2Collect.generateURLWithAppContextKey(
+            CommunicateContext.communicateContextKey,
+            query: CommunicateContext.imDownloadFileQuery,
+            parameter: ["##id##": fileId as AnyObject], generateTime: false)
+            self.audioView.setPlayUrl(url: urlStr)
+        } else if let filePath = info.fileTempPath {
+            self.audioView.setPlayUrl(url: filePath)
+        }
+        self.audioView.addTapGesture { (tap) in
+            self.playAudio(info: info)
+        }
+        self.constraintWithContent(contentView: self.audioView)
+    }
+    
+    private func playAudio(info: IMMessageBodyInfo) {
+        if let fileId = info.fileId {
+            O2IMFileManager.shared.getFileLocalUrl(fileId: fileId)
+                .then { (url) in
+                    do {
+                        let data = try Data(contentsOf: url)
+                        AudioPlayerManager.shared.managerAudioWithData(data, toplay: true)
+                    } catch {
+                        DDLogError(error.localizedDescription)
+                    }
+            }.catch { (e) in
+                DDLogError(e.localizedDescription)
+            }
+        } else if let filePath = info.fileTempPath {
+            do {
+                let data = try Data(contentsOf: URL(fileURLWithPath: filePath))
+                AudioPlayerManager.shared.managerAudioWithData(data, toplay: true)
+            } catch {
+                DDLogError(error.localizedDescription)
+            }
+        }
+    }
+    
+    private func constraintWithContent(contentView: UIView) {
+        let top = NSLayoutConstraint(item: contentView, attribute: .top, relatedBy: .equal, toItem: contentView.superview!, attribute: .top, multiplier: 1, constant: 10)
+        let bottom = NSLayoutConstraint(item: contentView.superview!, attribute: .bottom, relatedBy: .equal, toItem: contentView, attribute: .bottom, multiplier: 1, constant: 10)
+        let left = NSLayoutConstraint(item: contentView, attribute: .leading, relatedBy: .equal, toItem: contentView.superview!, attribute: .leading, multiplier: 1, constant: 10)
+        let right = NSLayoutConstraint(item: contentView.superview!, attribute: .trailing, relatedBy: .equal, toItem: contentView, attribute: .trailing, multiplier: 1, constant: 10)
+        NSLayoutConstraint.activate([top, bottom, left, right])
+    }
+    
+    //图片消息
+    private func imageMsgRender(info: IMMessageBodyInfo) {
+        let width: CGFloat = 144
+        let height: CGFloat = 192
+        self.messageBgWidth.constant = width + 20
+        self.messageBgHeight.constant = height + 20
+        //图片
+        let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: width, height: height))
+        if let fileId = info.fileId {
+            DDLogDebug("fileId  :\(fileId)")
+            let urlStr = AppDelegate.o2Collect.generateURLWithAppContextKey(
+                CommunicateContext.communicateContextKey,
+                query: CommunicateContext.imDownloadImageWithSizeQuery,
+                parameter: ["##id##": fileId as AnyObject,
+                    "##width##": "144" as AnyObject,
+                    "##height##": "192" as AnyObject], generateTime: false)
+            if let url = URL(string: urlStr!) {
+                imageView.hnk_setImageFromURL(url)
+            } else {
+                imageView.image = UIImage(named: "chat_image")
+            }
+        } else if let filePath = info.fileTempPath {
+            DDLogDebug("filePath  :\(filePath)")
+            imageView.hnk_setImageFromFile(filePath)
+        } else {
+            imageView.image = UIImage(named: "chat_image")
+        }
+        imageView.translatesAutoresizingMaskIntoConstraints = false
+        self.messageBackgroundView.addSubview(imageView)
+        imageView.addTapGesture { (tap) in
+            self.delegate?.clickImageMessage(fileId: info.fileId, tempPath: info.fileTempPath)
+        }
+        self.constraintWithContent(contentView: imageView)
+    }
     
     private func emojiMsgRender(emoji: String) {
         let emojiSize = 36
@@ -77,11 +176,7 @@ class IMChatMessageSendViewCell: UITableViewCell {
         emojiImage.image = UIImage(named: path, in: bundle, compatibleWith: nil)
         emojiImage.translatesAutoresizingMaskIntoConstraints = false
         self.messageBackgroundView.addSubview(emojiImage)
-        let top = NSLayoutConstraint(item: emojiImage, attribute: .top, relatedBy: .equal, toItem: emojiImage.superview!, attribute: .top, multiplier: 1, constant: 10)
-        let bottom = NSLayoutConstraint(item: emojiImage.superview! , attribute: .bottom, relatedBy: .equal, toItem: emojiImage, attribute: .bottom, multiplier: 1, constant: 10)
-        let left = NSLayoutConstraint(item: emojiImage, attribute: .leading, relatedBy: .equal, toItem: emojiImage.superview!, attribute: .leading, multiplier: 1, constant: 10)
-        let right = NSLayoutConstraint(item: emojiImage.superview!, attribute: .trailing, relatedBy: .equal, toItem: emojiImage, attribute: .trailing, multiplier: 1, constant: 10)
-        NSLayoutConstraint.activate([top, bottom, left, right])
+        self.constraintWithContent(contentView: emojiImage)
     }
     
     private func textMsgRender(msg: String) {

+ 130 - 28
o2ios/O2Platform/App/IM-聊天/View/IMChatMessageViewCell.swift

@@ -9,6 +9,10 @@
 import UIKit
 import CocoaLumberjack
 
+protocol IMChatMessageDelegate {
+    func clickImageMessage(fileId: String?, tempPath: String?)
+}
+
 class IMChatMessageViewCell: UITableViewCell {
 
     @IBOutlet weak var avatarImage: UIImageView!
@@ -19,15 +23,23 @@ class IMChatMessageViewCell: UITableViewCell {
     @IBOutlet weak var messageBackgroundHeight: NSLayoutConstraint!
     private let messageWidth = 176
     
+    private lazy var audioView: IMAudioView = {
+        let view = Bundle.main.loadNibNamed("IMAudioView", owner: self, options: nil)?.first as! IMAudioView
+        view.frame = CGRect(x: 0, y: 0, width: IMAudioView.IMAudioView_width, height: IMAudioView.IMAudioView_height)
+        return view
+    }()
+    
+    var delegate: IMChatMessageDelegate?
+
     override func awakeFromNib() {
         super.awakeFromNib()
-        
+
     }
 
     override func setSelected(_ selected: Bool, animated: Bool) {
         super.setSelected(selected, animated: animated)
     }
-   
+
     //普通通知消息
     func setInstantContent(item: InstantMessage) {
         if let time = item.createTime {
@@ -41,43 +53,43 @@ class IMChatMessageViewCell: UITableViewCell {
             if type.starts(with: "task_") {
                 self.avatarImage.image = UIImage(named: "icon_daiban")
                 self.titleLabel.text = "待办消息"
-            }else if type.starts(with: "taskCompleted_") {
+            } else if type.starts(with: "taskCompleted_") {
                 self.avatarImage.image = UIImage(named: "icon_taskcompleted")
                 self.titleLabel.text = "已办消息"
-            }else if type.starts(with: "read_") {
+            } else if type.starts(with: "read_") {
                 self.avatarImage.image = UIImage(named: "icon_read")
                 self.titleLabel.text = "待阅消息"
-            }else if type.starts(with: "readCompleted_") {
+            } else if type.starts(with: "readCompleted_") {
                 self.avatarImage.image = UIImage(named: "icon_readcompleted")
                 self.titleLabel.text = "已阅消息"
-            }else if type.starts(with: "review_") || type.starts(with: "work_") || type.starts(with: "process_") {
+            } else if type.starts(with: "review_") || type.starts(with: "work_") || type.starts(with: "process_") {
                 self.avatarImage.image = UIImage(named: "icon_daiban")
                 self.titleLabel.text = "工作消息"
-            }else if type.starts(with: "meeting_") {
+            } else if type.starts(with: "meeting_") {
                 self.avatarImage.image = UIImage(named: "icon_meeting")
                 self.titleLabel.text = "会议消息"
-            }else if type.starts(with: "attachment_") {
+            } else if type.starts(with: "attachment_") {
                 self.avatarImage.image = UIImage(named: "icon_yunpan")
                 self.titleLabel.text = "云盘消息"
-            }else if type.starts(with: "calendar_") {
+            } else if type.starts(with: "calendar_") {
                 self.avatarImage.image = UIImage(named: "icon_calendar")
                 self.titleLabel.text = "日历消息"
-            }else if type.starts(with: "cms_") {
+            } else if type.starts(with: "cms_") {
                 self.avatarImage.image = UIImage(named: "icon_cms")
                 self.titleLabel.text = "信息中心消息"
-            }else if type.starts(with: "bbs_") {
+            } else if type.starts(with: "bbs_") {
                 self.avatarImage.image = UIImage(named: "icon_bbs")
                 self.titleLabel.text = "论坛消息"
-            }else if type.starts(with: "mind_") {
+            } else if type.starts(with: "mind_") {
                 self.avatarImage.image = UIImage(named: "icon_mindMap")
                 self.titleLabel.text = "脑图消息"
-            }else {
+            } else {
                 self.avatarImage.image = UIImage(named: "icon_email")
                 self.titleLabel.text = "其他消息"
             }
         }
     }
-    
+
     //聊天消息
     func setContent(item: IMMessageInfo) {
         //time
@@ -87,15 +99,15 @@ class IMChatMessageViewCell: UITableViewCell {
         }
         //name avatart
         if let person = item.createPerson {
-            let urlstr = AppDelegate.o2Collect.generateURLWithAppContextKey(ContactContext.contactsContextKeyV2, query: ContactContext.personIconByNameQueryV2, parameter: ["##name##":person as AnyObject], generateTime: false)
+            let urlstr = AppDelegate.o2Collect.generateURLWithAppContextKey(ContactContext.contactsContextKeyV2, query: ContactContext.personIconByNameQueryV2, parameter: ["##name##": person as AnyObject], generateTime: false)
             if let u = URL(string: urlstr!) {
                 self.avatarImage.hnk_setImageFromURL(u)
-            }else {
+            } else {
                 self.avatarImage.image = UIImage(named: "icon_men")
             }
             //姓名
             self.titleLabel.text = person.split("@").first ?? ""
-        }else {
+        } else {
             self.avatarImage.image = UIImage(named: "icon_men")
             self.titleLabel.text = ""
         }
@@ -103,12 +115,106 @@ class IMChatMessageViewCell: UITableViewCell {
         if let jsonBody = item.body, let body = parseJson(msg: jsonBody) {
             if body.type == o2_im_msg_type_emoji {
                 emojiMsgRender(emoji: body.body!)
-            }else {
+            } else if body.type == o2_im_msg_type_image {
+                imageMsgRender(info: body)
+            } else if o2_im_msg_type_audio == body.type {
+                audioMsgRender(info: body)
+            } else {
                 textMsgRender(msg: body.body!)
             }
         }
     }
     
+    //音频消息
+    private func audioMsgRender(info: IMMessageBodyInfo) {
+        self.messageBackgroundWidth.constant = IMAudioView.IMAudioView_width + 20
+        self.messageBackgroundHeight.constant = IMAudioView.IMAudioView_height + 20
+        self.audioView.translatesAutoresizingMaskIntoConstraints = false
+        self.messageBackgroundView.addSubview(self.audioView)
+        self.audioView.setDuration(duration: info.audioDuration ?? "0")
+        //音频文件
+        if let fileId = info.fileId {
+            let urlStr = AppDelegate.o2Collect.generateURLWithAppContextKey(
+            CommunicateContext.communicateContextKey,
+            query: CommunicateContext.imDownloadFileQuery,
+            parameter: ["##id##": fileId as AnyObject], generateTime: false)
+            self.audioView.setPlayUrl(url: urlStr)
+        } else if let filePath = info.fileTempPath {
+            self.audioView.setPlayUrl(url: filePath)
+        }
+        self.audioView.addTapGesture { (tap) in
+            self.playAudio(info: info)
+        }
+        self.constraintWithContent(contentView: self.audioView)
+    }
+    
+    private func playAudio(info: IMMessageBodyInfo) {
+        if let fileId = info.fileId {
+            O2IMFileManager.shared.getFileLocalUrl(fileId: fileId)
+                .then { (url) in
+                    do {
+                        let data = try Data(contentsOf: url)
+                        AudioPlayerManager.shared.managerAudioWithData(data, toplay: true)
+                    } catch {
+                        DDLogError(error.localizedDescription)
+                    }
+            }.catch { (e) in
+                DDLogError(e.localizedDescription)
+            }
+        } else if let filePath = info.fileTempPath {
+            do {
+                let data = try Data(contentsOf: URL(fileURLWithPath: filePath))
+                AudioPlayerManager.shared.managerAudioWithData(data, toplay: true)
+            } catch {
+                DDLogError(error.localizedDescription)
+            }
+        }
+    }
+    
+    private func constraintWithContent(contentView: UIView) {
+        let top = NSLayoutConstraint(item: contentView, attribute: .top, relatedBy: .equal, toItem: contentView.superview!, attribute: .top, multiplier: 1, constant: 10)
+        let bottom = NSLayoutConstraint(item: contentView.superview!, attribute: .bottom, relatedBy: .equal, toItem: contentView, attribute: .bottom, multiplier: 1, constant: 10)
+        let left = NSLayoutConstraint(item: contentView, attribute: .leading, relatedBy: .equal, toItem: contentView.superview!, attribute: .leading, multiplier: 1, constant: 10)
+        let right = NSLayoutConstraint(item: contentView.superview!, attribute: .trailing, relatedBy: .equal, toItem: contentView, attribute: .trailing, multiplier: 1, constant: 10)
+        NSLayoutConstraint.activate([top, bottom, left, right])
+    }
+
+    //图片消息
+    private func imageMsgRender(info: IMMessageBodyInfo) {
+        let width: CGFloat = 144
+        let height: CGFloat = 192
+        self.messageBackgroundWidth.constant = width + 20
+        self.messageBackgroundHeight.constant = height + 20
+        //图片
+        let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: width, height: height))
+        if let fileId = info.fileId {
+            DDLogDebug("file id :\(fileId)")
+            let urlStr = AppDelegate.o2Collect.generateURLWithAppContextKey(
+                CommunicateContext.communicateContextKey,
+                query: CommunicateContext.imDownloadImageWithSizeQuery,
+                parameter: ["##id##": fileId as AnyObject,
+                    "##width##": "144" as AnyObject,
+                    "##height##": "192" as AnyObject], generateTime: false)
+            if let url = URL(string: urlStr!) {
+                imageView.hnk_setImageFromURL(url)
+            } else {
+                imageView.image = UIImage(named: "chat_image")
+            }
+        } else if let filePath = info.fileTempPath {
+            DDLogDebug("filePath  :\(filePath)")
+            imageView.hnk_setImageFromFile(filePath)
+        } else {
+            imageView.image = UIImage(named: "chat_image")
+        }
+        imageView.translatesAutoresizingMaskIntoConstraints = false
+        self.messageBackgroundView.addSubview(imageView)
+        imageView.addTapGesture { (tap) in
+            self.delegate?.clickImageMessage(fileId: info.fileId, tempPath: info.fileTempPath)
+        }
+        self.constraintWithContent(contentView: imageView)
+
+    }
+
     private func emojiMsgRender(emoji: String) {
         let emojiSize = 36
         let width = CGFloat(emojiSize + 20)
@@ -129,13 +235,9 @@ class IMChatMessageViewCell: UITableViewCell {
         emojiImage.image = UIImage(named: path, in: bundle, compatibleWith: nil)
         emojiImage.translatesAutoresizingMaskIntoConstraints = false
         self.messageBackgroundView.addSubview(emojiImage)
-        let top = NSLayoutConstraint(item: emojiImage, attribute: .top, relatedBy: .equal, toItem: emojiImage.superview!, attribute: .top, multiplier: 1, constant: 10)
-        let bottom = NSLayoutConstraint(item: emojiImage.superview! , attribute: .bottom, relatedBy: .equal, toItem: emojiImage, attribute: .bottom, multiplier: 1, constant: 10)
-        let left = NSLayoutConstraint(item: emojiImage, attribute: .leading, relatedBy: .equal, toItem: emojiImage.superview!, attribute: .leading, multiplier: 1, constant: 10)
-        let right = NSLayoutConstraint(item: emojiImage.superview!, attribute: .trailing, relatedBy: .equal, toItem: emojiImage, attribute: .trailing, multiplier: 1, constant: 10)
-        NSLayoutConstraint.activate([top, bottom, left, right])
+        self.constraintWithContent(contentView: emojiImage)
     }
-    
+
     private func textMsgRender(msg: String) {
         let size = calTextSize(str: msg)
         self.messageBackgroundWidth.constant = size.width + 20
@@ -156,7 +258,7 @@ class IMChatMessageViewCell: UITableViewCell {
         let right = NSLayoutConstraint(item: label.superview!, attribute: .trailing, relatedBy: .equal, toItem: label, attribute: .trailing, multiplier: 1, constant: 10)
         NSLayoutConstraint.activate([top, left, right])
     }
-    
+
     private func generateMessagelabel(str: String, size: CGSize) -> UILabel {
         let label = UILabel(frame: CGRect(x: 0, y: 0, width: size.width, height: size.height))
         label.text = str
@@ -166,13 +268,13 @@ class IMChatMessageViewCell: UITableViewCell {
         label.preferredMaxLayoutWidth = size.width
         return label
     }
-    
-    
+
+
     private func calTextSize(str: String) -> CGSize {
         let size = CGSize(width: messageWidth.toCGFloat, height: CGFloat(MAXFLOAT))
         return str.boundingRect(with: size, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16)], context: nil).size
     }
-    
+
     //解析json为消息对象
     private func parseJson(msg: String) -> IMMessageBodyInfo? {
         return IMMessageBodyInfo.deserialize(from: msg)

+ 4 - 3
o2ios/O2Platform/App/IM-聊天/View/IMConversationItemCell.swift

@@ -81,17 +81,18 @@ class IMConversationItemCell: UITableViewCell {
         }
         // message
         if let msgBody = conversation.lastMessage?.body, let body = parseJson(msg: msgBody) {
-            if body.type == o2_im_msg_type_text {
+            
+            if body.type == o2_im_msg_type_text || body.type == o2_im_msg_type_image || body.type == o2_im_msg_type_audio {
                 self.messageLabel.text = body.body
                 self.messageLabel.isHidden = false
                 self.emojiImg.isHidden = true
-            }else if body.type == o2_im_msg_type_emoji {
+            } else if body.type == o2_im_msg_type_emoji {
                 self.messageLabel.isHidden = true
                 self.emojiImg.isHidden = false
                 let bundle = Bundle().o2EmojiBundle(anyClass: IMConversationItemCell.self)
                 let path = o2ImEmojiPath(emojiBody: body.body!)
                 self.emojiImg.image = UIImage(named: path, in: bundle, compatibleWith: nil)
-            }else {
+            } else {
                 self.messageLabel.isHidden = true
                 self.emojiImg.isHidden = true
             }

+ 1 - 1
o2ios/O2Platform/App/IM-聊天/View/IMConversationItemCell.xib

@@ -44,7 +44,7 @@
                         <color key="textColor" white="0.66666666666666663" 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="I3j-lM-VtV" customClass="IMCon">
+                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="这里是消息" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="I3j-lM-VtV">
                         <rect key="frame" x="70" y="37" width="245" height="17"/>
                         <fontDescription key="fontDescription" type="system" pointSize="14"/>
                         <color key="textColor" systemColor="systemGrayColor" red="0.5568627451" green="0.5568627451" blue="0.57647058819999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>

+ 14 - 7
o2ios/O2Platform/App/Work-工作/c/TodoTaskDetailViewController.swift

@@ -869,9 +869,8 @@ extension TodoTaskDetailViewController: O2WKScriptMessageHandlerImplement {
         self.uploadAttachment(site, uploadURL: updloadURL!)
     }
     private func uploadAttachment(_ site:String,uploadURL url:String){
-        let vc = FileBSImagePickerViewController()
-        bs_presentImagePickerController(vc, animated: true,
-                                        select: { (asset: PHAsset) -> Void in
+        let vc = FileBSImagePickerViewController().bsImagePicker()
+        presentImagePicker(vc, select: { (asset: PHAsset) -> Void in
                                             // User selected an asset.
                                             // Do something with it, start upload perhaps?
         }, deselect: { (asset: PHAsset) -> Void in
@@ -903,10 +902,19 @@ extension TodoTaskDetailViewController: O2WKScriptMessageHandlerImplement {
                         DispatchQueue.main.async {
                             self.showLoading(title: "上传中...")
                         }
+                        var newData = imageData
+                        //处理图片旋转的问题
+                        if imageOrientation != UIImage.Orientation.up && imageData != nil {
+                            let newImage = UIImage(data: imageData!)?.fixOrientation()
+                            if newImage != nil {
+                                newData = newImage?.pngData()
+                            }
+                        }
+                        
                         DispatchQueue.global(qos: .userInitiated).async {
                             Alamofire.upload(multipartFormData: { (mData) in
                                 //mData.append(fileURL, withName: "file")
-                                mData.append(imageData!, withName: "file", fileName: fileName, mimeType: "application/octet-stream")
+                                mData.append(newData!, withName: "file", fileName: fileName, mimeType: "application/octet-stream")
                                 let siteData = site.data(using: String.Encoding.utf8, allowLossyConversion: false)
                                 mData.append(siteData!, withName: "site")
                             }, to: url, encodingCompletion: { (encodingResult) in
@@ -1047,9 +1055,8 @@ extension TodoTaskDetailViewController: O2WKScriptMessageHandlerImplement {
     }
     
     private func replaceAttachment(_ site:String,_ attachmentId:String,replaceURL url:String){
-        let vc = FileBSImagePickerViewController()
-        bs_presentImagePickerController(vc, animated: true,
-                                        select: { (asset: PHAsset) -> Void in
+        let vc = FileBSImagePickerViewController().bsImagePicker()
+        presentImagePicker(vc, select: { (asset: PHAsset) -> Void in
                                             // User selected an asset.
                                             // Do something with it, start upload perhaps?
         }, deselect: { (asset: PHAsset) -> Void in

+ 6 - 0
o2ios/O2Platform/Assets.xcassets/im/Contents.json

@@ -0,0 +1,6 @@
+{
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

+ 22 - 0
o2ios/O2Platform/Assets.xcassets/im/chat_audio_play_Normal.imageset/Contents.json

@@ -0,0 +1,22 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "chat_icon_audio_play_Normal@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "chat_icon_audio_play_Normal@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
o2ios/O2Platform/Assets.xcassets/im/chat_audio_play_Normal.imageset/chat_icon_audio_play_Normal@2x.png


BIN
o2ios/O2Platform/Assets.xcassets/im/chat_audio_play_Normal.imageset/chat_icon_audio_play_Normal@3x.png


+ 0 - 0
o2ios/O2Platform/Assets.xcassets/首页/chat_bubble_incomming.imageset/Contents.json → o2ios/O2Platform/Assets.xcassets/im/chat_bubble_incomming.imageset/Contents.json


+ 0 - 0
o2ios/O2Platform/Assets.xcassets/首页/chat_bubble_incomming.imageset/chat_bubble_incomming@2x.png → o2ios/O2Platform/Assets.xcassets/im/chat_bubble_incomming.imageset/chat_bubble_incomming@2x.png


+ 0 - 0
o2ios/O2Platform/Assets.xcassets/首页/chat_bubble_incomming.imageset/chat_bubble_incomming@3x.png → o2ios/O2Platform/Assets.xcassets/im/chat_bubble_incomming.imageset/chat_bubble_incomming@3x.png


+ 0 - 0
o2ios/O2Platform/Assets.xcassets/首页/chat_bubble_outgoing.imageset/Contents.json → o2ios/O2Platform/Assets.xcassets/im/chat_bubble_outgoing.imageset/Contents.json


+ 0 - 0
o2ios/O2Platform/Assets.xcassets/首页/chat_bubble_outgoing.imageset/chat_bubble_outgoing@2x.png → o2ios/O2Platform/Assets.xcassets/im/chat_bubble_outgoing.imageset/chat_bubble_outgoing@2x.png


+ 0 - 0
o2ios/O2Platform/Assets.xcassets/首页/chat_bubble_outgoing.imageset/chat_bubble_outgoing@3x.png → o2ios/O2Platform/Assets.xcassets/im/chat_bubble_outgoing.imageset/chat_bubble_outgoing@3x.png


+ 21 - 0
o2ios/O2Platform/Assets.xcassets/im/chat_camera.imageset/Contents.json

@@ -0,0 +1,21 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "chat_camera.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
o2ios/O2Platform/Assets.xcassets/im/chat_camera.imageset/chat_camera.png


+ 21 - 0
o2ios/O2Platform/Assets.xcassets/im/chat_image.imageset/Contents.json

@@ -0,0 +1,21 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "chat_image@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
o2ios/O2Platform/Assets.xcassets/im/chat_image.imageset/chat_image@2x.png


+ 21 - 0
o2ios/O2Platform/Assets.xcassets/im/chat_img.imageset/Contents.json

@@ -0,0 +1,21 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "chat_img.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
o2ios/O2Platform/Assets.xcassets/im/chat_img.imageset/chat_img.png


+ 21 - 0
o2ios/O2Platform/Assets.xcassets/im/chat_location.imageset/Contents.json

@@ -0,0 +1,21 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "chat_location.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
o2ios/O2Platform/Assets.xcassets/im/chat_location.imageset/chat_location.png


+ 21 - 0
o2ios/O2Platform/Assets.xcassets/im/chat_mic.imageset/Contents.json

@@ -0,0 +1,21 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "chat_mic.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
o2ios/O2Platform/Assets.xcassets/im/chat_mic.imageset/chat_mic.png


+ 0 - 0
o2ios/O2Platform/Assets.xcassets/首页/group_default.imageset/Contents.json → o2ios/O2Platform/Assets.xcassets/im/group_default.imageset/Contents.json


+ 0 - 0
o2ios/O2Platform/Assets.xcassets/首页/group_default.imageset/group_default@2x.png → o2ios/O2Platform/Assets.xcassets/im/group_default.imageset/group_default@2x.png


+ 15 - 2
o2ios/O2Platform/Extension/UIViewController+Extension.swift

@@ -325,10 +325,23 @@ extension UIViewController {
 // MARK: - 业务工具
 extension UIViewController {
     
+    
+    func takePhoto(delegate: (UIImagePickerControllerDelegate & UINavigationControllerDelegate)?) {
+        var sourceType = UIImagePickerController.SourceType.camera
+        if !UIImagePickerController.isSourceTypeAvailable(sourceType) {
+            sourceType = .photoLibrary
+        }
+        let picker = UIImagePickerController()
+        picker.allowsEditing = true
+        picker.sourceType = sourceType
+        picker.delegate = delegate
+        self.present(picker, animated:true, completion:nil)//进入照相界面
+    }
+    
     //照片选择器
     func choosePhotoWithImagePicker(callback: @escaping (String, Data)-> Void) {
-        let chooseImage = FileBSImagePickerViewController()
-        self.bs_presentImagePickerController(chooseImage, animated: true, select: nil, deselect: nil, cancel: nil, finish: {
+        let vc = FileBSImagePickerViewController().bsImagePicker()
+        presentImagePicker(vc, select: nil, deselect: nil, cancel: nil, finish: {
             (arr) in
             let count = arr.count
             print("选择了照片数量:\(count)")

+ 2 - 5
o2ios/O2Platform/Framework/JMessage/ChatModule/Chat/ViewController/JCChatViewController.swift

@@ -677,11 +677,8 @@ extension JCChatViewController: SAIToolboxInputViewDataSource, SAIToolboxInputVi
 //        present(vc, animated: true)
         
         
-        let vc = FileBSImagePickerViewController()
-        vc.defaultmaxNumberOfSelections = 9
-        vc.defaultTakePhotos = false
-        bs_presentImagePickerController(vc, animated: true,
-                                        select: { (asset: PHAsset) -> Void in
+        let vc = FileBSImagePickerViewController().bsImagePicker()
+        presentImagePicker(vc, select: { (asset: PHAsset) -> Void in
                                             // User selected an asset.
                                             // Do something with it, start upload perhaps?
         }, deselect: { (asset: PHAsset) -> Void in

+ 23 - 2
o2ios/O2Platform/Framework/O2API/Communicate/CommunicateAPI.swift

@@ -18,6 +18,8 @@ enum CommunicateAPI {
     case readConversation(String)
     case instantMessageList(Int)
     case createConversation(IMConversationInfo)
+    case imUploadFile(String, String, String, Data)
+    case imDownloadFullFile(String)
     
     
 }
@@ -57,14 +59,18 @@ extension CommunicateAPI: TargetType {
             return "/jaxrs/instant/list/currentperson/noim/count/\(count)/desc"
         case .createConversation(_):
             return "/jaxrs/im/conversation"
+        case .imUploadFile(let conversationId, let type, _, _):
+            return "/jaxrs/im/msg/upload/\(conversationId)/type/\(type)"
+        case .imDownloadFullFile(let id):
+            return "/jaxrs/im/msg/download/\(id)"
         }
     }
     
     var method: Moya.Method {
         switch self {
-        case .myConversationList, .instantMessageList(_):
+        case .myConversationList, .instantMessageList(_), .imDownloadFullFile(_):
             return .get
-        case .msgListByPaging(_, _, _), .sendMsg(_), .createConversation(_):
+        case .msgListByPaging(_, _, _), .sendMsg(_), .createConversation(_), .imUploadFile(_, _, _, _):
             return .post
         case .readConversation(_):
             return .put
@@ -87,11 +93,26 @@ extension CommunicateAPI: TargetType {
             return .requestParameters(parameters: msg.toJSON()!, encoding: JSONEncoding.default)
         case .createConversation(let conv):
             return .requestParameters(parameters: conv.toJSON()!, encoding: JSONEncoding.default)
+        case .imUploadFile(_, _, let fileName, let data):
+            //字符串类型 文件名
+            let strData = fileName.data(using: .utf8)
+            let fileNameData = MultipartFormData(provider: .data(strData!), name: "fileName")
+            //文件类型
+            let fileData = MultipartFormData(provider: .data(data), name: "file", fileName: fileName)
+            return .uploadMultipart([fileData, fileNameData])
+        case .imDownloadFullFile(let id):
+            let myDest:DownloadDestination = { temporaryURL, response in
+                //本地存储
+                return (O2IMFileManager.shared.localFilePath(fileId: id), [.removePreviousFile, .createIntermediateDirectories])
+            }
+            return .downloadDestination(myDest)
         }
     }
     
     var headers: [String : String]? {
         return nil
     }
+    
+     
    
 }

+ 6 - 2
o2ios/O2Platform/Framework/Utils/FileUtil.swift

@@ -15,9 +15,9 @@ public class FileUtil {
     private init () {}
     
     public func createDirectory(path: String) {
-        if FileManager.default.fileExists(atPath: path, isDirectory: nil) {
+        if !directoryExist(directoryPath: path) {
             do {
-                try FileManager.default.createDirectory(atPath: path, withIntermediateDirectories: false, attributes: nil)
+                try FileManager.default.createDirectory(atPath: path, withIntermediateDirectories: true, attributes: nil)
             } catch {
                 print("创建目录错误!")
             }
@@ -104,6 +104,10 @@ public class FileUtil {
         return NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first!
     }
     
+    public func cacheDir() -> URL {
+        return FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
+    }
+    
     public func cacheDirectoryPath(file: String) -> String {
         return (self.cacheDirectory() as NSString).appendingPathComponent(file)
     }

+ 17 - 0
o2ios/O2Platform/Framework/lame/AmrCodec.h

@@ -0,0 +1,17 @@
+//
+//  codec.h
+//  SwiftAmrWaveConvertor
+//
+//  Created by zhenlintie on 2017/3/10.
+//  Copyright © 2017年 sTeven. All rights reserved.
+//
+
+#ifndef codec_h
+#define codec_h
+
+#import "interf_dec.h"
+#import "interf_enc.h"
+#import "dec_if.h"
+#import "enc_if.h"
+
+#endif /* codec_h */

+ 15 - 0
o2ios/O2Platform/Framework/lame/ConvertMp3.h

@@ -0,0 +1,15 @@
+//
+//  ConvertMp3.h
+//  SwiftRecorder
+//
+//  Created by iOS on 2018/9/25.
+//  Copyright © 2018 AidaHe. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@interface ConvertMp3 : NSObject
+
+- (void) audioPCMtoMP3:(NSString *)audioFileSavePath mp3File:(NSString *)mp3FilePath;
+    
+@end

+ 66 - 0
o2ios/O2Platform/Framework/lame/ConvertMp3.m

@@ -0,0 +1,66 @@
+//
+//  ConvertMp3.m
+//  SwiftRecorder
+//
+//  Created by iOS on 2018/9/25.
+//  Copyright © 2018 AidaHe. All rights reserved.
+//
+
+#import "ConvertMp3.h"
+#import "lame.h"
+
+#define KFILESIZE (1 * 1024 * 1024)
+
+@implementation ConvertMp3
+
+- (void) audioPCMtoMP3:(NSString *)audioFileSavePath mp3File:(NSString *)mp3FilePath{
+    NSLog(@"转码开始---");
+    CFAbsoluteTime startTime =CFAbsoluteTimeGetCurrent();
+
+    @try {
+        int read, write;
+        
+        FILE *pcm = fopen([audioFileSavePath cStringUsingEncoding:1], "rb");  //source 被转换的音频文件位置
+        fseek(pcm, 4*1024, SEEK_CUR);                                   //skip file header
+        FILE *mp3 = fopen([mp3FilePath cStringUsingEncoding:1], "wb");  //output 输出生成的Mp3文件位置
+        
+        const int PCM_SIZE = 8192;
+        const int MP3_SIZE = 8192;
+        short int pcm_buffer[PCM_SIZE*2];
+        unsigned char mp3_buffer[MP3_SIZE];
+        
+        lame_t lame = lame_init();
+        //设置声道1单声道,2双声道
+        lame_set_num_channels(lame,1);
+        lame_set_in_samplerate(lame, 8000.0);
+        lame_set_VBR(lame, vbr_off);
+        lame_init_params(lame);
+        
+        do {
+            read = (int)fread(pcm_buffer, 2*sizeof(short int), PCM_SIZE, pcm);
+            
+            if (read == 0)
+                write = lame_encode_flush(lame, mp3_buffer, MP3_SIZE);
+            else
+                write = lame_encode_buffer_interleaved(lame,pcm_buffer, read, mp3_buffer, MP3_SIZE);
+            
+            fwrite(mp3_buffer, write, 1, mp3);
+        } while (read != 0);
+        
+        lame_close(lame);
+        fclose(mp3);
+        fclose(pcm);
+    }
+    @catch (NSException *exception) {
+        NSLog(@"%@",[exception description]);
+    }
+    NSLog(@"转码结束----");
+    CFAbsoluteTime linkTime = (CFAbsoluteTimeGetCurrent() - startTime);
+    
+    NSLog(@"Linked in %f ms", linkTime *1000.0);
+
+    
+}
+
+
+@end

+ 1323 - 0
o2ios/O2Platform/Framework/lame/lame.h

@@ -0,0 +1,1323 @@
+/*
+ *	Interface to MP3 LAME encoding engine
+ *
+ *	Copyright (c) 1999 Mark Taylor
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/* $Id: lame.h,v 1.189.2.1 2012/01/08 23:49:58 robert Exp $ */
+
+#ifndef LAME_LAME_H
+#define LAME_LAME_H
+
+/* for size_t typedef */
+#include <stddef.h>
+/* for va_list typedef */
+#include <stdarg.h>
+/* for FILE typedef, TODO: remove when removing lame_mp3_tags_fid */
+#include <stdio.h>
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+typedef void (*lame_report_function)(const char *format, va_list ap);
+
+#if defined(WIN32) || defined(_WIN32)
+#undef CDECL
+#define CDECL __cdecl
+#else
+#define CDECL
+#endif
+
+#define DEPRECATED_OR_OBSOLETE_CODE_REMOVED 1
+
+typedef enum vbr_mode_e {
+  vbr_off=0,
+  vbr_mt,               /* obsolete, same as vbr_mtrh */
+  vbr_rh,
+  vbr_abr,
+  vbr_mtrh,
+  vbr_max_indicator,    /* Don't use this! It's used for sanity checks.       */
+  vbr_default=vbr_mtrh    /* change this to change the default VBR mode of LAME */
+} vbr_mode;
+
+
+/* MPEG modes */
+typedef enum MPEG_mode_e {
+  STEREO = 0,
+  JOINT_STEREO,
+  DUAL_CHANNEL,   /* LAME doesn't supports this! */
+  MONO,
+  NOT_SET,
+  MAX_INDICATOR   /* Don't use this! It's used for sanity checks. */
+} MPEG_mode;
+
+/* Padding types */
+typedef enum Padding_type_e {
+  PAD_NO = 0,
+  PAD_ALL,
+  PAD_ADJUST,
+  PAD_MAX_INDICATOR   /* Don't use this! It's used for sanity checks. */
+} Padding_type;
+
+
+
+/*presets*/
+typedef enum preset_mode_e {
+    /*values from 8 to 320 should be reserved for abr bitrates*/
+    /*for abr I'd suggest to directly use the targeted bitrate as a value*/
+    ABR_8 = 8,
+    ABR_320 = 320,
+
+    V9 = 410, /*Vx to match Lame and VBR_xx to match FhG*/
+    VBR_10 = 410,
+    V8 = 420,
+    VBR_20 = 420,
+    V7 = 430,
+    VBR_30 = 430,
+    V6 = 440,
+    VBR_40 = 440,
+    V5 = 450,
+    VBR_50 = 450,
+    V4 = 460,
+    VBR_60 = 460,
+    V3 = 470,
+    VBR_70 = 470,
+    V2 = 480,
+    VBR_80 = 480,
+    V1 = 490,
+    VBR_90 = 490,
+    V0 = 500,
+    VBR_100 = 500,
+
+
+
+    /*still there for compatibility*/
+    R3MIX = 1000,
+    STANDARD = 1001,
+    EXTREME = 1002,
+    INSANE = 1003,
+    STANDARD_FAST = 1004,
+    EXTREME_FAST = 1005,
+    MEDIUM = 1006,
+    MEDIUM_FAST = 1007
+} preset_mode;
+
+
+/*asm optimizations*/
+typedef enum asm_optimizations_e {
+    MMX = 1,
+    AMD_3DNOW = 2,
+    SSE = 3
+} asm_optimizations;
+
+
+/* psychoacoustic model */
+typedef enum Psy_model_e {
+    PSY_GPSYCHO = 1,
+    PSY_NSPSYTUNE = 2
+} Psy_model;
+
+
+/* buffer considerations */
+typedef enum buffer_constraint_e {
+    MDB_DEFAULT=0,
+    MDB_STRICT_ISO=1,
+    MDB_MAXIMUM=2
+} buffer_constraint;
+
+
+struct lame_global_struct;
+typedef struct lame_global_struct lame_global_flags;
+typedef lame_global_flags *lame_t;
+
+
+
+
+/***********************************************************************
+ *
+ *  The LAME API
+ *  These functions should be called, in this order, for each
+ *  MP3 file to be encoded.  See the file "API" for more documentation
+ *
+ ***********************************************************************/
+
+
+/*
+ * REQUIRED:
+ * initialize the encoder.  sets default for all encoder parameters,
+ * returns NULL if some malloc()'s failed
+ * otherwise returns pointer to structure needed for all future
+ * API calls.
+ */
+lame_global_flags * CDECL lame_init(void);
+#if DEPRECATED_OR_OBSOLETE_CODE_REMOVED
+#else
+/* obsolete version */
+int CDECL lame_init_old(lame_global_flags *);
+#endif
+
+/*
+ * OPTIONAL:
+ * set as needed to override defaults
+ */
+
+/********************************************************************
+ *  input stream description
+ ***********************************************************************/
+/* number of samples.  default = 2^32-1   */
+int CDECL lame_set_num_samples(lame_global_flags *, unsigned long);
+unsigned long CDECL lame_get_num_samples(const lame_global_flags *);
+
+/* input sample rate in Hz.  default = 44100hz */
+int CDECL lame_set_in_samplerate(lame_global_flags *, int);
+int CDECL lame_get_in_samplerate(const lame_global_flags *);
+
+/* number of channels in input stream. default=2  */
+int CDECL lame_set_num_channels(lame_global_flags *, int);
+int CDECL lame_get_num_channels(const lame_global_flags *);
+
+/*
+  scale the input by this amount before encoding.  default=1
+  (not used by decoding routines)
+*/
+int CDECL lame_set_scale(lame_global_flags *, float);
+float CDECL lame_get_scale(const lame_global_flags *);
+
+/*
+  scale the channel 0 (left) input by this amount before encoding.  default=1
+  (not used by decoding routines)
+*/
+int CDECL lame_set_scale_left(lame_global_flags *, float);
+float CDECL lame_get_scale_left(const lame_global_flags *);
+
+/*
+  scale the channel 1 (right) input by this amount before encoding.  default=1
+  (not used by decoding routines)
+*/
+int CDECL lame_set_scale_right(lame_global_flags *, float);
+float CDECL lame_get_scale_right(const lame_global_flags *);
+
+/*
+  output sample rate in Hz.  default = 0, which means LAME picks best value
+  based on the amount of compression.  MPEG only allows:
+  MPEG1    32, 44.1,   48khz
+  MPEG2    16, 22.05,  24
+  MPEG2.5   8, 11.025, 12
+  (not used by decoding routines)
+*/
+int CDECL lame_set_out_samplerate(lame_global_flags *, int);
+int CDECL lame_get_out_samplerate(const lame_global_flags *);
+
+
+/********************************************************************
+ *  general control parameters
+ ***********************************************************************/
+/* 1=cause LAME to collect data for an MP3 frame analyzer. default=0 */
+int CDECL lame_set_analysis(lame_global_flags *, int);
+int CDECL lame_get_analysis(const lame_global_flags *);
+
+/*
+  1 = write a Xing VBR header frame.
+  default = 1
+  this variable must have been added by a Hungarian notation Windows programmer :-)
+*/
+int CDECL lame_set_bWriteVbrTag(lame_global_flags *, int);
+int CDECL lame_get_bWriteVbrTag(const lame_global_flags *);
+
+/* 1=decode only.  use lame/mpglib to convert mp3/ogg to wav.  default=0 */
+int CDECL lame_set_decode_only(lame_global_flags *, int);
+int CDECL lame_get_decode_only(const lame_global_flags *);
+
+#if DEPRECATED_OR_OBSOLETE_CODE_REMOVED
+#else
+/* 1=encode a Vorbis .ogg file.  default=0 */
+/* DEPRECATED */
+int CDECL lame_set_ogg(lame_global_flags *, int);
+int CDECL lame_get_ogg(const lame_global_flags *);
+#endif
+
+/*
+  internal algorithm selection.  True quality is determined by the bitrate
+  but this variable will effect quality by selecting expensive or cheap algorithms.
+  quality=0..9.  0=best (very slow).  9=worst.
+  recommended:  2     near-best quality, not too slow
+                5     good quality, fast
+                7     ok quality, really fast
+*/
+int CDECL lame_set_quality(lame_global_flags *, int);
+int CDECL lame_get_quality(const lame_global_flags *);
+
+/*
+  mode = 0,1,2,3 = stereo, jstereo, dual channel (not supported), mono
+  default: lame picks based on compression ration and input channels
+*/
+int CDECL lame_set_mode(lame_global_flags *, MPEG_mode);
+MPEG_mode CDECL lame_get_mode(const lame_global_flags *);
+
+#if DEPRECATED_OR_OBSOLETE_CODE_REMOVED
+#else
+/*
+  mode_automs.  Use a M/S mode with a switching threshold based on
+  compression ratio
+  DEPRECATED
+*/
+int CDECL lame_set_mode_automs(lame_global_flags *, int);
+int CDECL lame_get_mode_automs(const lame_global_flags *);
+#endif
+
+/*
+  force_ms.  Force M/S for all frames.  For testing only.
+  default = 0 (disabled)
+*/
+int CDECL lame_set_force_ms(lame_global_flags *, int);
+int CDECL lame_get_force_ms(const lame_global_flags *);
+
+/* use free_format?  default = 0 (disabled) */
+int CDECL lame_set_free_format(lame_global_flags *, int);
+int CDECL lame_get_free_format(const lame_global_flags *);
+
+/* perform ReplayGain analysis?  default = 0 (disabled) */
+int CDECL lame_set_findReplayGain(lame_global_flags *, int);
+int CDECL lame_get_findReplayGain(const lame_global_flags *);
+
+/* decode on the fly. Search for the peak sample. If the ReplayGain
+ * analysis is enabled then perform the analysis on the decoded data
+ * stream. default = 0 (disabled)
+ * NOTE: if this option is set the build-in decoder should not be used */
+int CDECL lame_set_decode_on_the_fly(lame_global_flags *, int);
+int CDECL lame_get_decode_on_the_fly(const lame_global_flags *);
+
+#if DEPRECATED_OR_OBSOLETE_CODE_REMOVED
+#else
+/* DEPRECATED: now does the same as lame_set_findReplayGain()
+   default = 0 (disabled) */
+int CDECL lame_set_ReplayGain_input(lame_global_flags *, int);
+int CDECL lame_get_ReplayGain_input(const lame_global_flags *);
+
+/* DEPRECATED: now does the same as
+   lame_set_decode_on_the_fly() && lame_set_findReplayGain()
+   default = 0 (disabled) */
+int CDECL lame_set_ReplayGain_decode(lame_global_flags *, int);
+int CDECL lame_get_ReplayGain_decode(const lame_global_flags *);
+
+/* DEPRECATED: now does the same as lame_set_decode_on_the_fly()
+   default = 0 (disabled) */
+int CDECL lame_set_findPeakSample(lame_global_flags *, int);
+int CDECL lame_get_findPeakSample(const lame_global_flags *);
+#endif
+
+/* counters for gapless encoding */
+int CDECL lame_set_nogap_total(lame_global_flags*, int);
+int CDECL lame_get_nogap_total(const lame_global_flags*);
+
+int CDECL lame_set_nogap_currentindex(lame_global_flags* , int);
+int CDECL lame_get_nogap_currentindex(const lame_global_flags*);
+
+
+/*
+ * OPTIONAL:
+ * Set printf like error/debug/message reporting functions.
+ * The second argument has to be a pointer to a function which looks like
+ *   void my_debugf(const char *format, va_list ap)
+ *   {
+ *       (void) vfprintf(stdout, format, ap);
+ *   }
+ * If you use NULL as the value of the pointer in the set function, the
+ * lame buildin function will be used (prints to stderr).
+ * To quiet any output you have to replace the body of the example function
+ * with just "return;" and use it in the set function.
+ */
+int CDECL lame_set_errorf(lame_global_flags *, lame_report_function);
+int CDECL lame_set_debugf(lame_global_flags *, lame_report_function);
+int CDECL lame_set_msgf  (lame_global_flags *, lame_report_function);
+
+
+
+/* set one of brate compression ratio.  default is compression ratio of 11.  */
+int CDECL lame_set_brate(lame_global_flags *, int);
+int CDECL lame_get_brate(const lame_global_flags *);
+int CDECL lame_set_compression_ratio(lame_global_flags *, float);
+float CDECL lame_get_compression_ratio(const lame_global_flags *);
+
+
+int CDECL lame_set_preset( lame_global_flags*  gfp, int );
+int CDECL lame_set_asm_optimizations( lame_global_flags*  gfp, int, int );
+
+
+
+/********************************************************************
+ *  frame params
+ ***********************************************************************/
+/* mark as copyright.  default=0 */
+int CDECL lame_set_copyright(lame_global_flags *, int);
+int CDECL lame_get_copyright(const lame_global_flags *);
+
+/* mark as original.  default=1 */
+int CDECL lame_set_original(lame_global_flags *, int);
+int CDECL lame_get_original(const lame_global_flags *);
+
+/* error_protection.  Use 2 bytes from each frame for CRC checksum. default=0 */
+int CDECL lame_set_error_protection(lame_global_flags *, int);
+int CDECL lame_get_error_protection(const lame_global_flags *);
+
+#if DEPRECATED_OR_OBSOLETE_CODE_REMOVED
+#else
+/* padding_type. 0=pad no frames  1=pad all frames 2=adjust padding(default) */
+int CDECL lame_set_padding_type(lame_global_flags *, Padding_type);
+Padding_type CDECL lame_get_padding_type(const lame_global_flags *);
+#endif
+
+/* MP3 'private extension' bit  Meaningless.  default=0 */
+int CDECL lame_set_extension(lame_global_flags *, int);
+int CDECL lame_get_extension(const lame_global_flags *);
+
+/* enforce strict ISO compliance.  default=0 */
+int CDECL lame_set_strict_ISO(lame_global_flags *, int);
+int CDECL lame_get_strict_ISO(const lame_global_flags *);
+
+
+/********************************************************************
+ * quantization/noise shaping
+ ***********************************************************************/
+
+/* disable the bit reservoir. For testing only. default=0 */
+int CDECL lame_set_disable_reservoir(lame_global_flags *, int);
+int CDECL lame_get_disable_reservoir(const lame_global_flags *);
+
+/* select a different "best quantization" function. default=0  */
+int CDECL lame_set_quant_comp(lame_global_flags *, int);
+int CDECL lame_get_quant_comp(const lame_global_flags *);
+int CDECL lame_set_quant_comp_short(lame_global_flags *, int);
+int CDECL lame_get_quant_comp_short(const lame_global_flags *);
+
+int CDECL lame_set_experimentalX(lame_global_flags *, int); /* compatibility*/
+int CDECL lame_get_experimentalX(const lame_global_flags *);
+
+/* another experimental option.  for testing only */
+int CDECL lame_set_experimentalY(lame_global_flags *, int);
+int CDECL lame_get_experimentalY(const lame_global_flags *);
+
+/* another experimental option.  for testing only */
+int CDECL lame_set_experimentalZ(lame_global_flags *, int);
+int CDECL lame_get_experimentalZ(const lame_global_flags *);
+
+/* Naoki's psycho acoustic model.  default=0 */
+int CDECL lame_set_exp_nspsytune(lame_global_flags *, int);
+int CDECL lame_get_exp_nspsytune(const lame_global_flags *);
+
+void CDECL lame_set_msfix(lame_global_flags *, double);
+float CDECL lame_get_msfix(const lame_global_flags *);
+
+
+/********************************************************************
+ * VBR control
+ ***********************************************************************/
+/* Types of VBR.  default = vbr_off = CBR */
+int CDECL lame_set_VBR(lame_global_flags *, vbr_mode);
+vbr_mode CDECL lame_get_VBR(const lame_global_flags *);
+
+/* VBR quality level.  0=highest  9=lowest  */
+int CDECL lame_set_VBR_q(lame_global_flags *, int);
+int CDECL lame_get_VBR_q(const lame_global_flags *);
+
+/* VBR quality level.  0=highest  9=lowest, Range [0,...,10[  */
+int CDECL lame_set_VBR_quality(lame_global_flags *, float);
+float CDECL lame_get_VBR_quality(const lame_global_flags *);
+
+/* Ignored except for VBR=vbr_abr (ABR mode) */
+int CDECL lame_set_VBR_mean_bitrate_kbps(lame_global_flags *, int);
+int CDECL lame_get_VBR_mean_bitrate_kbps(const lame_global_flags *);
+
+int CDECL lame_set_VBR_min_bitrate_kbps(lame_global_flags *, int);
+int CDECL lame_get_VBR_min_bitrate_kbps(const lame_global_flags *);
+
+int CDECL lame_set_VBR_max_bitrate_kbps(lame_global_flags *, int);
+int CDECL lame_get_VBR_max_bitrate_kbps(const lame_global_flags *);
+
+/*
+  1=strictly enforce VBR_min_bitrate.  Normally it will be violated for
+  analog silence
+*/
+int CDECL lame_set_VBR_hard_min(lame_global_flags *, int);
+int CDECL lame_get_VBR_hard_min(const lame_global_flags *);
+
+/* for preset */
+#if DEPRECATED_OR_OBSOLETE_CODE_REMOVED
+#else
+int CDECL lame_set_preset_expopts(lame_global_flags *, int);
+#endif
+
+/********************************************************************
+ * Filtering control
+ ***********************************************************************/
+/* freq in Hz to apply lowpass. Default = 0 = lame chooses.  -1 = disabled */
+int CDECL lame_set_lowpassfreq(lame_global_flags *, int);
+int CDECL lame_get_lowpassfreq(const lame_global_flags *);
+/* width of transition band, in Hz.  Default = one polyphase filter band */
+int CDECL lame_set_lowpasswidth(lame_global_flags *, int);
+int CDECL lame_get_lowpasswidth(const lame_global_flags *);
+
+/* freq in Hz to apply highpass. Default = 0 = lame chooses.  -1 = disabled */
+int CDECL lame_set_highpassfreq(lame_global_flags *, int);
+int CDECL lame_get_highpassfreq(const lame_global_flags *);
+/* width of transition band, in Hz.  Default = one polyphase filter band */
+int CDECL lame_set_highpasswidth(lame_global_flags *, int);
+int CDECL lame_get_highpasswidth(const lame_global_flags *);
+
+
+/********************************************************************
+ * psycho acoustics and other arguments which you should not change
+ * unless you know what you are doing
+ ***********************************************************************/
+
+/* only use ATH for masking */
+int CDECL lame_set_ATHonly(lame_global_flags *, int);
+int CDECL lame_get_ATHonly(const lame_global_flags *);
+
+/* only use ATH for short blocks */
+int CDECL lame_set_ATHshort(lame_global_flags *, int);
+int CDECL lame_get_ATHshort(const lame_global_flags *);
+
+/* disable ATH */
+int CDECL lame_set_noATH(lame_global_flags *, int);
+int CDECL lame_get_noATH(const lame_global_flags *);
+
+/* select ATH formula */
+int CDECL lame_set_ATHtype(lame_global_flags *, int);
+int CDECL lame_get_ATHtype(const lame_global_flags *);
+
+/* lower ATH by this many db */
+int CDECL lame_set_ATHlower(lame_global_flags *, float);
+float CDECL lame_get_ATHlower(const lame_global_flags *);
+
+/* select ATH adaptive adjustment type */
+int CDECL lame_set_athaa_type( lame_global_flags *, int);
+int CDECL lame_get_athaa_type( const lame_global_flags *);
+
+#if DEPRECATED_OR_OBSOLETE_CODE_REMOVED
+#else
+/* select the loudness approximation used by the ATH adaptive auto-leveling  */
+int CDECL lame_set_athaa_loudapprox( lame_global_flags *, int);
+int CDECL lame_get_athaa_loudapprox( const lame_global_flags *);
+#endif
+
+/* adjust (in dB) the point below which adaptive ATH level adjustment occurs */
+int CDECL lame_set_athaa_sensitivity( lame_global_flags *, float);
+float CDECL lame_get_athaa_sensitivity( const lame_global_flags* );
+
+#if DEPRECATED_OR_OBSOLETE_CODE_REMOVED
+#else
+/* OBSOLETE: predictability limit (ISO tonality formula) */
+int CDECL lame_set_cwlimit(lame_global_flags *, int);
+int CDECL lame_get_cwlimit(const lame_global_flags *);
+#endif
+
+/*
+  allow blocktypes to differ between channels?
+  default: 0 for jstereo, 1 for stereo
+*/
+int CDECL lame_set_allow_diff_short(lame_global_flags *, int);
+int CDECL lame_get_allow_diff_short(const lame_global_flags *);
+
+/* use temporal masking effect (default = 1) */
+int CDECL lame_set_useTemporal(lame_global_flags *, int);
+int CDECL lame_get_useTemporal(const lame_global_flags *);
+
+/* use temporal masking effect (default = 1) */
+int CDECL lame_set_interChRatio(lame_global_flags *, float);
+float CDECL lame_get_interChRatio(const lame_global_flags *);
+
+/* disable short blocks */
+int CDECL lame_set_no_short_blocks(lame_global_flags *, int);
+int CDECL lame_get_no_short_blocks(const lame_global_flags *);
+
+/* force short blocks */
+int CDECL lame_set_force_short_blocks(lame_global_flags *, int);
+int CDECL lame_get_force_short_blocks(const lame_global_flags *);
+
+/* Input PCM is emphased PCM (for instance from one of the rarely
+   emphased CDs), it is STRONGLY not recommended to use this, because
+   psycho does not take it into account, and last but not least many decoders
+   ignore these bits */
+int CDECL lame_set_emphasis(lame_global_flags *, int);
+int CDECL lame_get_emphasis(const lame_global_flags *);
+
+
+
+/************************************************************************/
+/* internal variables, cannot be set...                                 */
+/* provided because they may be of use to calling application           */
+/************************************************************************/
+/* version  0=MPEG-2  1=MPEG-1  (2=MPEG-2.5)     */
+int CDECL lame_get_version(const lame_global_flags *);
+
+/* encoder delay   */
+int CDECL lame_get_encoder_delay(const lame_global_flags *);
+
+/*
+  padding appended to the input to make sure decoder can fully decode
+  all input.  Note that this value can only be calculated during the
+  call to lame_encoder_flush().  Before lame_encoder_flush() has
+  been called, the value of encoder_padding = 0.
+*/
+int CDECL lame_get_encoder_padding(const lame_global_flags *);
+
+/* size of MPEG frame */
+int CDECL lame_get_framesize(const lame_global_flags *);
+
+/* number of PCM samples buffered, but not yet encoded to mp3 data. */
+int CDECL lame_get_mf_samples_to_encode( const lame_global_flags*  gfp );
+
+/*
+  size (bytes) of mp3 data buffered, but not yet encoded.
+  this is the number of bytes which would be output by a call to
+  lame_encode_flush_nogap.  NOTE: lame_encode_flush() will return
+  more bytes than this because it will encode the reamining buffered
+  PCM samples before flushing the mp3 buffers.
+*/
+int CDECL lame_get_size_mp3buffer( const lame_global_flags*  gfp );
+
+/* number of frames encoded so far */
+int CDECL lame_get_frameNum(const lame_global_flags *);
+
+/*
+  lame's estimate of the total number of frames to be encoded
+   only valid if calling program set num_samples
+*/
+int CDECL lame_get_totalframes(const lame_global_flags *);
+
+/* RadioGain value. Multiplied by 10 and rounded to the nearest. */
+int CDECL lame_get_RadioGain(const lame_global_flags *);
+
+/* AudiophileGain value. Multipled by 10 and rounded to the nearest. */
+int CDECL lame_get_AudiophileGain(const lame_global_flags *);
+
+/* the peak sample */
+float CDECL lame_get_PeakSample(const lame_global_flags *);
+
+/* Gain change required for preventing clipping. The value is correct only if
+   peak sample searching was enabled. If negative then the waveform
+   already does not clip. The value is multiplied by 10 and rounded up. */
+int CDECL lame_get_noclipGainChange(const lame_global_flags *);
+
+/* user-specified scale factor required for preventing clipping. Value is
+   correct only if peak sample searching was enabled and no user-specified
+   scaling was performed. If negative then either the waveform already does
+   not clip or the value cannot be determined */
+float CDECL lame_get_noclipScale(const lame_global_flags *);
+
+
+
+
+
+
+
+/*
+ * REQUIRED:
+ * sets more internal configuration based on data provided above.
+ * returns -1 if something failed.
+ */
+int CDECL lame_init_params(lame_global_flags *);
+
+
+/*
+ * OPTIONAL:
+ * get the version number, in a string. of the form:
+ * "3.63 (beta)" or just "3.63".
+ */
+const char*  CDECL get_lame_version       ( void );
+const char*  CDECL get_lame_short_version ( void );
+const char*  CDECL get_lame_very_short_version ( void );
+const char*  CDECL get_psy_version        ( void );
+const char*  CDECL get_lame_url           ( void );
+const char*  CDECL get_lame_os_bitness    ( void );
+
+/*
+ * OPTIONAL:
+ * get the version numbers in numerical form.
+ */
+typedef struct {
+    /* generic LAME version */
+    int major;
+    int minor;
+    int alpha;               /* 0 if not an alpha version                  */
+    int beta;                /* 0 if not a beta version                    */
+
+    /* version of the psy model */
+    int psy_major;
+    int psy_minor;
+    int psy_alpha;           /* 0 if not an alpha version                  */
+    int psy_beta;            /* 0 if not a beta version                    */
+
+    /* compile time features */
+    const char *features;    /* Don't make assumptions about the contents! */
+} lame_version_t;
+void CDECL get_lame_version_numerical(lame_version_t *);
+
+
+/*
+ * OPTIONAL:
+ * print internal lame configuration to message handler
+ */
+void CDECL lame_print_config(const lame_global_flags*  gfp);
+
+void CDECL lame_print_internals( const lame_global_flags *gfp);
+
+
+/*
+ * input pcm data, output (maybe) mp3 frames.
+ * This routine handles all buffering, resampling and filtering for you.
+ *
+ * return code     number of bytes output in mp3buf. Can be 0
+ *                 -1:  mp3buf was too small
+ *                 -2:  malloc() problem
+ *                 -3:  lame_init_params() not called
+ *                 -4:  psycho acoustic problems
+ *
+ * The required mp3buf_size can be computed from num_samples,
+ * samplerate and encoding rate, but here is a worst case estimate:
+ *
+ * mp3buf_size in bytes = 1.25*num_samples + 7200
+ *
+ * I think a tighter bound could be:  (mt, March 2000)
+ * MPEG1:
+ *    num_samples*(bitrate/8)/samplerate + 4*1152*(bitrate/8)/samplerate + 512
+ * MPEG2:
+ *    num_samples*(bitrate/8)/samplerate + 4*576*(bitrate/8)/samplerate + 256
+ *
+ * but test first if you use that!
+ *
+ * set mp3buf_size = 0 and LAME will not check if mp3buf_size is
+ * large enough.
+ *
+ * NOTE:
+ * if gfp->num_channels=2, but gfp->mode = 3 (mono), the L & R channels
+ * will be averaged into the L channel before encoding only the L channel
+ * This will overwrite the data in buffer_l[] and buffer_r[].
+ *
+*/
+int CDECL lame_encode_buffer (
+        lame_global_flags*  gfp,           /* global context handle         */
+        const short int     buffer_l [],   /* PCM data for left channel     */
+        const short int     buffer_r [],   /* PCM data for right channel    */
+        const int           nsamples,      /* number of samples per channel */
+        unsigned char*      mp3buf,        /* pointer to encoded MP3 stream */
+        const int           mp3buf_size ); /* number of valid octets in this
+                                              stream                        */
+
+/*
+ * as above, but input has L & R channel data interleaved.
+ * NOTE:
+ * num_samples = number of samples in the L (or R)
+ * channel, not the total number of samples in pcm[]
+ */
+int CDECL lame_encode_buffer_interleaved(
+        lame_global_flags*  gfp,           /* global context handlei        */
+        short int           pcm[],         /* PCM data for left and right
+                                              channel, interleaved          */
+        int                 num_samples,   /* number of samples per channel,
+                                              _not_ number of samples in
+                                              pcm[]                         */
+        unsigned char*      mp3buf,        /* pointer to encoded MP3 stream */
+        int                 mp3buf_size ); /* number of valid octets in this
+                                              stream                        */
+
+
+/* as lame_encode_buffer, but for 'float's.
+ * !! NOTE: !! data must still be scaled to be in the same range as
+ * short int, +/- 32768
+ */
+int CDECL lame_encode_buffer_float(
+        lame_global_flags*  gfp,           /* global context handle         */
+        const float         pcm_l [],      /* PCM data for left channel     */
+        const float         pcm_r [],      /* PCM data for right channel    */
+        const int           nsamples,      /* number of samples per channel */
+        unsigned char*      mp3buf,        /* pointer to encoded MP3 stream */
+        const int           mp3buf_size ); /* number of valid octets in this
+                                              stream                        */
+
+/* as lame_encode_buffer, but for 'float's.
+ * !! NOTE: !! data must be scaled to +/- 1 full scale
+ */
+int CDECL lame_encode_buffer_ieee_float(
+        lame_t          gfp,
+        const float     pcm_l [],          /* PCM data for left channel     */
+        const float     pcm_r [],          /* PCM data for right channel    */
+        const int       nsamples,
+        unsigned char * mp3buf,
+        const int       mp3buf_size);
+int CDECL lame_encode_buffer_interleaved_ieee_float(
+        lame_t          gfp,
+        const float     pcm[],             /* PCM data for left and right
+                                              channel, interleaved          */
+        const int       nsamples,
+        unsigned char * mp3buf,
+        const int       mp3buf_size);
+
+/* as lame_encode_buffer, but for 'double's.
+ * !! NOTE: !! data must be scaled to +/- 1 full scale
+ */
+int CDECL lame_encode_buffer_ieee_double(
+        lame_t          gfp,
+        const double    pcm_l [],          /* PCM data for left channel     */
+        const double    pcm_r [],          /* PCM data for right channel    */
+        const int       nsamples,
+        unsigned char * mp3buf,
+        const int       mp3buf_size);
+int CDECL lame_encode_buffer_interleaved_ieee_double(
+        lame_t          gfp,
+        const double    pcm[],             /* PCM data for left and right
+                                              channel, interleaved          */
+        const int       nsamples,
+        unsigned char * mp3buf,
+        const int       mp3buf_size);
+
+/* as lame_encode_buffer, but for long's
+ * !! NOTE: !! data must still be scaled to be in the same range as
+ * short int, +/- 32768
+ *
+ * This scaling was a mistake (doesn't allow one to exploit full
+ * precision of type 'long'.  Use lame_encode_buffer_long2() instead.
+ *
+ */
+int CDECL lame_encode_buffer_long(
+        lame_global_flags*  gfp,           /* global context handle         */
+        const long     buffer_l [],       /* PCM data for left channel     */
+        const long     buffer_r [],       /* PCM data for right channel    */
+        const int           nsamples,      /* number of samples per channel */
+        unsigned char*      mp3buf,        /* pointer to encoded MP3 stream */
+        const int           mp3buf_size ); /* number of valid octets in this
+                                              stream                        */
+
+/* Same as lame_encode_buffer_long(), but with correct scaling.
+ * !! NOTE: !! data must still be scaled to be in the same range as
+ * type 'long'.   Data should be in the range:  +/- 2^(8*size(long)-1)
+ *
+ */
+int CDECL lame_encode_buffer_long2(
+        lame_global_flags*  gfp,           /* global context handle         */
+        const long     buffer_l [],       /* PCM data for left channel     */
+        const long     buffer_r [],       /* PCM data for right channel    */
+        const int           nsamples,      /* number of samples per channel */
+        unsigned char*      mp3buf,        /* pointer to encoded MP3 stream */
+        const int           mp3buf_size ); /* number of valid octets in this
+                                              stream                        */
+
+/* as lame_encode_buffer, but for int's
+ * !! NOTE: !! input should be scaled to the maximum range of 'int'
+ * If int is 4 bytes, then the values should range from
+ * +/- 2147483648.
+ *
+ * This routine does not (and cannot, without loosing precision) use
+ * the same scaling as the rest of the lame_encode_buffer() routines.
+ *
+ */
+int CDECL lame_encode_buffer_int(
+        lame_global_flags*  gfp,           /* global context handle         */
+        const int      buffer_l [],       /* PCM data for left channel     */
+        const int      buffer_r [],       /* PCM data for right channel    */
+        const int           nsamples,      /* number of samples per channel */
+        unsigned char*      mp3buf,        /* pointer to encoded MP3 stream */
+        const int           mp3buf_size ); /* number of valid octets in this
+                                              stream                        */
+
+
+
+
+
+/*
+ * REQUIRED:
+ * lame_encode_flush will flush the intenal PCM buffers, padding with
+ * 0's to make sure the final frame is complete, and then flush
+ * the internal MP3 buffers, and thus may return a
+ * final few mp3 frames.  'mp3buf' should be at least 7200 bytes long
+ * to hold all possible emitted data.
+ *
+ * will also write id3v1 tags (if any) into the bitstream
+ *
+ * return code = number of bytes output to mp3buf. Can be 0
+ */
+int CDECL lame_encode_flush(
+        lame_global_flags *  gfp,    /* global context handle                 */
+        unsigned char*       mp3buf, /* pointer to encoded MP3 stream         */
+        int                  size);  /* number of valid octets in this stream */
+
+/*
+ * OPTIONAL:
+ * lame_encode_flush_nogap will flush the internal mp3 buffers and pad
+ * the last frame with ancillary data so it is a complete mp3 frame.
+ *
+ * 'mp3buf' should be at least 7200 bytes long
+ * to hold all possible emitted data.
+ *
+ * After a call to this routine, the outputed mp3 data is complete, but
+ * you may continue to encode new PCM samples and write future mp3 data
+ * to a different file.  The two mp3 files will play back with no gaps
+ * if they are concatenated together.
+ *
+ * This routine will NOT write id3v1 tags into the bitstream.
+ *
+ * return code = number of bytes output to mp3buf. Can be 0
+ */
+int CDECL lame_encode_flush_nogap(
+        lame_global_flags *  gfp,    /* global context handle                 */
+        unsigned char*       mp3buf, /* pointer to encoded MP3 stream         */
+        int                  size);  /* number of valid octets in this stream */
+
+/*
+ * OPTIONAL:
+ * Normally, this is called by lame_init_params().  It writes id3v2 and
+ * Xing headers into the front of the bitstream, and sets frame counters
+ * and bitrate histogram data to 0.  You can also call this after
+ * lame_encode_flush_nogap().
+ */
+int CDECL lame_init_bitstream(
+        lame_global_flags *  gfp);    /* global context handle                 */
+
+
+
+/*
+ * OPTIONAL:    some simple statistics
+ * a bitrate histogram to visualize the distribution of used frame sizes
+ * a stereo mode histogram to visualize the distribution of used stereo
+ *   modes, useful in joint-stereo mode only
+ *   0: LR    left-right encoded
+ *   1: LR-I  left-right and intensity encoded (currently not supported)
+ *   2: MS    mid-side encoded
+ *   3: MS-I  mid-side and intensity encoded (currently not supported)
+ *
+ * attention: don't call them after lame_encode_finish
+ * suggested: lame_encode_flush -> lame_*_hist -> lame_close
+ */
+
+void CDECL lame_bitrate_hist(
+        const lame_global_flags * gfp,
+        int bitrate_count[14] );
+void CDECL lame_bitrate_kbps(
+        const lame_global_flags * gfp,
+        int bitrate_kbps [14] );
+void CDECL lame_stereo_mode_hist(
+        const lame_global_flags * gfp,
+        int stereo_mode_count[4] );
+
+void CDECL lame_bitrate_stereo_mode_hist (
+        const lame_global_flags * gfp,
+        int bitrate_stmode_count[14][4] );
+
+void CDECL lame_block_type_hist (
+        const lame_global_flags * gfp,
+        int btype_count[6] );
+
+void CDECL lame_bitrate_block_type_hist (
+        const lame_global_flags * gfp,
+        int bitrate_btype_count[14][6] );
+
+#if (DEPRECATED_OR_OBSOLETE_CODE_REMOVED && 0)
+#else
+/*
+ * OPTIONAL:
+ * lame_mp3_tags_fid will rewrite a Xing VBR tag to the mp3 file with file
+ * pointer fid.  These calls perform forward and backwards seeks, so make
+ * sure fid is a real file.  Make sure lame_encode_flush has been called,
+ * and all mp3 data has been written to the file before calling this
+ * function.
+ * NOTE:
+ * if VBR  tags are turned off by the user, or turned off by LAME because
+ * the output is not a regular file, this call does nothing
+ * NOTE:
+ * LAME wants to read from the file to skip an optional ID3v2 tag, so
+ * make sure you opened the file for writing and reading.
+ * NOTE:
+ * You can call lame_get_lametag_frame instead, if you want to insert
+ * the lametag yourself.
+*/
+void CDECL lame_mp3_tags_fid(lame_global_flags *, FILE* fid);
+#endif
+
+/*
+ * OPTIONAL:
+ * lame_get_lametag_frame copies the final LAME-tag into 'buffer'.
+ * The function returns the number of bytes copied into buffer, or
+ * the required buffer size, if the provided buffer is too small.
+ * Function failed, if the return value is larger than 'size'!
+ * Make sure lame_encode flush has been called before calling this function.
+ * NOTE:
+ * if VBR  tags are turned off by the user, or turned off by LAME,
+ * this call does nothing and returns 0.
+ * NOTE:
+ * LAME inserted an empty frame in the beginning of mp3 audio data,
+ * which you have to replace by the final LAME-tag frame after encoding.
+ * In case there is no ID3v2 tag, usually this frame will be the very first
+ * data in your mp3 file. If you put some other leading data into your
+ * file, you'll have to do some bookkeeping about where to write this buffer.
+ */
+size_t CDECL lame_get_lametag_frame(
+        const lame_global_flags *, unsigned char* buffer, size_t size);
+
+/*
+ * REQUIRED:
+ * final call to free all remaining buffers
+ */
+int  CDECL lame_close (lame_global_flags *);
+
+#if DEPRECATED_OR_OBSOLETE_CODE_REMOVED
+#else
+/*
+ * OBSOLETE:
+ * lame_encode_finish combines lame_encode_flush() and lame_close() in
+ * one call.  However, once this call is made, the statistics routines
+ * will no longer work because the data will have been cleared, and
+ * lame_mp3_tags_fid() cannot be called to add data to the VBR header
+ */
+int CDECL lame_encode_finish(
+        lame_global_flags*  gfp,
+        unsigned char*      mp3buf,
+        int                 size );
+#endif
+
+
+
+
+
+
+/*********************************************************************
+ *
+ * decoding
+ *
+ * a simple interface to mpglib, part of mpg123, is also included if
+ * libmp3lame is compiled with HAVE_MPGLIB
+ *
+ *********************************************************************/
+
+struct hip_global_struct;
+typedef struct hip_global_struct hip_global_flags;
+typedef hip_global_flags *hip_t;
+
+
+typedef struct {
+  int header_parsed;   /* 1 if header was parsed and following data was
+                          computed                                       */
+  int stereo;          /* number of channels                             */
+  int samplerate;      /* sample rate                                    */
+  int bitrate;         /* bitrate                                        */
+  int mode;            /* mp3 frame type                                 */
+  int mode_ext;        /* mp3 frame type                                 */
+  int framesize;       /* number of samples per mp3 frame                */
+
+  /* this data is only computed if mpglib detects a Xing VBR header */
+  unsigned long nsamp; /* number of samples in mp3 file.                 */
+  int totalframes;     /* total number of frames in mp3 file             */
+
+  /* this data is not currently computed by the mpglib routines */
+  int framenum;        /* frames decoded counter                         */
+} mp3data_struct;
+
+/* required call to initialize decoder */
+hip_t CDECL hip_decode_init(void);
+
+/* cleanup call to exit decoder  */
+int CDECL hip_decode_exit(hip_t gfp);
+
+/* HIP reporting functions */
+void CDECL hip_set_errorf(hip_t gfp, lame_report_function f);
+void CDECL hip_set_debugf(hip_t gfp, lame_report_function f);
+void CDECL hip_set_msgf  (hip_t gfp, lame_report_function f);
+
+/*********************************************************************
+ * input 1 mp3 frame, output (maybe) pcm data.
+ *
+ *  nout = hip_decode(hip, mp3buf,len,pcm_l,pcm_r);
+ *
+ * input:
+ *    len          :  number of bytes of mp3 data in mp3buf
+ *    mp3buf[len]  :  mp3 data to be decoded
+ *
+ * output:
+ *    nout:  -1    : decoding error
+ *            0    : need more data before we can complete the decode
+ *           >0    : returned 'nout' samples worth of data in pcm_l,pcm_r
+ *    pcm_l[nout]  : left channel data
+ *    pcm_r[nout]  : right channel data
+ *
+ *********************************************************************/
+int CDECL hip_decode( hip_t           gfp
+                    , unsigned char * mp3buf
+                    , size_t          len
+                    , short           pcm_l[]
+                    , short           pcm_r[]
+                    );
+
+/* same as hip_decode, and also returns mp3 header data */
+int CDECL hip_decode_headers( hip_t           gfp
+                            , unsigned char*  mp3buf
+                            , size_t          len
+                            , short           pcm_l[]
+                            , short           pcm_r[]
+                            , mp3data_struct* mp3data
+                            );
+
+/* same as hip_decode, but returns at most one frame */
+int CDECL hip_decode1( hip_t          gfp
+                     , unsigned char* mp3buf
+                     , size_t         len
+                     , short          pcm_l[]
+                     , short          pcm_r[]
+                     );
+
+/* same as hip_decode1, but returns at most one frame and mp3 header data */
+int CDECL hip_decode1_headers( hip_t           gfp
+                             , unsigned char*  mp3buf
+                             , size_t          len
+                             , short           pcm_l[]
+                             , short           pcm_r[]
+                             , mp3data_struct* mp3data
+                             );
+
+/* same as hip_decode1_headers, but also returns enc_delay and enc_padding
+   from VBR Info tag, (-1 if no info tag was found) */
+int CDECL hip_decode1_headersB( hip_t gfp
+                              , unsigned char*   mp3buf
+                              , size_t           len
+                              , short            pcm_l[]
+                              , short            pcm_r[]
+                              , mp3data_struct*  mp3data
+                              , int             *enc_delay
+                              , int             *enc_padding
+                              );
+
+
+
+/* OBSOLETE:
+ * lame_decode... functions are there to keep old code working
+ * but it is strongly recommended to replace calls by hip_decode...
+ * function calls, see above.
+ */
+#if DEPRECATED_OR_OBSOLETE_CODE_REMOVED
+#else
+int CDECL lame_decode_init(void);
+int CDECL lame_decode(
+        unsigned char *  mp3buf,
+        int              len,
+        short            pcm_l[],
+        short            pcm_r[] );
+int CDECL lame_decode_headers(
+        unsigned char*   mp3buf,
+        int              len,
+        short            pcm_l[],
+        short            pcm_r[],
+        mp3data_struct*  mp3data );
+int CDECL lame_decode1(
+        unsigned char*  mp3buf,
+        int             len,
+        short           pcm_l[],
+        short           pcm_r[] );
+int CDECL lame_decode1_headers(
+        unsigned char*   mp3buf,
+        int              len,
+        short            pcm_l[],
+        short            pcm_r[],
+        mp3data_struct*  mp3data );
+int CDECL lame_decode1_headersB(
+        unsigned char*   mp3buf,
+        int              len,
+        short            pcm_l[],
+        short            pcm_r[],
+        mp3data_struct*  mp3data,
+        int              *enc_delay,
+        int              *enc_padding );
+int CDECL lame_decode_exit(void);
+
+#endif /* obsolete lame_decode API calls */
+
+
+/*********************************************************************
+ *
+ * id3tag stuff
+ *
+ *********************************************************************/
+
+/*
+ * id3tag.h -- Interface to write ID3 version 1 and 2 tags.
+ *
+ * Copyright (C) 2000 Don Melton.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/* utility to obtain alphabetically sorted list of genre names with numbers */
+void CDECL id3tag_genre_list(
+        void (*handler)(int, const char *, void *),
+        void*  cookie);
+
+void CDECL id3tag_init     (lame_t gfp);
+
+/* force addition of version 2 tag */
+void CDECL id3tag_add_v2   (lame_t gfp);
+
+/* add only a version 1 tag */
+void CDECL id3tag_v1_only  (lame_t gfp);
+
+/* add only a version 2 tag */
+void CDECL id3tag_v2_only  (lame_t gfp);
+
+/* pad version 1 tag with spaces instead of nulls */
+void CDECL id3tag_space_v1 (lame_t gfp);
+
+/* pad version 2 tag with extra 128 bytes */
+void CDECL id3tag_pad_v2   (lame_t gfp);
+
+/* pad version 2 tag with extra n bytes */
+void CDECL id3tag_set_pad  (lame_t gfp, size_t n);
+
+void CDECL id3tag_set_title(lame_t gfp, const char* title);
+void CDECL id3tag_set_artist(lame_t gfp, const char* artist);
+void CDECL id3tag_set_album(lame_t gfp, const char* album);
+void CDECL id3tag_set_year(lame_t gfp, const char* year);
+void CDECL id3tag_set_comment(lame_t gfp, const char* comment);
+            
+/* return -1 result if track number is out of ID3v1 range
+                    and ignored for ID3v1 */
+int CDECL id3tag_set_track(lame_t gfp, const char* track);
+
+/* return non-zero result if genre name or number is invalid
+  result 0: OK
+  result -1: genre number out of range
+  result -2: no valid ID3v1 genre name, mapped to ID3v1 'Other'
+             but taken as-is for ID3v2 genre tag */
+int CDECL id3tag_set_genre(lame_t gfp, const char* genre);
+
+/* return non-zero result if field name is invalid */
+int CDECL id3tag_set_fieldvalue(lame_t gfp, const char* fieldvalue);
+
+/* return non-zero result if image type is invalid */
+int CDECL id3tag_set_albumart(lame_t gfp, const char* image, size_t size);
+
+/* lame_get_id3v1_tag copies ID3v1 tag into buffer.
+ * Function returns number of bytes copied into buffer, or number
+ * of bytes rquired if buffer 'size' is too small.
+ * Function fails, if returned value is larger than 'size'.
+ * NOTE:
+ * This functions does nothing, if user/LAME disabled ID3v1 tag.
+ */
+size_t CDECL lame_get_id3v1_tag(lame_t gfp, unsigned char* buffer, size_t size);
+
+/* lame_get_id3v2_tag copies ID3v2 tag into buffer.
+ * Function returns number of bytes copied into buffer, or number
+ * of bytes rquired if buffer 'size' is too small.
+ * Function fails, if returned value is larger than 'size'.
+ * NOTE:
+ * This functions does nothing, if user/LAME disabled ID3v2 tag.
+ */
+size_t CDECL lame_get_id3v2_tag(lame_t gfp, unsigned char* buffer, size_t size);
+
+/* normaly lame_init_param writes ID3v2 tags into the audio stream
+ * Call lame_set_write_id3tag_automatic(gfp, 0) before lame_init_param
+ * to turn off this behaviour and get ID3v2 tag with above function
+ * write it yourself into your file.
+ */
+void CDECL lame_set_write_id3tag_automatic(lame_global_flags * gfp, int);
+int CDECL lame_get_write_id3tag_automatic(lame_global_flags const* gfp);
+
+/* experimental */
+int CDECL id3tag_set_textinfo_latin1(lame_t gfp, char const *id, char const *text);
+
+/* experimental */
+int CDECL id3tag_set_comment_latin1(lame_t gfp, char const *lang, char const *desc, char const *text);
+
+#if DEPRECATED_OR_OBSOLETE_CODE_REMOVED
+#else
+/* experimental */
+int CDECL id3tag_set_textinfo_ucs2(lame_t gfp, char const *id, unsigned short const *text);
+
+/* experimental */
+int CDECL id3tag_set_comment_ucs2(lame_t gfp, char const *lang,
+                                  unsigned short const *desc, unsigned short const *text);
+
+/* experimental */
+int CDECL id3tag_set_fieldvalue_ucs2(lame_t gfp, const unsigned short *fieldvalue);
+#endif
+
+/* experimental */
+int CDECL id3tag_set_fieldvalue_utf16(lame_t gfp, const unsigned short *fieldvalue);
+
+/* experimental */
+int CDECL id3tag_set_textinfo_utf16(lame_t gfp, char const *id, unsigned short const *text);
+
+/* experimental */
+int CDECL id3tag_set_comment_utf16(lame_t gfp, char const *lang, unsigned short const *desc, unsigned short const *text);
+
+
+/***********************************************************************
+*
+*  list of valid bitrates [kbps] & sample frequencies [Hz].
+*  first index: 0: MPEG-2   values  (sample frequencies 16...24 kHz)
+*               1: MPEG-1   values  (sample frequencies 32...48 kHz)
+*               2: MPEG-2.5 values  (sample frequencies  8...12 kHz)
+***********************************************************************/
+
+extern const int     bitrate_table    [3][16];
+extern const int     samplerate_table [3][ 4];
+
+/* access functions for use in DLL, global vars are not exported */
+int CDECL lame_get_bitrate(int mpeg_version, int table_index);
+int CDECL lame_get_samplerate(int mpeg_version, int table_index);
+
+
+/* maximum size of albumart image (128KB), which affects LAME_MAXMP3BUFFER
+   as well since lame_encode_buffer() also returns ID3v2 tag data */
+#define LAME_MAXALBUMART    (128 * 1024)
+
+/* maximum size of mp3buffer needed if you encode at most 1152 samples for
+   each call to lame_encode_buffer.  see lame_encode_buffer() below  
+   (LAME_MAXMP3BUFFER is now obsolete)  */
+#define LAME_MAXMP3BUFFER   (16384 + LAME_MAXALBUMART)
+
+
+typedef enum {
+    LAME_OKAY             =   0,
+    LAME_NOERROR          =   0,
+    LAME_GENERICERROR     =  -1,
+    LAME_NOMEM            = -10,
+    LAME_BADBITRATE       = -11,
+    LAME_BADSAMPFREQ      = -12,
+    LAME_INTERNALERROR    = -13,
+
+    FRONTEND_READERROR    = -80,
+    FRONTEND_WRITEERROR   = -81,
+    FRONTEND_FILETOOLARGE = -82
+
+} lame_errorcodes_t;
+
+#if defined(__cplusplus)
+}
+#endif
+#endif /* LAME_LAME_H */
+

BIN
o2ios/O2Platform/Framework/lame/libmp3lame.a


BIN
o2ios/O2Platform/Framework/lame/opencore-amr/lib/libopencore-amrnb.a


BIN
o2ios/O2Platform/Framework/lame/opencore-amr/lib/libopencore-amrwb.a


+ 34 - 0
o2ios/O2Platform/Framework/lame/opencore-amr/opencore-amrnb/interf_dec.h

@@ -0,0 +1,34 @@
+/* ------------------------------------------------------------------
+ * Copyright (C) 2009 Martin Storsjo
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ * -------------------------------------------------------------------
+ */
+
+#ifndef OPENCORE_AMRNB_INTERF_DEC_H
+#define OPENCORE_AMRNB_INTERF_DEC_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void* Decoder_Interface_init(void);
+void Decoder_Interface_exit(void* state);
+void Decoder_Interface_Decode(void* state, const unsigned char* in, short* out, int bfi);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif

+ 50 - 0
o2ios/O2Platform/Framework/lame/opencore-amr/opencore-amrnb/interf_enc.h

@@ -0,0 +1,50 @@
+/* ------------------------------------------------------------------
+ * Copyright (C) 2009 Martin Storsjo
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ * -------------------------------------------------------------------
+ */
+
+#ifndef OPENCORE_AMRNB_INTERF_ENC_H
+#define OPENCORE_AMRNB_INTERF_ENC_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef AMRNB_WRAPPER_INTERNAL
+/* Copied from enc/src/gsmamr_enc.h */
+enum Mode {
+	MR475 = 0,/* 4.75 kbps */
+	MR515,    /* 5.15 kbps */
+	MR59,     /* 5.90 kbps */
+	MR67,     /* 6.70 kbps */
+	MR74,     /* 7.40 kbps */
+	MR795,    /* 7.95 kbps */
+	MR102,    /* 10.2 kbps */
+	MR122,    /* 12.2 kbps */
+	MRDTX,    /* DTX       */
+	N_MODES   /* Not Used  */
+};
+#endif
+
+void* Encoder_Interface_init(int dtx);
+void Encoder_Interface_exit(void* state);
+int Encoder_Interface_Encode(void* state, enum Mode mode, const short* speech, unsigned char* out, int forceSpeech);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif

+ 36 - 0
o2ios/O2Platform/Framework/lame/opencore-amr/opencore-amrwb/dec_if.h

@@ -0,0 +1,36 @@
+/* ------------------------------------------------------------------
+ * Copyright (C) 2009 Martin Storsjo
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ * -------------------------------------------------------------------
+ */
+
+#ifndef OPENCORE_AMRWB_DEC_IF_H
+#define OPENCORE_AMRWB_DEC_IF_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define _good_frame 0
+
+void* D_IF_init(void);
+void D_IF_decode(void* state, const unsigned char* bits, short* synth, int bfi);
+void D_IF_exit(void* state);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif

+ 33 - 0
o2ios/O2Platform/Framework/lame/opencore-amr/opencore-amrwb/if_rom.h

@@ -0,0 +1,33 @@
+/* ------------------------------------------------------------------
+ * Copyright (C) 2009 Martin Storsjo
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ * -------------------------------------------------------------------
+ */
+
+#ifndef OPENCORE_AMRWB_IF_ROM_H
+#define OPENCORE_AMRWB_IF_ROM_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdint.h>
+typedef int16_t Word16;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif

BIN
o2ios/O2Platform/Framework/lame/vo-amrwbenc/lib/libvo-amrwbenc.a


+ 34 - 0
o2ios/O2Platform/Framework/lame/vo-amrwbenc/vo-amrwbenc/enc_if.h

@@ -0,0 +1,34 @@
+/* ------------------------------------------------------------------
+ * Copyright (C) 2010 Martin Storsjo
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ * -------------------------------------------------------------------
+ */
+
+#ifndef VO_AMRWBENC_ENC_IF_H
+#define VO_AMRWBENC_ENC_IF_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void* E_IF_init(void);
+int E_IF_encode(void* state, int mode, const short* speech, unsigned char* out, int dtx);
+void E_IF_exit(void* state);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif

+ 78 - 0
o2ios/O2Platform/Manager/AudioPlayerManager.swift

@@ -0,0 +1,78 @@
+//
+//  AudioPlayerManager.swift
+//  O2Platform
+//
+//  Created by FancyLou on 2020/6/17.
+//  Copyright © 2020 zoneland. All rights reserved.
+//
+
+import AVFoundation
+
+protocol AudioPlayerManagerDelegate {
+    func didAudioPlayerBeginPlay(_ AudioPlayer: AVAudioPlayer)
+    func didAudioPlayerStopPlay(_ AudioPlayer: AVAudioPlayer)
+    func didAudioPlayerPausePlay(_ AudioPlayer: AVAudioPlayer)
+}
+
+class AudioPlayerManager: NSObject {
+    static let  shared: AudioPlayerManager = {
+        return AudioPlayerManager()
+    }()
+    
+    private override init() {super.init()}
+    
+    var delegate: AudioPlayerManagerDelegate?
+    var player: AVAudioPlayer!
+    
+    
+    
+    func managerAudioWithData(_ data:Data, toplay:Bool) {
+        if toplay {
+            playAudioWithData(data)
+        } else {
+            pausePlayingAudio()
+        }
+    }
+    
+    func playAudioWithData(_ voiceData:Data) {
+        do {
+            try AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .default, options: .defaultToSpeaker)
+        } catch let error as NSError {
+            print("set category fail \(error)")
+        }
+        
+        if player != nil {
+            player.stop()
+            player = nil
+        }
+        
+        do {
+            let pl: AVAudioPlayer = try AVAudioPlayer(data: voiceData)
+            pl.delegate = self
+            pl.play()
+            player = pl
+        } catch let error as NSError {
+            print("alloc AVAudioPlayer with voice data fail with error \(error)")
+        }
+        
+        UIDevice.current.isProximityMonitoringEnabled = true
+    }
+    
+    func pausePlayingAudio() {
+        player?.pause()
+    }
+    
+    func stopAudio() {
+        if player != nil && player.isPlaying {
+            player.stop()
+        }
+        UIDevice.current.isProximityMonitoringEnabled = false
+        delegate?.didAudioPlayerStopPlay(player)
+    }
+}
+
+extension AudioPlayerManager: AVAudioPlayerDelegate {
+    func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
+        stopAudio()
+    }
+}

+ 235 - 0
o2ios/O2Platform/Manager/O2RecordVoiceManager.swift

@@ -0,0 +1,235 @@
+//
+//  O2RecordVoiceManager.swift
+//  O2Platform
+//
+//  Created by FancyLou on 2020/6/17.
+//  Copyright © 2020 zoneland. All rights reserved.
+//
+
+import UIKit
+import AVFoundation
+
+
+fileprivate func < <T : Comparable>(lhs: T?, rhs: T?) -> Bool {
+    switch (lhs, rhs) {
+    case let (l?, r?):
+        return l < r
+    case (nil, _?):
+        return true
+    default:
+        return false
+    }
+}
+
+fileprivate func > <T : Comparable>(lhs: T?, rhs: T?) -> Bool {
+    switch (lhs, rhs) {
+    case let (l?, r?):
+        return l > r
+    default:
+        return rhs < lhs
+    }
+}
+
+typealias O2RecordCompletionCallBack = () -> Void
+
+protocol O2RecordVoiceDelegate {
+    func beyondLimit(_ time: TimeInterval)
+}
+
+class O2RecordVoiceManager: NSObject {
+    private let maxRecordTime = 60.0
+
+    private var startRecordCompleted: O2RecordCompletionCallBack?
+    private var recorder: AVAudioRecorder?
+    private var recordProgress: Float?
+    private var theTimer: Timer?
+    private var currentTimeInterval: TimeInterval?
+    
+    var recordPath: String?
+    var recordDuration: String?
+    var stopRecordCompletion: O2RecordCompletionCallBack?
+    var cancelledDeleteCompletion: O2RecordCompletionCallBack?
+    var delegate: O2RecordVoiceDelegate?
+    
+    override init() {
+        super.init()
+    }
+    
+    deinit {
+        stopRecord()
+        recordPath = nil
+    }
+    
+    @objc private func updateMeters() {
+        if recorder == nil {
+            return
+        }
+        currentTimeInterval = recorder?.currentTime
+        
+        recordProgress = recorder?.peakPower(forChannel: 0)
+        if currentTimeInterval > maxRecordTime {
+            stopRecord()
+            delegate?.beyondLimit(currentTimeInterval!)
+            if stopRecordCompletion != nil {
+                DispatchQueue.main.async(execute: stopRecordCompletion!)
+                recorder?.updateMeters()
+            }
+        }
+    }
+    
+    private func getVoiceDuration(_ recordPath:String) {
+        do {
+            let player:AVAudioPlayer = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: recordPath))
+            player.play()
+            let duration = player.duration
+            self.recordDuration = "\(Int(duration))"
+        } catch let error as NSError {
+            print("get AVAudioPlayer is fail \(error)")
+            self.recordDuration = "0"
+        }
+    }
+    
+    private func resetTimer() {
+        if theTimer == nil {
+            return
+        } else {
+            theTimer!.invalidate()
+            theTimer = nil
+        }
+    }
+    
+    private func cancelRecording() {
+        if recorder == nil {
+            return
+        }
+        if recorder?.isRecording != false {
+            recorder?.stop()
+        }
+        recorder = nil
+    }
+    
+    private func stopRecord() {
+        cancelRecording()
+        resetTimer()
+    }
+    
+    func startRecordingWithPath(_ path:String, startRecordCompleted:@escaping O2RecordCompletionCallBack) {
+        print("Action - startRecordingWithPath:")
+        self.startRecordCompleted = startRecordCompleted
+        self.recordPath = path
+        
+        let audioSession:AVAudioSession = AVAudioSession.sharedInstance()
+        do {
+            try audioSession.setCategory(AVAudioSession.Category.playAndRecord, mode: .default, options: .defaultToSpeaker)
+        } catch let error as NSError {
+            print("could not set session category")
+            print(error.localizedDescription)
+        }
+        
+        do {
+            try audioSession.setActive(true)
+        } catch let error as NSError {
+            print("could not set session active")
+            print(error.localizedDescription)
+        }
+        
+//        let recordSettings:[String : AnyObject] = [
+//            AVFormatIDKey: NSNumber(value: kAudioFormatAppleIMA4 as UInt32),
+//            AVNumberOfChannelsKey: 1 as AnyObject,
+//            AVSampleRateKey : 16000.0 as AnyObject
+//        ]
+        let recordSetting: [String: Any] = [
+            AVSampleRateKey: NSNumber(value: 16000),//采样率
+            AVEncoderBitRateKey:NSNumber(value: 16000),
+            AVFormatIDKey: NSNumber(value: kAudioFormatLinearPCM),//音频格式
+            AVNumberOfChannelsKey: NSNumber(value: 1),//通道数
+            AVLinearPCMBitDepthKey:NSNumber(value: 16),
+            AVEncoderAudioQualityKey: NSNumber(value: AVAudioQuality.high.rawValue)//录音质量
+        ]
+        
+        do {
+            self.recorder = try AVAudioRecorder(url: URL(fileURLWithPath: self.recordPath!), settings: recordSetting)
+            self.recorder!.delegate = self
+            self.recorder!.prepareToRecord()
+            self.recorder?.record(forDuration: 160.0)
+        } catch let error as NSError {
+            recorder = nil
+            print(error.localizedDescription)
+        }
+        
+        if ((self.recorder?.record()) != false) {
+            self.resetTimer()
+            self.theTimer = Timer.scheduledTimer(timeInterval: 0.05, target: self, selector: #selector(updateMeters), userInfo: nil, repeats: true)
+        } else {
+            print("fail record")
+        }
+        
+        if self.startRecordCompleted != nil {
+            DispatchQueue.main.async(execute: self.startRecordCompleted!)
+        }
+    }
+    
+    func finishRecordingCompletion() {
+        stopRecord()
+        getVoiceDuration(recordPath!)
+        
+        if stopRecordCompletion != nil {
+            DispatchQueue.main.async(execute: stopRecordCompletion!)
+        }
+    }
+    
+    func cancelledDeleteWithCompletion() {
+        stopRecord()
+        if recordPath != nil {
+            let fileManager:FileManager = FileManager.default
+            if fileManager.fileExists(atPath: recordPath!) == true {
+                do {
+                    try fileManager.removeItem(atPath: recordPath!)
+                } catch let error as NSError {
+                    print("can no to remove the voice file \(error.localizedDescription)")
+                }
+            } else {
+                if cancelledDeleteCompletion != nil {
+                    DispatchQueue.main.async(execute: cancelledDeleteCompletion!)
+                }
+            }
+            
+        }
+    }
+    // test player
+    func playVoice(_ recordPath:String) {
+        do {
+            print("\(recordPath)")
+            let player:AVAudioPlayer = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: recordPath))
+            player.volume = 1
+            player.delegate = self
+            player.numberOfLoops = -1
+            player.prepareToPlay()
+            player.play()
+            
+        } catch let error as NSError {
+            print("get AVAudioPlayer is fail \(error)")
+        }
+    }
+    //caf文件转mp3
+    func convertCafToMp3(cafPath: String, mp3Path: String){
+        ConvertMp3().audioPCMtoMP3(cafPath, mp3File: mp3Path)
+        print("caf源文件:\(cafPath)")
+        print("mp3文件:\(mp3Path)")
+    }
+    
+    
+}
+
+extension O2RecordVoiceManager: AVAudioPlayerDelegate, AVAudioRecorderDelegate {
+    func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
+        print("finished playing \(flag)")
+        
+    }
+
+    func audioPlayerDecodeErrorDidOccur(_ player: AVAudioPlayer, error: Error?) {
+        if let e = error {
+            print("\(e.localizedDescription)")
+        }
+    }
+}

+ 4 - 0
o2ios/O2Platform/O2Platform-Bridging-Header.h

@@ -44,3 +44,7 @@
 // flutter
 #import "GeneratedPluginRegistrant.h"
 
+//lame mp3转码
+#import "ConvertMp3.h"
+#import "AmrCodec.h"
+

+ 2 - 2
o2ios/O2Platform/UI/o2JsApi/O2BaseJsMessageHandler.swift

@@ -266,8 +266,8 @@ class O2BaseJsMessageHandler: O2WKScriptMessageHandlerImplement {
             return
         }
         data.scale = 800
-        let chooseImage = FileBSImagePickerViewController()
-        self.viewController.bs_presentImagePickerController(chooseImage, animated: true, select: nil, deselect: nil, cancel: nil, finish: { (arr) in
+        let vc = FileBSImagePickerViewController().bsImagePicker()
+        self.viewController.presentImagePicker(vc, select: nil, deselect: nil, cancel: nil, finish: { (arr) in
             let count = arr.count
             DDLogDebug("选择了照片数量:\(count)")
             if count > 0 {

+ 8 - 0
o2ios/O2Platform/config/O2URLContext.swift

@@ -261,6 +261,14 @@ struct BBSContext {
     
 }
 
+struct CommunicateContext {
+    static let communicateContextKey = "x_message_assemble_communicate"
+    static let imUploadFileQuery = "jaxrs/im/msg/upload/##conversationId##/type/##type##"
+    //文件打开地址
+    static let imDownloadFileQuery = "jaxrs/im/msg/download/##id##"
+    //图片根据尺寸压缩 0<width<5000 0<height<5000
+    static let imDownloadImageWithSizeQuery = "jaxrs/im/msg/download/##id##/image/width/##width##/height/##height##"
+}
 
 struct DesktopContext {
     static let DesktopContextKey = "x_desktop"

+ 1 - 1
o2ios/Podfile

@@ -57,7 +57,7 @@ target 'O2Platform' do
     
     pod 'SDWebImage', '~>4.0'
     
-    pod 'BSImagePicker'
+    pod 'BSImagePicker', '~> 3.1.0'
     pod 'Eureka', '~> 5.2.1'
     pod 'SwiftyTimer'
 

+ 4 - 12
o2ios/Podfile.lock

@@ -10,11 +10,7 @@ PODS:
   - BaiduMapKit (5.2.0)
   - BetterSegmentedControl (1.2.1)
   - BMKLocationKit (1.8.5)
-  - BSGridCollectionViewLayout (1.2.2)
-  - BSImagePicker (2.9.0):
-    - BSGridCollectionViewLayout (= 1.2.2)
-    - BSImageView (= 1.0.2)
-  - BSImageView (1.0.2)
+  - BSImagePicker (3.1.1)
   - Bugly (2.5.0)
   - Charts (3.2.1):
     - Charts/Core (= 3.2.1)
@@ -87,7 +83,7 @@ DEPENDENCIES:
   - BaiduMapKit
   - BetterSegmentedControl (~> 1.2)
   - BMKLocationKit
-  - BSImagePicker
+  - BSImagePicker (~> 3.1.0)
   - Bugly
   - Charts
   - Chrysan
@@ -131,9 +127,7 @@ SPEC REPOS:
     - BaiduMapKit
     - BetterSegmentedControl
     - BMKLocationKit
-    - BSGridCollectionViewLayout
     - BSImagePicker
-    - BSImageView
     - Bugly
     - Charts
     - Chrysan
@@ -188,9 +182,7 @@ SPEC CHECKSUMS:
   BaiduMapKit: 075465cf7134d0ebf81796b93a59d504c350d9a4
   BetterSegmentedControl: b27bddbdff29b6cae988678d6cd106858986cd03
   BMKLocationKit: fd3d6b4a9a3a1a2763819f56b780bda5fc5b8726
-  BSGridCollectionViewLayout: 3e6f23b3a2b4f0ca9afae09ca01389d1057f7b32
-  BSImagePicker: 553707de1adaad42eac252e0b84d20dbdba83fe9
-  BSImageView: ab22ff8cae61f1ae17a3c9efeeb216af3cf84a09
+  BSImagePicker: 86f28e0f63b78d35fec7c6f0174b7af9e6f1431e
   Bugly: 3ca9f255c01025582df26f9222893b383c7e4b4e
   Charts: f122fb70b19847fa5817d018b77d6c5a2296ab25
   Chrysan: 6bf6682adb2db76f7f5c103930a8c6670f54eebe
@@ -228,6 +220,6 @@ SPEC CHECKSUMS:
   YHPhotoKit: e88368af1fca0a110d41b88417088f7e49876981
   YHPopupView: 96d9c05c79a2a17cf69c3def09e5305cfae9e8e8
 
-PODFILE CHECKSUM: c9dd30b0987ffddb95ec8f9c0af2a4134a03ae69
+PODFILE CHECKSUM: 7994fed5103facb0bfff5a065e5612ebe4b411a2
 
 COCOAPODS: 1.8.4