diff --git a/src/chat/functions/prepareAudioWaveform.ts b/src/chat/functions/prepareAudioWaveform.ts
new file mode 100644
index 0000000000..1db8fd5520
--- /dev/null
+++ b/src/chat/functions/prepareAudioWaveform.ts
@@ -0,0 +1,80 @@
+/*!
+ * Copyright 2023 WPPConnect Team
+ *
+ * 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.
+ */
+
+import Debug from 'debug';
+
+import type { AudioMessageOptions } from './sendFileMessage';
+
+const debug = Debug('WA-JS:chat:sendFileMessage');
+
+/**
+ * Prepare waveform form message audio file
+ *
+ * @category Message
+ * @internal
+ */
+export async function prepareAudioWaveform(
+ options: AudioMessageOptions,
+ file: File
+): Promise<
+ | undefined
+ | {
+ duration: number;
+ waveform: Uint8Array;
+ }
+> {
+ if (!options.isPtt || !options.waveform) {
+ return;
+ }
+
+ /**
+ * @see https://css-tricks.com/making-an-audio-waveform-visualizer-with-vanilla-javascript/
+ */
+ try {
+ const audioData = await file.arrayBuffer();
+ const audioContext = new AudioContext();
+ const audioBuffer = await audioContext.decodeAudioData(audioData);
+
+ const rawData = audioBuffer.getChannelData(0); // We only need to work with one channel of data
+ const samples = 64; // Number of samples we want to have in our final data set
+ const blockSize = Math.floor(rawData.length / samples); // the number of samples in each subdivision
+ const filteredData = [];
+ for (let i = 0; i < samples; i++) {
+ const blockStart = blockSize * i; // the location of the first sample in the block
+ let sum = 0;
+ for (let j = 0; j < blockSize; j++) {
+ sum = sum + Math.abs(rawData[blockStart + j]); // find the sum of all the samples in the block
+ }
+ filteredData.push(sum / blockSize); // divide the sum by the block size to get the average
+ }
+
+ // This guarantees that the largest data point will be set to 1, and the rest of the data will scale proportionally.
+ const multiplier = Math.pow(Math.max(...filteredData), -1);
+ const normalizedData = filteredData.map((n) => n * multiplier);
+
+ // Generate waveform like WhatsApp
+ const waveform = new Uint8Array(
+ normalizedData.map((n) => Math.floor(100 * n))
+ );
+
+ return {
+ duration: Math.floor(audioBuffer.duration),
+ waveform,
+ };
+ } catch (error) {
+ debug('Failed to generate waveform', error);
+ }
+}
diff --git a/src/chat/functions/sendFileMessage.ts b/src/chat/functions/sendFileMessage.ts
index 511ed6ee22..7abee1ceee 100644
--- a/src/chat/functions/sendFileMessage.ts
+++ b/src/chat/functions/sendFileMessage.ts
@@ -39,6 +39,7 @@ import {
prepareMessageButtons,
prepareRawMessage,
} from '.';
+import { prepareAudioWaveform } from './prepareAudioWaveform';
const debug = Debug('WA-JS:message');
@@ -54,9 +55,53 @@ export interface AutoDetectMessageOptions extends FileMessageOptions {
type: 'auto-detect';
}
+/**
+ * Send an audio message as a PTT, like a recorded message
+ *
+ * @example
+ * ```javascript
+ * // PTT audio
+ * WPP.chat.sendFileMessage(
+ * '[number]@c.us',
+ * 'data:audio/mp3;base64,',
+ * {
+ * type: 'audio',
+ * isPtt: true // false for common audio
+ * }
+ * );
+ * ```
+ */
export interface AudioMessageOptions extends FileMessageOptions {
type: 'audio';
isPtt?: boolean;
+ /**
+ * Send an audio message as a PTT with waveform
+ *
+ * @example
+ * ```javascript
+ * // Enable waveform
+ * WPP.chat.sendFileMessage(
+ * '[number]@c.us',
+ * 'data:audio/mp3;base64,',
+ * {
+ * type: 'audio',
+ * isPtt: true,
+ * waveform: true // false to disable
+ * }
+ * );
+ * // Disable waveform
+ * WPP.chat.sendFileMessage(
+ * '[number]@c.us',
+ * 'data:audio/mp3;base64,',
+ * {
+ * type: 'audio',
+ * isPtt: true,
+ * waveform: false
+ * }
+ * );
+ * ```
+ */
+ waveform?: boolean;
}
export interface DocumentMessageOptions
@@ -169,6 +214,7 @@ export async function sendFileMessage(
...defaultSendMessageOptions,
...{
type: 'auto-detect',
+ waveform: true,
},
...options,
};
@@ -189,12 +235,20 @@ export async function sendFileMessage(
asGif?: boolean;
isAudio?: boolean;
asSticker?: boolean;
+ precomputedFields?: {
+ duration: number;
+ waveform: Uint8Array;
+ };
} = {};
let isViewOnce: boolean | undefined;
if (options.type === 'audio') {
rawMediaOptions.isPtt = options.isPtt;
+ rawMediaOptions.precomputedFields = await prepareAudioWaveform(
+ options as any,
+ file
+ );
} else if (options.type === 'image') {
isViewOnce = options.isViewOnce;
} else if (options.type === 'video') {
diff --git a/src/whatsapp/models/MsgModel.ts b/src/whatsapp/models/MsgModel.ts
index df1d36aec4..8f1a14a7b5 100644
--- a/src/whatsapp/models/MsgModel.ts
+++ b/src/whatsapp/models/MsgModel.ts
@@ -74,7 +74,8 @@ interface Props {
deprecatedMms3Url?: any;
directPath?: any;
mimetype?: any;
- duration?: any;
+ waveform?: any;
+ duration?: number;
filehash?: any;
encFilehash?: any;
size?: any;