@@ -33,7 +33,14 @@ import {
33
33
import { THREAD_RELATION_TYPE } from "matrix-js-sdk/src/models/thread" ;
34
34
import { removeElement } from "matrix-js-sdk/src/utils" ;
35
35
36
- import { IEncryptedFile , IMediaEventContent , IMediaEventInfo } from "./customisations/models/IMediaEventContent" ;
36
+ import {
37
+ AudioInfo ,
38
+ EncryptedFile ,
39
+ ImageInfo ,
40
+ IMediaEventContent ,
41
+ IMediaEventInfo ,
42
+ VideoInfo ,
43
+ } from "./customisations/models/IMediaEventContent" ;
37
44
import dis from "./dispatcher/dispatcher" ;
38
45
import { _t } from "./languageHandler" ;
39
46
import Modal from "./Modal" ;
@@ -146,11 +153,7 @@ const ALWAYS_INCLUDE_THUMBNAIL = ["image/avif", "image/webp"];
146
153
* @param {File } imageFile The image to read and thumbnail.
147
154
* @return {Promise } A promise that resolves with the attachment info.
148
155
*/
149
- async function infoForImageFile (
150
- matrixClient : MatrixClient ,
151
- roomId : string ,
152
- imageFile : File ,
153
- ) : Promise < Partial < IMediaEventInfo > > {
156
+ async function infoForImageFile ( matrixClient : MatrixClient , roomId : string , imageFile : File ) : Promise < ImageInfo > {
154
157
let thumbnailType = "image/png" ;
155
158
if ( imageFile . type === "image/jpeg" ) {
156
159
thumbnailType = "image/jpeg" ;
@@ -184,16 +187,59 @@ async function infoForImageFile(
184
187
return imageInfo ;
185
188
}
186
189
190
+ /**
191
+ * Load a file into a newly created audio element and load the metadata
192
+ *
193
+ * @param {File } audioFile The file to load in an audio element.
194
+ * @return {Promise } A promise that resolves with the audio element.
195
+ */
196
+ function loadAudioElement ( audioFile : File ) : Promise < HTMLAudioElement > {
197
+ return new Promise ( ( resolve , reject ) => {
198
+ // Load the file into a html element
199
+ const audio = document . createElement ( "audio" ) ;
200
+ audio . preload = "metadata" ;
201
+ audio . muted = true ;
202
+
203
+ const reader = new FileReader ( ) ;
204
+
205
+ reader . onload = function ( ev ) : void {
206
+ audio . onloadedmetadata = async function ( ) : Promise < void > {
207
+ resolve ( audio ) ;
208
+ } ;
209
+ audio . onerror = function ( e ) : void {
210
+ reject ( e ) ;
211
+ } ;
212
+
213
+ audio . src = ev . target ?. result as string ;
214
+ } ;
215
+ reader . onerror = function ( e ) : void {
216
+ reject ( e ) ;
217
+ } ;
218
+ reader . readAsDataURL ( audioFile ) ;
219
+ } ) ;
220
+ }
221
+
222
+ /**
223
+ * Read the metadata for an audio file.
224
+ *
225
+ * @param {File } audioFile The audio to read.
226
+ * @return {Promise } A promise that resolves with the attachment info.
227
+ */
228
+ async function infoForAudioFile ( audioFile : File ) : Promise < AudioInfo > {
229
+ const audio = await loadAudioElement ( audioFile ) ;
230
+ return { duration : Math . ceil ( audio . duration * 1000 ) } ;
231
+ }
232
+
187
233
/**
188
234
* Load a file into a newly created video element and pull some strings
189
235
* in an attempt to guarantee the first frame will be showing.
190
236
*
191
- * @param {File } videoFile The file to load in an video element.
192
- * @return {Promise } A promise that resolves with the video image element.
237
+ * @param {File } videoFile The file to load in a video element.
238
+ * @return {Promise } A promise that resolves with the video element.
193
239
*/
194
240
function loadVideoElement ( videoFile : File ) : Promise < HTMLVideoElement > {
195
241
return new Promise ( ( resolve , reject ) => {
196
- // Load the file into an html element
242
+ // Load the file into a html element
197
243
const video = document . createElement ( "video" ) ;
198
244
video . preload = "metadata" ;
199
245
video . playsInline = true ;
@@ -237,20 +283,17 @@ function loadVideoElement(videoFile: File): Promise<HTMLVideoElement> {
237
283
* @param {File } videoFile The video to read and thumbnail.
238
284
* @return {Promise } A promise that resolves with the attachment info.
239
285
*/
240
- function infoForVideoFile (
241
- matrixClient : MatrixClient ,
242
- roomId : string ,
243
- videoFile : File ,
244
- ) : Promise < Partial < IMediaEventInfo > > {
286
+ function infoForVideoFile ( matrixClient : MatrixClient , roomId : string , videoFile : File ) : Promise < VideoInfo > {
245
287
const thumbnailType = "image/jpeg" ;
246
288
247
- let videoInfo : Partial < IMediaEventInfo > ;
289
+ const videoInfo : VideoInfo = { } ;
248
290
return loadVideoElement ( videoFile )
249
291
. then ( ( video ) => {
292
+ videoInfo . duration = Math . ceil ( video . duration * 1000 ) ;
250
293
return createThumbnail ( video , video . videoWidth , video . videoHeight , thumbnailType ) ;
251
294
} )
252
295
. then ( ( result ) => {
253
- videoInfo = result . info ;
296
+ Object . assign ( videoInfo , result . info ) ;
254
297
return uploadFile ( matrixClient , roomId , result . thumbnail ) ;
255
298
} )
256
299
. then ( ( result ) => {
@@ -299,7 +342,7 @@ export async function uploadFile(
299
342
file : File | Blob ,
300
343
progressHandler ?: UploadOpts [ "progressHandler" ] ,
301
344
controller ?: AbortController ,
302
- ) : Promise < { url ?: string ; file ?: IEncryptedFile } > {
345
+ ) : Promise < { url ?: string ; file ?: EncryptedFile } > {
303
346
const abortController = controller ?? new AbortController ( ) ;
304
347
305
348
// If the room is encrypted then encrypt the file before uploading it.
@@ -329,7 +372,7 @@ export async function uploadFile(
329
372
file : {
330
373
...encryptResult . info ,
331
374
url,
332
- } as IEncryptedFile ,
375
+ } as EncryptedFile ,
333
376
} ;
334
377
} else {
335
378
const { content_uri : url } = await matrixClient . uploadContent ( file , { progressHandler, abortController } ) ;
@@ -546,6 +589,14 @@ export default class ContentMessages {
546
589
}
547
590
} else if ( file . type . indexOf ( "audio/" ) === 0 ) {
548
591
content . msgtype = MsgType . Audio ;
592
+ try {
593
+ const audioInfo = await infoForAudioFile ( file ) ;
594
+ Object . assign ( content . info , audioInfo ) ;
595
+ } catch ( e ) {
596
+ // Failed to process audio file, fall back to uploading an m.file
597
+ logger . error ( e ) ;
598
+ content . msgtype = MsgType . File ;
599
+ }
549
600
} else if ( file . type . indexOf ( "video/" ) === 0 ) {
550
601
content . msgtype = MsgType . Video ;
551
602
try {
0 commit comments