Skip to content

Ripple and voice playback are not synchronized #383

@ybrsss

Description

@ybrsss

I am developing a chat app using flutter's getx framework. I found that when I receive a voice message and play the audio, the ripple shows that it has been played, but the voice is still playing. When I do not limit the width of the ripple, it can be displayed normally and ends synchronously. Within the fixed width of the conversation bubble, my ripple does not seem to have a way to compress its width to ensure that it can be fully displayed within the conversation bubble.
这是我语音logic中的代码
`
class DmAudioMessageLogic extends GetxController {
String tag = 'DMAudioMessageLogic';
ROLog log;
double maxWidth;
String uid;

DmAudioMessageLogic(
{required this.log, required this.maxWidth, required this.uid});

final DmAudioMessageState state = DmAudioMessageState();
final PlayerController playerController = PlayerController();
late StreamSubscription playerStateSubscription;
List waveformData = [];
late PlayerWaveStyle playerWaveStyle;

@OverRide
void onInit() {
super.onInit();
playerWaveStyle = PlayerWaveStyle(
fixedWaveColor: const Color(0xFFD8D8D8),
liveWaveColor: log.incoming
? DMColor.textColorPrimary
: DMColor.btnColorPrimary,
spacing: 3,
waveThickness: 2);
initDate(); // 初始化数据
autoDownLoad();
playerStateSubscription =
playerController.onPlayerStateChanged.listen((onData) {
DMLogHelper.logInfo(tag, "${playerController.playerKey}--状态更新--$onData");
if (onData == PlayerState.paused) {
state.isPlaying.value = false;
}
});
}

/// 下载语音
Future autoDownLoad() async {
File file = File(log.fileUrl!.absoluteFilePath!);
if (!await file.exists()) {
DMLogHelper.logInfo(tag, "文件不存在");
String signedUrl = log.fileUrl!.url!.signedUrl;
String savePath = log.fileUrl!.absoluteFilePath!;
await DMHttpHelper().dio.download(signedUrl, savePath,
onReceiveProgress: (count, total) {
if (total == count) {
state.isDownLoaded.value = true;
} else {
state.downLoadProgress.value = count / total;
}
});
}
if (await file.exists()) {
state.isDownLoaded.value = true;
await preparePlayer(file);
}
}

/// 准备播放器
Future preparePlayer(File file) async {
DMLogHelper.logInfo(tag, "准备播放器");

final width = getWidth();
// final samples = (width ~/ 8).clamp(50, 200);
final Samples =  playerWaveStyle.getSamplesForWidth(width);
print("width---$width------samples--${Samples}");
await playerController.preparePlayer(
  path: file.path,
  shouldExtractWaveform: false,
  noOfSamples: Samples,
  // noOfSamples: width ~/ 3,
  volume: 1.0,
);

}

/// 从 .dat 文件中提取波纹
initDate() {
try {
File file = File(log.fileUrl!.absolutePreviewFilePath!);
String jsonString = file.readAsStringSync();
DMLogHelper.logInfo(tag, 'json string $jsonString');
List decodedList = json.decode(jsonString);
waveformData =
decodedList.map((dynamic item) => (item as double) / 10.0).toList();
} catch (error) {
DMLogHelper.logError(tag, "读取波形数据失败: $error");
}
final duration = Duration(seconds: log.fileUrl!.duration!.round());
final minutes = duration.inMinutes.remainder(60).toString().padLeft(2, '0');
final remainingSeconds =
duration.inSeconds.remainder(60).toString().padLeft(2, '0');
state.audioTime.value = '${minutes}:${remainingSeconds}';
}

/// 播放语音
playAudio() async {
DMLogHelper.logInfo(tag, "处理语音--${(playerController.playerState)}");

// 正在播放
if (playerController.playerState == PlayerState.playing) {
  state.isPlaying.value = false;
  await playerController.pausePlayer(); //暂停播放
  await DMAudioPlayerManager.instance.pausePlaying(log.imdnId);
}
// 暂停/停止/初始化
else if (playerController.playerState == PlayerState.paused ||
    playerController.playerState == PlayerState.stopped ||
    playerController.playerState == PlayerState.initialized) {
  state.isPlaying.value = true;
  await DMAudioPlayerManager.instance
      .startPlaying(log.imdnId, playerController);
  playerController.setFinishMode(finishMode: FinishMode.pause);

  DmImLogic logic = Get.find<DmImLogic>(tag: uid);

  if (logic.recorderState() == RecorderState.recording) {
    logic.pauseRecordWithUI();
  }

  // 修改语音播放状态
  if (!log.voiceIsPlayed) {
    await DMDbHelper.beginTransaction(() {
      log.voiceIsPlayed = true;
    });
  }
}

}

/// 音频波形的实际宽度
/// 0-29s 宽度自适应 minWidth = 62,音频波形宽度 = 62+(录制时长-1)*4
/// 30-59s 178
/// >60 气泡最大宽度-74
double getWidth() {
final duration = log.fileUrl?.duration ?? 0;
if (duration < 30) {
return math.max(62, 62 + (duration * 4));
} else if (duration < 60) {
return 178;
} else {
return maxWidth - 74;
}
}

/// 处理发送按钮
void dealSendIcon() {
if (log.state == DMConstants.messageStateSendOk) {
state.isSendOver.value = true;
} else if (log.state == DMConstants.messageStateSendFail) {
state.isSendOver.value = true;
}
}
}
这是我语音气泡的widget viewclass DMAudioMessagePage extends StatelessWidget {
double maxWidth = 0.0;
ROLog log;
BorderRadius borderRadius;

DMAudioMessagePage(
{super.key,
required this.maxWidth,
required this.borderRadius,
required this.log});

@OverRide
Widget build(BuildContext context) {
double minWidth = 136;
double minHeight = 68;
return GetBuilder(
tag: log.imdnId,
builder: (logic) {
return GestureDetector(
onTap: () => logic.playAudio(),
child: Padding(
padding: EdgeInsets.only(left: log.incoming ? 16 : 0),
child: IntrinsicWidth(
child: ConstrainedBox(
constraints:
BoxConstraints(maxWidth: maxWidth, minHeight: minHeight),
child: Container(
constraints: BoxConstraints(minWidth: minWidth),
decoration: BoxDecoration(
color: log.incoming
? messageBubbleBackground(context)
: Theme.of(context).primaryColor,
borderRadius: borderRadius,
),
padding: const EdgeInsets.symmetric(
vertical: 11, horizontal: 12),
child: IntrinsicHeight(
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Obx(() {
return buildIcon(logic);
}),
const SizedBox(width: 10),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 音频条
AudioFileWaveforms(
size: Size(logic.getWidth(), 24.0),
// 提取数据后立即显示波形
waveformData: logic.waveformData,
playerController: logic.playerController,
// 波形类型
waveformType: WaveformType.fitWidth,
playerWaveStyle: logic.playerWaveStyle,
),
// 时长条
const SizedBox(height: 4),
audioTime(logic),
],
)
],
),
),
),
),
),
),
);
});
}这是在聊天界面中使用它的部分 /// 语音消息
Widget buildAudioMessage(
ROLog log, DmImLogic logic, BorderRadius borderRadius) {
double maxWidth = sDeviceInfo.idiom != 'pad'
? logic.state.currentViewWidth * 0.725
: logic.state.currentViewWidth * 0.5625;
Get.put(DmAudioMessageLogic(log: log, maxWidth: maxWidth, uid: uid),
tag: log.imdnId);
return GetBuilder(
tag: log.imdnId,
builder: (logic) {
return DMAudioMessagePage(
key: ValueKey(log.imdnId),
borderRadius: borderRadius,
log: log,
maxWidth: maxWidth,
);
});
}`
我的波纹数据是从预览文件中提取出来的

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions