Skip to content

Commit a5a5a98

Browse files
committed
(feat) add voice messages
1 parent e89cdfe commit a5a5a98

File tree

9 files changed

+115
-14
lines changed

9 files changed

+115
-14
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ fetchMessages({ room, options }) {
179179
| showAddRoom | Boolean | - | true |
180180
| showSendIcon | Boolean | - | true |
181181
| showFiles | Boolean | - | true |
182+
| showAudio | Boolean | - | true |
182183
| showEmojis | Boolean | - | true |
183184
| showReactionEmojis | Boolean | - | true |
184185
| showNewMessagesDivider (8) | Boolean | - | true |
@@ -642,6 +643,8 @@ Example:
642643
| pencil-icon | Replace the pencil icon | - | - |
643644
| checkmark-icon | Replace the checkmark icon | message | - |
644645
| deleted-icon | Replace the deleted icon | deleted | - |
646+
| microphone-icon | Replace the microphone icon | | - |
647+
| microphone-off-icon | Replace the microphone-off icon | | - |
645648
| dropdown-icon | Replace the dropdown icon | - | - |
646649
| search-icon | Replace the search icon | - | - |
647650
| add-icon | Replace the add room icon | - | - |

demo/src/ChatContainer.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,7 @@ export default {
394394
type: file.type,
395395
url: file.localUrl
396396
}
397+
if (file.audio) message.file.audio = true
397398
}
398399
399400
if (replyMessage) {
@@ -428,6 +429,7 @@ export default {
428429
type: file.type,
429430
url: file.url || file.localUrl
430431
}
432+
if (file.audio) message.file.audio = true
431433
} else {
432434
newMessage.file = deleteDbField
433435
}

src/ChatWindow/ChatWindow.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
:messageActions="messageActions"
3232
:showSendIcon="showSendIcon"
3333
:showFiles="showFiles"
34+
:showAudio="showAudio"
3435
:showEmojis="showEmojis"
3536
:showReactionEmojis="showReactionEmojis"
3637
:showNewMessagesDivider="showNewMessagesDivider"
@@ -103,6 +104,7 @@ export default {
103104
showAddRoom: { type: Boolean, default: true },
104105
showSendIcon: { type: Boolean, default: true },
105106
showFiles: { type: Boolean, default: true },
107+
showAudio: { type: Boolean, default: true },
106108
showEmojis: { type: Boolean, default: true },
107109
showReactionEmojis: { type: Boolean, default: true },
108110
showNewMessagesDivider: { type: Boolean, default: true },

src/ChatWindow/FormatMessage.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@
2222
>
2323
<slot name="deleted-icon" v-bind="{ deleted }">
2424
<svg-icon v-if="deleted" name="deleted" class="icon-deleted" />
25-
{{ message.value }}
2625
</slot>
26+
{{ message.value }}
2727
</component>
2828
</template>
2929
</div>

src/ChatWindow/Message.vue

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,12 @@
129129
></format-message>
130130
</div>
131131

132+
<div v-else-if="message.file.audio" class="audio-message">
133+
<audio controls v-if="message.file.audio">
134+
<source :src="message.file.url" />
135+
</audio>
136+
</div>
137+
132138
<div v-else class="file-message">
133139
<div
134140
class="svg-button icon-file"
@@ -715,6 +721,10 @@ export default {
715721
text-align: right;
716722
}
717723
724+
.audio-message {
725+
margin-top: 3px;
726+
}
727+
718728
.file-message {
719729
display: flex;
720730
flex-wrap: wrap;

src/ChatWindow/Room.vue

Lines changed: 61 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,20 @@
169169
</transition>
170170

171171
<div class="box-footer">
172+
<div class="icon-textarea-left" v-if="showAudio">
173+
<div class="svg-button" @click="recordAudio">
174+
<slot
175+
v-if="recorder.state === 'recording'"
176+
name="microphone-off-icon"
177+
>
178+
<svg-icon name="microphone-off" />
179+
</slot>
180+
<slot v-else name="microphone-icon">
181+
<svg-icon name="microphone" />
182+
</slot>
183+
</div>
184+
</div>
185+
172186
<div
173187
class="image-container"
174188
:class="{ 'image-container-right': textareaAction }"
@@ -202,14 +216,6 @@
202216
</div>
203217
</div>
204218

205-
<div class="icon-textarea-left" v-if="textareaAction">
206-
<div class="svg-button" @click="textareaActionHandler">
207-
<slot name="custom-action-icon">
208-
<svg-icon name="microphone" />
209-
</slot>
210-
</div>
211-
</div>
212-
213219
<textarea
214220
v-show="!file || imageFile"
215221
ref="roomTextarea"
@@ -324,6 +330,7 @@ export default {
324330
messageActions: { type: Array, required: true },
325331
showSendIcon: { type: Boolean, required: true },
326332
showFiles: { type: Boolean, required: true },
333+
showAudio: { type: Boolean, required: true },
327334
showEmojis: { type: Boolean, required: true },
328335
showReactionEmojis: { type: Boolean, required: true },
329336
showNewMessagesDivider: { type: Boolean, required: true },
@@ -349,7 +356,10 @@ export default {
349356
emojisList: {},
350357
hideOptions: true,
351358
scrollIcon: false,
352-
newMessages: []
359+
newMessages: [],
360+
recorderStream: {},
361+
recorder: {},
362+
recordedChunks: []
353363
}
354364
},
355365
@@ -501,6 +511,48 @@ export default {
501511
},
502512
503513
methods: {
514+
async recordAudio() {
515+
if (this.recorder.state === 'recording') {
516+
this.recorder.stop()
517+
} else {
518+
this.recordedChunk = await this.startRecording()
519+
}
520+
},
521+
async startRecording() {
522+
const stream = await navigator.mediaDevices.getUserMedia({
523+
audio: true,
524+
video: false
525+
})
526+
527+
this.recorder = new MediaRecorder(stream)
528+
529+
this.recorder.ondataavailable = e => this.recordedChunks.push(e.data)
530+
this.recorder.start()
531+
532+
const stopped = new Promise((resolve, reject) => {
533+
this.recorder.onstop = resolve
534+
this.recorder.onerror = event => reject(event.name)
535+
})
536+
537+
stopped.then(() => {
538+
stream.getTracks().forEach(track => track.stop())
539+
540+
const recordedBlob = new Blob(this.recordedChunks, {
541+
type: 'audio/ogg; codecs="opus"'
542+
})
543+
544+
this.file = {
545+
blob: recordedBlob,
546+
name: 'audio',
547+
size: recordedBlob.size,
548+
type: recordedBlob.type,
549+
audio: true,
550+
localUrl: URL.createObjectURL(recordedBlob)
551+
}
552+
553+
this.message = 'audio'
554+
})
555+
},
504556
addNewMessage(message) {
505557
this.newMessages.push(message)
506558
},

src/ChatWindow/RoomsList.vue

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,20 @@
8181
></svg-icon>
8282
</slot>
8383
</span>
84+
<div
85+
v-if="
86+
room.lastMessage &&
87+
room.lastMessage.file &&
88+
room.lastMessage.file.audio
89+
"
90+
class="text-ellipsis"
91+
>
92+
<slot name="microphone-icon">
93+
<svg-icon name="microphone" class="icon-microphone" />
94+
</slot>
95+
</div>
8496
<format-message
85-
v-if="room.lastMessage"
97+
v-else-if="room.lastMessage"
8698
:content="getLastMessage(room)"
8799
:deleted="!!room.lastMessage.deleted"
88100
:formatLinks="false"
@@ -375,6 +387,14 @@ input {
375387
background-color: var(--chat-room-color-online);
376388
}
377389
390+
.icon-microphone {
391+
height: 15px;
392+
width: 15px;
393+
vertical-align: middle;
394+
margin: -3px 1px 0 -1px;
395+
fill: var(--chat-room-color-message);
396+
}
397+
378398
.room-badge {
379399
background-color: var(--chat-room-bg-color-badge);
380400
color: var(--chat-room-color-badge);

src/ChatWindow/SvgIcon.vue

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,11 @@ export default {
112112
name: 'microphone',
113113
path:
114114
'M16 24h-8v-1h3.5v-3.018c-3.63-.256-6.5-3.287-6.5-6.982v-2h1v2.01c.005 3.307 2.692 5.99 6 5.99 3.311 0 6-2.689 6-6v-2h1v2c0 3.695-2.87 6.726-6.5 6.982v3.018h3.5v1zm-9-19c0-2.76 2.24-5 5-5s5 2.24 5 5v8c0 2.76-2.24 5-5 5s-5-2.24-5-5v-8zm9 0c0-2.208-1.792-4-4-4s-4 1.792-4 4v8c0 2.208 1.792 4 4 4s4-1.792 4-4v-8z'
115+
},
116+
{
117+
name: 'microphone-off',
118+
path:
119+
'M16 24h-8v-1h3.5v-3.018c-3.63-.256-6.5-3.287-6.5-6.982v-2h1v2.01c.005 3.307 2.692 5.99 6 5.99 3.311 0 6-2.689 6-6v-2h1v2c0 3.695-2.87 6.726-6.5 6.982v3.018h3.5v1zm-9-19c0-2.76 2.24-5 5-5s5 2.24 5 5v8c0 2.76-2.24 5-5 5s-5-2.24-5-5v-8zm9 0c0-2.208-1.792-4-4-4s-4 1.792-4 4v8c0 2.208 1.792 4 4 4s4-1.792 4-4v-8z'
115120
}
116121
]
117122
}
@@ -205,4 +210,8 @@ export default {
205210
#chat-icon-microphone {
206211
fill: var(--chat-icon-color-microphone);
207212
}
213+
214+
#chat-icon-microphone-off {
215+
fill: var(--chat-icon-color-microphone-off);
216+
}
208217
</style>

src/themes/index.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,8 @@ export const defaultThemeStyles = {
120120
dropdownMessage: '#fff',
121121
dropdownMessageBackground: 'rgba(0, 0, 0, 0.25)',
122122
dropdownScroll: '#0a0a0a',
123-
microphone: '#1976d2'
123+
microphone: '#1976d2',
124+
microphoneOff: '#eb4034'
124125
}
125126
},
126127
dark: {
@@ -244,7 +245,8 @@ export const defaultThemeStyles = {
244245
dropdownMessage: '#fff',
245246
dropdownMessageBackground: 'rgba(0, 0, 0, 0.25)',
246247
dropdownScroll: '#0a0a0a',
247-
microphone: '#fff'
248+
microphone: '#fff',
249+
microphoneOff: '#eb4034'
248250
}
249251
}
250252
}
@@ -375,6 +377,7 @@ export const cssThemeVars = ({
375377
'--chat-icon-color-dropdown-message': icons.dropdownMessage,
376378
'--chat-icon-bg-dropdown-message': icons.dropdownMessageBackground,
377379
'--chat-icon-color-dropdown-scroll': icons.dropdownScroll,
378-
'--chat-icon-color-microphone': icons.microphone
380+
'--chat-icon-color-microphone': icons.microphone,
381+
'--chat-icon-color-microphone-off': icons.microphoneOff
379382
}
380383
}

0 commit comments

Comments
 (0)