@@ -6,6 +6,8 @@ export { VideoUploadError } from "@api.video/video-uploader/dist/src/abstract-up
6
6
7
7
export interface Options {
8
8
onError ?: ( error : VideoUploadError ) => void ;
9
+ generateFileOnStop ?: boolean ;
10
+ mimeType ?: string ;
9
11
}
10
12
11
13
let PACKAGE_VERSION = "" ;
@@ -25,15 +27,31 @@ export class ApiVideoMediaRecorder {
25
27
private onVideoAvailable ?: ( video : VideoUploadResponse ) => void ;
26
28
private onStopError ?: ( error : VideoUploadError ) => void ;
27
29
private eventTarget : EventTarget ;
30
+ private debugChunks : Blob [ ] = [ ] ;
31
+ private generateFileOnStop : boolean ;
32
+ private mimeType : string ;
28
33
29
34
constructor ( mediaStream : MediaStream , options : Options & ( ProgressiveUploaderOptionsWithUploadToken | ProgressiveUploaderOptionsWithAccessToken ) ) {
30
35
this . eventTarget = new EventTarget ( ) ;
31
- const supportedTypes = this . getSupportedMimeTypes ( ) ;
32
- if ( supportedTypes . length === 0 ) {
33
- throw new Error ( "No compatible supported video mime type" ) ;
36
+ this . generateFileOnStop = options . generateFileOnStop || false ;
37
+
38
+ const findBestMimeType = ( ) => {
39
+ const supportedTypes = this . getSupportedMimeTypes ( ) ;
40
+ if ( supportedTypes . length === 0 ) {
41
+ throw new Error ( "No compatible supported video mime type" ) ;
42
+ }
43
+ return supportedTypes [ 0 ] ;
34
44
}
45
+
46
+ this . mimeType = options . mimeType || findBestMimeType ( ) ;
47
+
35
48
this . mediaRecorder = new MediaRecorder ( mediaStream , {
36
- mimeType : supportedTypes [ 0 ] ,
49
+ mimeType : this . mimeType ,
50
+ } ) ;
51
+
52
+ this . mediaRecorder . addEventListener ( "stop" , ( ) => {
53
+ const stopEventPayload = this . generateFileOnStop ? { file : new Blob ( this . debugChunks , { type : this . mimeType } ) } : { } ;
54
+ this . dispatch ( "recordingStopped" , stopEventPayload ) ;
37
55
} ) ;
38
56
39
57
this . streamUpload = new ProgressiveUploader ( {
@@ -53,7 +71,7 @@ export class ApiVideoMediaRecorder {
53
71
}
54
72
55
73
public addEventListener ( type : EventType , callback : EventListenerOrEventListenerObject | null , options ?: boolean | AddEventListenerOptions | undefined ) : void {
56
- if ( type === "videoPlayable" ) {
74
+ if ( type === "videoPlayable" ) {
57
75
this . streamUpload . onPlayable ( ( video ) => this . dispatch ( "videoPlayable" , video ) ) ;
58
76
}
59
77
this . eventTarget . addEventListener ( type , callback , options ) ;
@@ -62,6 +80,9 @@ export class ApiVideoMediaRecorder {
62
80
private async onDataAvailable ( ev : BlobEvent ) {
63
81
const isLast = ( ev as any ) . currentTarget . state === "inactive" ;
64
82
try {
83
+ if ( this . generateFileOnStop ) {
84
+ this . debugChunks . push ( ev . data ) ;
85
+ }
65
86
if ( isLast ) {
66
87
const video = await this . streamUpload . uploadLastPart ( ev . data ) ;
67
88
@@ -72,18 +93,18 @@ export class ApiVideoMediaRecorder {
72
93
await this . streamUpload . uploadPart ( ev . data ) ;
73
94
}
74
95
} catch ( error ) {
75
- if ( ! isLast ) this . stopMediaRecorder ( ) ;
96
+ if ( ! isLast ) this . mediaRecorder . stop ( ) ;
76
97
this . dispatch ( "error" , error ) ;
77
98
if ( this . onStopError ) this . onStopError ( error as VideoUploadError ) ;
78
99
}
79
100
}
80
101
81
102
private dispatch ( type : EventType , data : any ) : boolean {
82
- return this . eventTarget . dispatchEvent ( Object . assign ( new Event ( type ) , { data} ) ) ;
103
+ return this . eventTarget . dispatchEvent ( Object . assign ( new Event ( type ) , { data } ) ) ;
83
104
}
84
105
85
106
public start ( options ?: { timeslice ?: number } ) {
86
- if ( this . getMediaRecorderState ( ) === "recording" ) {
107
+ if ( this . getMediaRecorderState ( ) === "recording" ) {
87
108
throw new Error ( "MediaRecorder is already recording" ) ;
88
109
}
89
110
this . mediaRecorder . start ( options ?. timeslice || 5000 ) ;
@@ -95,46 +116,42 @@ export class ApiVideoMediaRecorder {
95
116
96
117
public stop ( ) : Promise < VideoUploadResponse > {
97
118
return new Promise ( ( resolve , reject ) => {
98
- if ( this . getMediaRecorderState ( ) === "inactive" ) {
119
+ if ( this . getMediaRecorderState ( ) === "inactive" ) {
99
120
reject ( new Error ( "MediaRecorder is already inactive" ) ) ;
100
121
}
101
- this . stopMediaRecorder ( ) ;
122
+ this . mediaRecorder . stop ( ) ;
102
123
this . onVideoAvailable = ( v ) => resolve ( v ) ;
103
124
this . onStopError = ( e ) => reject ( e ) ;
104
125
} )
105
126
}
106
127
107
128
public pause ( ) {
108
- if ( this . getMediaRecorderState ( ) !== "recording" ) {
129
+ if ( this . getMediaRecorderState ( ) !== "recording" ) {
109
130
throw new Error ( "MediaRecorder is not recording" ) ;
110
131
}
111
132
this . mediaRecorder . pause ( ) ;
112
133
}
113
134
114
- private stopMediaRecorder ( ) {
115
- this . mediaRecorder . stop ( ) ;
116
- this . dispatch ( "recordingStopped" , { } ) ;
117
- }
118
-
119
- private getSupportedMimeTypes ( ) {
135
+ public getSupportedMimeTypes ( ) {
120
136
const VIDEO_TYPES = [
137
+ "mp4" ,
121
138
"webm" ,
122
139
"ogg" ,
123
- "mp4" ,
124
140
"x-matroska"
125
141
] ;
126
142
const VIDEO_CODECS = [
127
- "h264 " ,
128
- "h.264 " ,
143
+ "vp9,opus " ,
144
+ "vp8,opus " ,
129
145
"vp9" ,
130
146
"vp9.0" ,
131
147
"vp8" ,
132
148
"vp8.0" ,
149
+ "h264" ,
150
+ "h.264" ,
133
151
"avc1" ,
134
152
"av1" ,
135
153
"h265" ,
136
154
"h.265" ,
137
- "opus" ,
138
155
] ;
139
156
140
157
const supportedTypes : string [ ] = [ ] ;
@@ -148,10 +165,12 @@ export class ApiVideoMediaRecorder {
148
165
`${ type } ;codecs:${ codec . toUpperCase ( ) } ` ,
149
166
`${ type } `
150
167
]
151
- variations . forEach ( variation => {
152
- if ( MediaRecorder . isTypeSupported ( variation ) )
168
+ for ( const variation of variations ) {
169
+ if ( MediaRecorder . isTypeSupported ( variation ) ) {
153
170
supportedTypes . push ( variation ) ;
154
- } )
171
+ break ;
172
+ }
173
+ }
155
174
} ) ;
156
175
} ) ;
157
176
return supportedTypes ;
0 commit comments