@@ -3,14 +3,24 @@ import { Streamer, Utils, prepareStream, playStream } from "@dank074/discord-vid
33import config from "./config.js" ;
44import fs from 'fs' ;
55import path from 'path' ;
6- import ytdl from '@distube/ytdl-core' ;
76import { getStream , getVod } from 'twitch-m3u8' ;
87import yts from 'play-dl' ;
98import { getVideoParams , ffmpegScreenshot } from "./utils/ffmpeg.js" ;
109import logger from './utils/logger.js' ;
10+ import { downloadExecutable , downloadToTempFile , checkForUpdatesAndUpdate } from './utils/yt-dlp.js' ;
1111import { Youtube } from './utils/youtube.js' ;
1212import { TwitchStream } from './@types/index.js' ;
1313
14+ // Download yt-dlp and check for updates
15+ ( async ( ) => {
16+ try {
17+ await downloadExecutable ( ) ;
18+ await checkForUpdatesAndUpdate ( ) ;
19+ } catch ( error ) {
20+ logger . error ( "Error during initial yt-dlp setup/update:" , error ) ;
21+ }
22+ } ) ( ) ;
23+
1424// Create a new instance of Streamer
1525const streamer = new Streamer ( new Client ( ) ) ;
1626
@@ -172,7 +182,7 @@ streamer.client.on('messageCreate', async (message) => {
172182 sendPlaying ( message , videoname || "Local Video" ) ;
173183
174184 // Play video
175- playVideo ( video . path , videoname ) ;
185+ playVideo ( message , video . path , videoname ) ;
176186 }
177187 break ;
178188 case 'playlink' :
@@ -190,19 +200,20 @@ streamer.client.on('messageCreate', async (message) => {
190200 }
191201
192202 switch ( true ) {
193- case ytdl . validateURL ( link ) :
203+ case ( link . includes ( 'youtube.com/' ) || link . includes ( 'youtu.be/' ) ) :
194204 {
195- const [ videoInfo , yturl ] = await Promise . all ( [
196- ytdl . getInfo ( link ) ,
197- getVideoUrl ( link ) . catch ( error => {
198- logger . error ( "Error:" , error ) ;
199- return null ;
200- } )
201- ] ) ;
202-
203- if ( yturl ) {
204- sendPlaying ( message , videoInfo . videoDetails . title ) ;
205- playVideo ( yturl , videoInfo . videoDetails . title ) ;
205+ try {
206+ const videoDetails = await youtube . getVideoInfo ( link ) ;
207+
208+ if ( videoDetails && videoDetails . title ) {
209+ playVideo ( message , link , videoDetails . title ) ;
210+ } else {
211+ logger . error ( `Failed to get YouTube video info for link: ${ link } .` ) ;
212+ await sendError ( message , 'Failed to process YouTube link.' ) ;
213+ }
214+ } catch ( error ) {
215+ logger . error ( `Error processing YouTube link: ${ link } ` , error ) ;
216+ await sendError ( message , 'Error processing YouTube link.' ) ;
206217 }
207218 }
208219 break ;
@@ -212,14 +223,14 @@ streamer.client.on('messageCreate', async (message) => {
212223 const twitchUrl = await getTwitchStreamUrl ( link ) ;
213224 if ( twitchUrl ) {
214225 sendPlaying ( message , `${ twitchId } 's Twitch Stream` ) ;
215- playVideo ( twitchUrl , `twitch.tv/${ twitchId } ` ) ;
226+ playVideo ( message , twitchUrl , `twitch.tv/${ twitchId } ` ) ;
216227 }
217228 }
218229 break ;
219230 default :
220231 {
221232 sendPlaying ( message , "URL" ) ;
222- playVideo ( link , "URL" ) ;
233+ playVideo ( message , link , "URL" ) ;
223234 }
224235 }
225236 }
@@ -234,16 +245,15 @@ streamer.client.on('messageCreate', async (message) => {
234245 }
235246
236247 try {
237- const [ ytUrlFromTitle , searchResults ] = await Promise . all ( [
238- ytPlayTitle ( title ) ,
239- yts . search ( title , { limit : 1 } )
240- ] ) ;
241-
248+ const searchResults = await yts . search ( title , { limit : 1 } ) ;
242249 const videoResult = searchResults [ 0 ] ;
243- if ( ytUrlFromTitle && videoResult ?. title ) {
244- sendPlaying ( message , videoResult . title ) ;
245- playVideo ( ytUrlFromTitle , videoResult . title ) ;
250+
251+ const searchResult = await youtube . searchAndGetPageUrl ( title ) ;
252+
253+ if ( searchResult . pageUrl && searchResult . title ) {
254+ playVideo ( message , searchResult . pageUrl , searchResult . title ) ;
246255 } else {
256+ logger . warn ( `No video found or title missing for search: "${ title } " using youtube.searchAndGetPageUrl.` ) ;
247257 throw new Error ( 'Could not find video' ) ;
248258 }
249259 } catch ( error ) {
@@ -419,54 +429,125 @@ streamer.client.on('messageCreate', async (message) => {
419429} ) ;
420430
421431// Function to play video
422- async function playVideo ( video : string , title ?: string ) {
423- logger . info ( "Started playing " + video ) ;
432+ async function playVideo ( message : Message , videoSource : string , title ?: string ) {
433+ logger . info ( `Attempting to play: ${ title || videoSource } ` ) ;
424434 const [ guildId , channelId , cmdChannelId ] = [ config . guildId , config . videoChannelId , config . cmdChannelId ! ] ;
425435
426- // Reset manual stop flag
427436 streamStatus . manualStop = false ;
428437
429- // Join voice channel
430- await streamer . joinVoice ( guildId , channelId )
431- streamStatus . joined = true ;
432- streamStatus . playing = true ;
433- streamStatus . channelInfo = {
434- guildId : guildId ,
435- channelId : channelId ,
436- cmdChannelId : cmdChannelId
437- }
438+ let inputForFfmpeg : any = videoSource ;
439+ let tempFilePath : string | null = null ;
440+ let downloadInProgressMessage : Message | null = null ;
441+ let isLiveYouTubeStream = false ;
438442
439443 try {
444+ if ( typeof videoSource === 'string' && ( videoSource . includes ( 'youtube.com/' ) || videoSource . includes ( 'youtu.be/' ) ) ) {
445+ const videoDetails = await youtube . getVideoInfo ( videoSource ) ;
446+
447+ if ( videoDetails ?. videoDetails ?. isLiveContent ) {
448+ isLiveYouTubeStream = true ;
449+ logger . info ( `YouTube video is live: ${ title || videoSource } .` ) ;
450+ const liveStreamUrl = await youtube . getLiveStreamUrl ( videoSource ) ;
451+ if ( liveStreamUrl ) {
452+ inputForFfmpeg = liveStreamUrl ;
453+ logger . info ( `Using direct live stream URL for ffmpeg: ${ liveStreamUrl } ` ) ;
454+ } else {
455+ logger . error ( `Failed to get live stream URL for ${ title || videoSource } . Falling back to download attempt or error.` ) ;
456+ await sendError ( message , `Failed to get live stream URL for \`${ title || 'YouTube live video' } \`.` ) ;
457+ await cleanupStreamStatus ( ) ;
458+ return ;
459+ }
460+ } else {
461+ downloadInProgressMessage = await message . reply ( `📥 Downloading \`${ title || 'YouTube video' } \`...` ) . catch ( e => {
462+ logger . warn ( "Failed to send 'Downloading...' message:" , e ) ;
463+ return null ;
464+ } ) ;
465+ logger . info ( `Downloading YouTube link with yt-dlp to temp file: ${ videoSource } ` ) ;
466+
467+ const ytDlpDownloadOptions : Parameters < typeof downloadToTempFile > [ 1 ] = {
468+ format : `bestvideo[height<=${ streamOpts . height || 720 } ][ext=mp4]+bestaudio[ext=m4a]/bestvideo[height<=${ streamOpts . height || 720 } ]+bestaudio/best[height<=${ streamOpts . height || 720 } ]/best` ,
469+ noPlaylist : true ,
470+ } ;
471+
472+ try {
473+ tempFilePath = await downloadToTempFile ( videoSource , ytDlpDownloadOptions ) ;
474+ inputForFfmpeg = tempFilePath ;
475+ logger . info ( `Using temp file for ffmpeg: ${ tempFilePath } ` ) ;
476+ if ( downloadInProgressMessage ) {
477+ await downloadInProgressMessage . delete ( ) . catch ( e => logger . warn ( "Failed to delete 'Downloading...' message:" , e ) ) ;
478+ }
479+ } catch ( downloadError ) {
480+ logger . error ( "Failed to download YouTube video:" , downloadError ) ;
481+ if ( downloadInProgressMessage ) {
482+ await downloadInProgressMessage . edit ( `❌ Failed to download \`${ title || 'YouTube video' } \`.` ) . catch ( e => logger . warn ( "Failed to edit 'Downloading...' message:" , e ) ) ;
483+ } else {
484+ await sendError ( message , `Failed to download video: ${ downloadError instanceof Error ? downloadError . message : String ( downloadError ) } ` ) ;
485+ }
486+ await cleanupStreamStatus ( ) ;
487+ return ;
488+ }
489+ }
490+ }
491+
492+ await streamer . joinVoice ( guildId , channelId ) ;
493+ streamStatus . joined = true ;
494+ streamStatus . playing = true ;
495+ streamStatus . channelInfo = { guildId, channelId, cmdChannelId } ;
496+
440497 if ( title ) {
441498 streamer . client . user ?. setActivity ( status_watch ( title ) as ActivityOptions ) ;
442499 }
500+ await sendPlaying ( message , title || videoSource ) ;
443501
444- // Abort any existing controller
445502 controller ?. abort ( ) ;
446503 controller = new AbortController ( ) ;
447504
448- const { command, output } = prepareStream ( video , streamOpts , controller . signal ) ;
505+ const { command, output : ffmpegOutput } = prepareStream ( inputForFfmpeg , streamOpts , controller . signal ) ;
449506
450- command . on ( "error" , ( err ) => {
451- logger . error ( "An error happened with ffmpeg" , err ) ;
507+ command . on ( "error" , ( err , stdout , stderr ) => {
508+ logger . error ( "An error happened with ffmpeg:" , err . message ) ;
509+ if ( stdout ) logger . error ( "ffmpeg stdout:" , stdout ) ;
510+ if ( stderr ) logger . error ( "ffmpeg stderr:" , stderr ) ;
511+ if ( ! controller . signal . aborted ) controller . abort ( ) ;
512+ } ) ;
513+
514+ command . on ( "end" , ( stdout , stderr ) => {
515+ logger . info ( `ffmpeg processing finished successfully for ${ title || videoSource } .` ) ;
452516 } ) ;
453517
454- await playStream ( output , streamer , undefined , controller . signal )
455- . catch ( ( ) => controller . abort ( ) ) ;
518+ await playStream ( ffmpegOutput , streamer , undefined , controller . signal )
519+ . catch ( ( err ) => {
520+ if ( ! controller . signal . aborted ) {
521+ logger . error ( 'playStream error:' , err ) ;
522+ }
523+ if ( ! controller . signal . aborted ) controller . abort ( ) ;
524+ } ) ;
525+
526+ if ( ! controller . signal . aborted ) {
527+ logger . info ( `Finished playing: ${ title || videoSource } ` ) ;
528+ }
456529
457- logger . info ( `Finished playing video: ${ video } ` ) ;
458530 } catch ( error ) {
459- logger . error ( " Error occurred while playing video:" , error ) ;
460- controller ?. abort ( ) ;
531+ logger . error ( ` Error in playVideo for ${ title || videoSource } :` , error ) ;
532+ if ( ! controller . signal . aborted ) controller ?. abort ( ) ;
461533 } finally {
462534 await cleanupStreamStatus ( ) ;
463- if ( ! streamStatus . manualStop ) {
535+ if ( tempFilePath && ! isLiveYouTubeStream ) {
536+ try {
537+ logger . info ( `Attempting to delete temp file: ${ tempFilePath } ` ) ;
538+ fs . unlinkSync ( tempFilePath ) ;
539+ logger . info ( `Successfully deleted temp file: ${ tempFilePath } ` ) ;
540+ } catch ( cleanupError ) {
541+ logger . error ( `Failed to delete temp file ${ tempFilePath } :` , cleanupError ) ;
542+ }
543+ }
544+ if ( ! streamStatus . manualStop && ! controller . signal . aborted ) {
464545 await sendFinishMessage ( ) ;
465546 }
466547 }
467548}
468549
469- // Function to cleanup stream status - updated
550+ // Function to cleanup stream status
470551async function cleanupStreamStatus ( ) {
471552 if ( streamStatus . manualStop ) {
472553 return ;
@@ -523,16 +604,6 @@ async function getTwitchStreamUrl(url: string): Promise<string | null> {
523604 }
524605}
525606
526- // Function to get video URL from YouTube
527- async function getVideoUrl ( videoUrl : string ) : Promise < string | null > {
528- return await youtube . getVideoUrl ( videoUrl ) ;
529- }
530-
531- // Function to play video from YouTube
532- async function ytPlayTitle ( title : string ) : Promise < string | null > {
533- return await youtube . searchAndPlay ( title ) ;
534- }
535-
536607// Function to search for videos on YouTube
537608async function ytSearch ( title : string ) : Promise < string [ ] > {
538609 return await youtube . search ( title ) ;
0 commit comments