TUIVoiceMessageCellData.m 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. //
  2. // TUIVoiceMessageCellData.m
  3. // TXIMSDK_TUIKit_iOS
  4. //
  5. // Created by annidyfeng on 2019/5/21.
  6. // Copyright © 2023 Tencent. All rights reserved.
  7. //
  8. #import "TUIVoiceMessageCellData.h"
  9. #import <TIMCommon/TIMDefine.h>
  10. #import <TUICore/TUIThemeManager.h>
  11. @import AVFoundation;
  12. @interface TUIVoiceMessageCellData () <AVAudioPlayerDelegate>
  13. @property AVAudioPlayer *audioPlayer;
  14. @property NSString *wavPath;
  15. @property(nonatomic, strong) NSTimer *timer;
  16. @end
  17. @implementation TUIVoiceMessageCellData
  18. + (TUIMessageCellData *)getCellData:(V2TIMMessage *)message {
  19. V2TIMSoundElem *elem = message.soundElem;
  20. TUIVoiceMessageCellData *soundData = [[TUIVoiceMessageCellData alloc] initWithDirection:(message.isSelf ? MsgDirectionOutgoing : MsgDirectionIncoming)];
  21. soundData.duration = elem.duration;
  22. soundData.length = elem.dataSize;
  23. soundData.uuid = elem.uuid;
  24. soundData.reuseId = TVoiceMessageCell_ReuseId;
  25. soundData.path = elem.path;
  26. return soundData;
  27. }
  28. + (NSString *)getDisplayString:(V2TIMMessage *)message {
  29. return TIMCommonLocalizableString(TUIKitMessageTypeVoice); // @"[Voice]";
  30. }
  31. - (Class)getReplyQuoteViewDataClass {
  32. return NSClassFromString(@"TUIVoiceReplyQuoteViewData");
  33. }
  34. - (Class)getReplyQuoteViewClass {
  35. return NSClassFromString(@"TUIVoiceReplyQuoteView");
  36. }
  37. - (instancetype)initWithDirection:(TMsgDirection)direction {
  38. self = [super initWithDirection:direction];
  39. if (self) {
  40. if (direction == MsgDirectionIncoming) {
  41. self.cellLayout = [TUIMessageCellLayout incommingVoiceMessageLayout];
  42. _voiceImage = TUIChatDynamicImage(@"chat_voice_message_receiver_voice_normal_img",
  43. [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath(@"message_voice_receiver_normal")]);
  44. _voiceImage = [_voiceImage rtl_imageFlippedForRightToLeftLayoutDirection];
  45. _voiceAnimationImages = [NSArray arrayWithObjects:[self.class formatImageByName:@"message_voice_receiver_playing_1"],
  46. [self.class formatImageByName:@"message_voice_receiver_playing_2"],
  47. [self.class formatImageByName:@"message_voice_receiver_playing_3"], nil];
  48. _voiceTop = [[self class] incommingVoiceTop];
  49. } else {
  50. self.cellLayout = [TUIMessageCellLayout outgoingVoiceMessageLayout];
  51. _voiceImage = TUIChatDynamicImage(@"chat_voice_message_sender_voice_normal_img",
  52. [[TUIImageCache sharedInstance] getResourceFromCache:TUIChatImagePath(@"message_voice_sender_normal")]);
  53. _voiceImage = [_voiceImage rtl_imageFlippedForRightToLeftLayoutDirection];
  54. _voiceAnimationImages = [NSArray arrayWithObjects:[self.class formatImageByName:@"message_voice_sender_playing_1"],
  55. [self.class formatImageByName:@"message_voice_sender_playing_2"],
  56. [self.class formatImageByName:@"message_voice_sender_playing_3"], nil];
  57. _voiceTop = [[self class] outgoingVoiceTop];
  58. }
  59. _voiceHeight = 21;
  60. }
  61. return self;
  62. }
  63. + (UIImage *)formatImageByName:(NSString *)imgName {
  64. NSString *path = TUIChatImagePath(imgName);
  65. UIImage *img = [[TUIImageCache sharedInstance] getResourceFromCache:path];
  66. return [img rtl_imageFlippedForRightToLeftLayoutDirection];
  67. }
  68. - (NSString *)getVoicePath:(BOOL *)isExist {
  69. NSString *path = nil;
  70. BOOL isDir = false;
  71. *isExist = NO;
  72. if (self.direction == MsgDirectionOutgoing) {
  73. if (_path.length) {
  74. path = [NSString stringWithFormat:@"%@%@", TUIKit_Voice_Path, _path.lastPathComponent];
  75. if ([[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDir]) {
  76. if (!isDir) {
  77. *isExist = YES;
  78. }
  79. }
  80. }
  81. }
  82. if (!*isExist) {
  83. if (_uuid.length) {
  84. path = [NSString stringWithFormat:@"%@%@.amr", TUIKit_Voice_Path, _uuid];
  85. if ([[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDir]) {
  86. if (!isDir) {
  87. *isExist = YES;
  88. }
  89. }
  90. }
  91. }
  92. return path;
  93. }
  94. - (V2TIMSoundElem *)getIMSoundElem {
  95. V2TIMMessage *imMsg = self.innerMessage;
  96. if (imMsg.elemType == V2TIM_ELEM_TYPE_SOUND) {
  97. return imMsg.soundElem;
  98. }
  99. return nil;
  100. }
  101. - (void)playVoiceMessage {
  102. if (self.isPlaying) {
  103. [self stopVoiceMessage];
  104. return;
  105. }
  106. self.isPlaying = YES;
  107. if (self.innerMessage.localCustomInt == 0) self.innerMessage.localCustomInt = 1;
  108. V2TIMSoundElem *imSound = [self getIMSoundElem];
  109. BOOL isExist = NO;
  110. if (self.uuid.length == 0) {
  111. self.uuid = imSound.uuid;
  112. }
  113. NSString *path = [self getVoicePath:&isExist];
  114. if (isExist) {
  115. [self playInternal:path];
  116. } else {
  117. if (self.isDownloading) {
  118. return;
  119. }
  120. //
  121. self.isDownloading = YES;
  122. @weakify(self);
  123. [imSound downloadSound:path
  124. progress:^(NSInteger curSize, NSInteger totalSize) {
  125. }
  126. succ:^{
  127. @strongify(self);
  128. self.isDownloading = NO;
  129. [self playInternal:path];
  130. }
  131. fail:^(int code, NSString *msg) {
  132. @strongify(self);
  133. self.isDownloading = NO;
  134. [self stopVoiceMessage];
  135. }];
  136. }
  137. }
  138. - (void)playInternal:(NSString *)path {
  139. if (!self.isPlaying) return;
  140. // play current
  141. TUIVoiceAudioPlaybackStyle playbackStyle = [self.class getAudioplaybackStyle];
  142. if(playbackStyle == TUIVoiceAudioPlaybackStyleHandset) {
  143. [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
  144. }
  145. else {
  146. [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
  147. }
  148. NSURL *url = [NSURL fileURLWithPath:path];
  149. self.audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
  150. self.audioPlayer.delegate = self;
  151. bool result = [self.audioPlayer play];
  152. if (!result) {
  153. self.wavPath = [[path stringByDeletingPathExtension] stringByAppendingString:@".wav"];
  154. NSURL *url = [NSURL fileURLWithPath:self.wavPath];
  155. [self.audioPlayer stop];
  156. self.audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
  157. self.audioPlayer.delegate = self;
  158. [self.audioPlayer play];
  159. }
  160. @weakify(self);
  161. if (@available(iOS 10.0, *)) {
  162. self.timer = [NSTimer scheduledTimerWithTimeInterval:0.1
  163. repeats:YES
  164. block:^(NSTimer *_Nonnull timer) {
  165. @strongify(self);
  166. [self updateProgress];
  167. }];
  168. } else {
  169. // Fallback on earlier versions
  170. }
  171. }
  172. //The style of audio playback.
  173. + (TUIVoiceAudioPlaybackStyle)getAudioplaybackStyle {
  174. NSString *style = [NSUserDefaults.standardUserDefaults objectForKey:@"tui_audioPlaybackStyle"];
  175. if ([style isEqualToString:@"1"]) {
  176. return TUIVoiceAudioPlaybackStyleLoudspeaker;
  177. } else if ([style isEqualToString:@"2"]) {
  178. return TUIVoiceAudioPlaybackStyleHandset;
  179. }
  180. return TUIVoiceAudioPlaybackStyleLoudspeaker;
  181. }
  182. + (void)changeAudioPlaybackStyle {
  183. TUIVoiceAudioPlaybackStyle style = [self getAudioplaybackStyle];
  184. if (style == TUIVoiceAudioPlaybackStyleLoudspeaker) {
  185. [NSUserDefaults.standardUserDefaults setObject:@"2" forKey:@"tui_audioPlaybackStyle"];
  186. }
  187. else {
  188. [NSUserDefaults.standardUserDefaults setObject:@"1" forKey:@"tui_audioPlaybackStyle"];
  189. }
  190. [NSUserDefaults.standardUserDefaults synchronize];
  191. }
  192. - (void)updateProgress {
  193. @weakify(self);
  194. dispatch_async(dispatch_get_main_queue(), ^{
  195. @strongify(self);
  196. self.currentTime = self.audioPlayer.currentTime;
  197. });
  198. }
  199. - (void)stopVoiceMessage {
  200. if ([self.audioPlayer isPlaying]) {
  201. [self.audioPlayer stop];
  202. self.audioPlayer = nil;
  203. }
  204. if (self.timer) {
  205. [self.timer invalidate];
  206. self.timer = nil;
  207. }
  208. self.isPlaying = NO;
  209. }
  210. - (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag;
  211. {
  212. [self stopVoiceMessage];
  213. [[NSFileManager defaultManager] removeItemAtPath:self.wavPath error:nil];
  214. if (self.audioPlayerDidFinishPlayingBlock) {
  215. self.audioPlayerDidFinishPlayingBlock();
  216. }
  217. }
  218. static CGFloat gIncommingVoiceTop = 12;
  219. + (void)setIncommingVoiceTop:(CGFloat)incommingVoiceTop {
  220. gIncommingVoiceTop = incommingVoiceTop;
  221. }
  222. + (CGFloat)incommingVoiceTop {
  223. return gIncommingVoiceTop;
  224. }
  225. static CGFloat gOutgoingVoiceTop = 12;
  226. + (void)setOutgoingVoiceTop:(CGFloat)outgoingVoiceTop {
  227. gOutgoingVoiceTop = outgoingVoiceTop;
  228. }
  229. + (CGFloat)outgoingVoiceTop {
  230. return gOutgoingVoiceTop;
  231. }
  232. @end