@@ -22,7 +22,7 @@ export class SessionController extends BaseController {
22
22
router . get ( "/:sessionId/logs/debug" , this . getDebugLogs . bind ( this ) ) ;
23
23
router . get ( "/:sessionId/profiling_data" , this . getProfilingData . bind ( this ) ) ;
24
24
router . get ( "/:sessionId/http_logs" , this . getHttpLogs . bind ( this ) ) ;
25
- router . get ( "/:sessionId/live_video" , this . getVideo . bind ( this ) ) ;
25
+ router . get ( "/:sessionId/live_video" , this . getLiveVideo . bind ( this ) ) ;
26
26
}
27
27
28
28
public async getSessions ( request : Request , response : Response , next : NextFunction ) {
@@ -75,16 +75,42 @@ export class SessionController extends BaseController {
75
75
76
76
public async getVideoForSession ( request : Request , response : Response , next : NextFunction ) {
77
77
let sessionId : string = request . params . sessionId ;
78
+ const range = request . headers . range ;
78
79
let session = await Session . findOne ( {
79
80
where : {
80
81
session_id : sessionId ,
81
82
} ,
82
83
} ) ;
83
- if ( session && session . video_path ) {
84
- return response . status ( 200 ) . sendFile ( session . video_path ) ;
84
+ const videoPath = session ?. video_path ;
85
+
86
+ if ( session && videoPath && range ) {
87
+ const videoSize = fs . statSync ( videoPath ) . size ;
88
+ // Parse Range
89
+ // Example: "bytes=32324-"
90
+ const CHUNK_SIZE = 10 ** 6 ; // 1MB
91
+ const start = Number ( range . replace ( / \D / g, "" ) ) ;
92
+ const end = Math . min ( start + CHUNK_SIZE , videoSize - 1 ) ;
93
+
94
+ // Create headers
95
+ const contentLength = end - start + 1 ;
96
+ const headers = {
97
+ "Content-Range" : `bytes ${ start } -${ end } /${ videoSize } ` ,
98
+ "Accept-Ranges" : "bytes" ,
99
+ "Content-Length" : contentLength ,
100
+ "Content-Type" : "video/mp4" ,
101
+ } ;
102
+
103
+ // HTTP Status 206 for Partial Content
104
+ response . writeHead ( 206 , headers ) ;
105
+
106
+ // create video read stream for this particular chunk
107
+ const videoStream = fs . createReadStream ( videoPath , { start, end } ) ;
108
+
109
+ // Stream the video chunk to the client
110
+ videoStream . pipe ( response ) ;
111
+ } else {
112
+ this . sendFailureResponse ( response , "Video not available" ) ;
85
113
}
86
-
87
- this . sendFailureResponse ( response , "Video not available" ) ;
88
114
}
89
115
90
116
public async getTextLogs ( request : Request , response : Response , next : NextFunction ) {
@@ -166,7 +192,7 @@ export class SessionController extends BaseController {
166
192
this . sendSuccessResponse ( response , logs ) ;
167
193
}
168
194
169
- public async getVideo ( request : Request , response : Response , next : NextFunction ) {
195
+ public async getLiveVideo ( request : Request , response : Response , next : NextFunction ) {
170
196
let sessionId : string = request . params . sessionId ;
171
197
let session = await Session . findOne ( {
172
198
where : {
@@ -182,6 +208,10 @@ export class SessionController extends BaseController {
182
208
const url = `${ request . protocol } ://${ request . hostname } :${ proxyPort } ` ;
183
209
SessionController . mjpegProxyCache . set ( proxyPort , new MjpegProxy ( url ) ) ;
184
210
}
185
- SessionController . mjpegProxyCache . get ( proxyPort ) ?. proxyRequest ( request , response ) ;
211
+ try {
212
+ SessionController . mjpegProxyCache . get ( proxyPort ) ?. proxyRequest ( request , response ) ;
213
+ } catch ( e ) {
214
+ return this . sendFailureResponse ( response , "Live video not available" ) ;
215
+ }
186
216
}
187
217
}
0 commit comments