@@ -6,11 +6,10 @@ import {
66 Body ,
77 Param ,
88 Res ,
9- Headers ,
109 BadRequestException ,
1110} from '@nestjs/common' ;
1211import { Response } from 'express' ;
13- import { DownloadRequest , DownloadResult , VideoInfo , PlaylistInfo , YouTubeService } from './youtube.service' ;
12+ import { DownloadRequest , DownloadResult , VideoInfo , PlaylistInfo , DirectLinkResult , YouTubeService } from './youtube.service' ;
1413
1514
1615@Controller ( 'youtube' )
@@ -39,11 +38,37 @@ export class YouTubeController {
3938 return this . youtubeService . getPlaylistInfo ( url ) ;
4039 }
4140
41+ /**
42+ * Get direct download link for low-quality videos or audio
43+ * - Video: 720p and below (combined video+audio streams)
44+ * - Audio: any quality (bestaudio, 128kbps, 192kbps, etc.)
45+ * Saves server bandwidth compared to download-then-serve approach
46+ */
47+ @Get ( 'direct-link' )
48+ async getDirectLink (
49+ @Query ( 'url' ) url : string ,
50+ @Query ( 'formatType' ) formatType : 'video' | 'audio' ,
51+ @Query ( 'quality' ) quality : string ,
52+ ) : Promise < DirectLinkResult > {
53+ if ( ! url ) {
54+ throw new BadRequestException ( 'URL is required' ) ;
55+ }
56+ if ( ! formatType || ! [ 'video' , 'audio' ] . includes ( formatType ) ) {
57+ throw new BadRequestException ( 'formatType must be "video" or "audio"' ) ;
58+ }
59+ if ( ! quality ) {
60+ throw new BadRequestException ( 'quality is required (e.g., 720p, 360p for video or bestaudio, 128kbps for audio)' ) ;
61+ }
62+ return this . youtubeService . getDirectLink ( url , formatType , quality ) ;
63+ }
64+
4265 /**
4366 * Start download with selected format
4467 */
4568 @Post ( 'download' )
46- async startDownload ( @Body ( ) request : DownloadRequest ) : Promise < DownloadResult > {
69+ async startDownload (
70+ @Body ( ) request : DownloadRequest ,
71+ ) : Promise < DownloadResult > {
4772 if ( ! request . url ) {
4873 throw new BadRequestException ( 'URL is required' ) ;
4974 }
@@ -53,9 +78,71 @@ export class YouTubeController {
5378 if ( ! request . quality ) {
5479 throw new BadRequestException ( 'quality is required' ) ;
5580 }
81+
5682 return this . youtubeService . startDownload ( request ) ;
5783 }
5884
85+ /**
86+ * Proxy download endpoint - streams YouTube content with proper download headers
87+ * This ensures the browser downloads the file instead of opening in a new tab
88+ * MUST be placed before :id route to avoid route conflict
89+ */
90+ @Get ( 'proxy-download' )
91+ async proxyDownload (
92+ @Query ( 'url' ) url : string ,
93+ @Query ( 'filename' ) filename : string ,
94+ @Res ( ) res : Response ,
95+ ) : Promise < void > {
96+ if ( ! url ) {
97+ throw new BadRequestException ( 'URL is required' ) ;
98+ }
99+ if ( ! filename ) {
100+ throw new BadRequestException ( 'Filename is required' ) ;
101+ }
102+
103+ try {
104+ // Fetch the video/audio from YouTube CDN
105+ const response = await fetch ( url , {
106+ headers : {
107+ 'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' ,
108+ } ,
109+ } ) ;
110+
111+ if ( ! response . ok ) {
112+ throw new Error ( `Failed to fetch: ${ response . status } ` ) ;
113+ }
114+
115+ // Set proper headers for download
116+ const contentType = response . headers . get ( 'content-type' ) || 'application/octet-stream' ;
117+ const contentLength = response . headers . get ( 'content-length' ) ;
118+
119+ res . setHeader ( 'Content-Type' , contentType ) ;
120+ res . setHeader ( 'Content-Disposition' , `attachment; filename="${ encodeURIComponent ( filename ) } "` ) ;
121+ if ( contentLength ) {
122+ res . setHeader ( 'Content-Length' , contentLength ) ;
123+ }
124+
125+ // Pipe the stream directly to response (no storage)
126+ const reader = response . body ?. getReader ( ) ;
127+ if ( ! reader ) {
128+ throw new Error ( 'Failed to get reader' ) ;
129+ }
130+
131+ const pump = async ( ) => {
132+ while ( true ) {
133+ const { done, value } = await reader . read ( ) ;
134+ if ( done ) break ;
135+ res . write ( value ) ;
136+ }
137+ res . end ( ) ;
138+ } ;
139+
140+ pump ( ) . catch ( ( ) => res . end ( ) ) ;
141+ } catch ( error ) {
142+ res . status ( 500 ) . json ( { error : 'Failed to download' } ) ;
143+ }
144+ }
145+
59146 /**
60147 * Get download status
61148 */
0 commit comments