1
- import { Body , Controller , Get , Param , Post , Req , Res } from '@nestjs/common' ;
1
+ import {
2
+ Body ,
3
+ Controller ,
4
+ Get ,
5
+ Param ,
6
+ Post ,
7
+ Query ,
8
+ Req ,
9
+ Res ,
10
+ StreamableFile ,
11
+ } from '@nestjs/common' ;
2
12
import { ApiTags } from '@nestjs/swagger' ;
3
13
import { AgenciesService } from '@gitroom/nestjs-libraries/database/prisma/agencies/agencies.service' ;
4
14
import { PostsService } from '@gitroom/nestjs-libraries/database/prisma/posts/posts.service' ;
@@ -11,6 +21,10 @@ import { makeId } from '@gitroom/nestjs-libraries/services/make.is';
11
21
import { getCookieUrlFromDomain } from '@gitroom/helpers/subdomain/subdomain.management' ;
12
22
import { AgentGraphInsertService } from '@gitroom/nestjs-libraries/agent/agent.graph.insert.service' ;
13
23
import { Nowpayments } from '@gitroom/nestjs-libraries/crypto/nowpayments' ;
24
+ import { Readable , pipeline } from 'stream' ;
25
+ import { promisify } from 'util' ;
26
+
27
+ const pump = promisify ( pipeline ) ;
14
28
15
29
@ApiTags ( 'Public' )
16
30
@Controller ( '/public' )
@@ -136,4 +150,46 @@ export class PublicController {
136
150
console . log ( 'cryptoPost' , body , path ) ;
137
151
return this . _nowpayments . processPayment ( path , body ) ;
138
152
}
153
+
154
+ @Get ( '/stream' )
155
+ async streamFile (
156
+ @Query ( 'url' ) url : string ,
157
+ @Res ( ) res : Response ,
158
+ @Req ( ) req : Request
159
+ ) {
160
+ if ( ! url . endsWith ( 'mp4' ) ) {
161
+ return res . status ( 400 ) . send ( 'Invalid video URL' ) ;
162
+ }
163
+
164
+ const ac = new AbortController ( ) ;
165
+ const onClose = ( ) => ac . abort ( ) ;
166
+ req . on ( 'aborted' , onClose ) ;
167
+ res . on ( 'close' , onClose ) ;
168
+
169
+ const r = await fetch ( url , { signal : ac . signal } ) ;
170
+
171
+ if ( ! r . ok && r . status !== 206 ) {
172
+ res . status ( r . status ) ;
173
+ throw new Error ( `Upstream error: ${ r . statusText } ` ) ;
174
+ }
175
+
176
+ const type = r . headers . get ( 'content-type' ) ?? 'application/octet-stream' ;
177
+ res . setHeader ( 'Content-Type' , type ) ;
178
+
179
+ const contentRange = r . headers . get ( 'content-range' ) ;
180
+ if ( contentRange ) res . setHeader ( 'Content-Range' , contentRange ) ;
181
+
182
+ const len = r . headers . get ( 'content-length' ) ;
183
+ if ( len ) res . setHeader ( 'Content-Length' , len ) ;
184
+
185
+ const acceptRanges = r . headers . get ( 'accept-ranges' ) ?? 'bytes' ;
186
+ res . setHeader ( 'Accept-Ranges' , acceptRanges ) ;
187
+
188
+ if ( r . status === 206 ) res . status ( 206 ) ; // Partial Content for range responses
189
+
190
+ try {
191
+ await pump ( Readable . fromWeb ( r . body as any ) , res ) ;
192
+ } catch ( err ) {
193
+ }
194
+ }
139
195
}
0 commit comments