@@ -18,6 +18,7 @@ function getRedirectUri(user) {
1818
1919let checkIntervalId = null ;
2020const activeRotationStreams = new Map ( ) ;
21+ const failedRotationStarts = new Map ( ) ;
2122const loggedAlreadyRunning = new Set ( ) ;
2223const loggedScheduleInfo = new Set ( ) ;
2324
@@ -91,6 +92,37 @@ function init() {
9192 checkRotations ( ) ;
9293}
9394
95+ async function moveRotationToNextScheduledItem ( rotation , items , currentIndex , reason ) {
96+ const nextIndex = currentIndex + 1 ;
97+
98+ if ( nextIndex >= items . length ) {
99+ if ( rotation . repeat_mode && rotation . repeat_mode !== 'none' ) {
100+ const nextSchedule = getNextSchedule ( rotation ) ;
101+
102+ await Rotation . update ( rotation . id , {
103+ current_index : 0 ,
104+ start_time : formatLocalDateTime ( nextSchedule . start ) ,
105+ end_time : formatLocalDateTime ( nextSchedule . end )
106+ } ) ;
107+ console . log ( `[RotationService] ${ reason } Rotation ${ rotation . name } diulang dari item pertama pada ${ formatLocalDateTime ( nextSchedule . start ) } ` ) ;
108+ } else {
109+ await Rotation . update ( rotation . id , { status : 'completed' } ) ;
110+ console . log ( `[RotationService] ${ reason } Rotation ${ rotation . name } selesai` ) ;
111+ }
112+
113+ return ;
114+ }
115+
116+ const nextSchedule = getNextSchedule ( rotation ) ;
117+
118+ await Rotation . update ( rotation . id , {
119+ current_index : nextIndex ,
120+ start_time : formatLocalDateTime ( nextSchedule . start ) ,
121+ end_time : formatLocalDateTime ( nextSchedule . end )
122+ } ) ;
123+ console . log ( `[RotationService] ${ reason } Lanjut ke item ${ nextIndex + 1 } /${ items . length } pada ${ formatLocalDateTime ( nextSchedule . start ) } ` ) ;
124+ }
125+
94126async function checkRotations ( ) {
95127 try {
96128 const activeRotations = await Rotation . findActiveRotations ( ) ;
@@ -114,6 +146,7 @@ async function checkRotations() {
114146 activeRotationStreams . delete ( streamKey ) ;
115147 loggedAlreadyRunning . delete ( streamKey ) ;
116148 }
149+ failedRotationStarts . delete ( streamKey ) ;
117150 }
118151
119152 if ( rotation . repeat_mode && rotation . repeat_mode !== 'none' ) {
@@ -156,33 +189,25 @@ async function checkRotations() {
156189 activeRotationStreams . delete ( streamKey ) ;
157190 loggedAlreadyRunning . delete ( streamKey ) ;
158191 }
192+ failedRotationStarts . delete ( streamKey ) ;
159193 }
160194
161195 const nextIndex = currentIndex + 1 ;
162196
163197 if ( nextIndex >= items . length ) {
164- if ( rotation . repeat_mode && rotation . repeat_mode !== 'none' ) {
165- const nextSchedule = getNextSchedule ( rotation ) ;
166-
167- await Rotation . update ( rotation . id , {
168- current_index : 0 ,
169- start_time : formatLocalDateTime ( nextSchedule . start ) ,
170- end_time : formatLocalDateTime ( nextSchedule . end )
171- } ) ;
172- console . log ( `[RotationService] All items completed. Rotation ${ rotation . name } rescheduled for ${ formatLocalDateTime ( nextSchedule . start ) } ` ) ;
173- } else {
174- await Rotation . update ( rotation . id , { status : 'completed' } ) ;
175- console . log ( `[RotationService] All items completed. Rotation ${ rotation . name } marked as completed` ) ;
176- }
198+ await moveRotationToNextScheduledItem (
199+ rotation ,
200+ items ,
201+ currentIndex ,
202+ 'Semua item di slot hari ini selesai.'
203+ ) ;
177204 } else {
178- const nextSchedule = getNextSchedule ( rotation ) ;
179-
180- await Rotation . update ( rotation . id , {
181- current_index : nextIndex ,
182- start_time : formatLocalDateTime ( nextSchedule . start ) ,
183- end_time : formatLocalDateTime ( nextSchedule . end )
184- } ) ;
185- console . log ( `[RotationService] Moving to next item (${ nextIndex + 1 } /${ items . length } ). Rotation ${ rotation . name } rescheduled for ${ formatLocalDateTime ( nextSchedule . start ) } ` ) ;
205+ await moveRotationToNextScheduledItem (
206+ rotation ,
207+ items ,
208+ currentIndex ,
209+ 'Slot hari ini berakhir.'
210+ ) ;
186211 }
187212 continue ;
188213 }
@@ -191,16 +216,33 @@ async function checkRotations() {
191216 if ( ! currentItem ) continue ;
192217
193218 const streamKey = `${ rotation . id } _${ currentItem . id } ` ;
219+ const windowKey = `${ rotation . start_time } |${ rotation . end_time } |${ currentIndex } ` ;
220+
221+ if ( failedRotationStarts . get ( streamKey ) === windowKey ) {
222+ continue ;
223+ }
194224
195225 if ( ! activeRotationStreams . has ( streamKey ) ) {
196226 console . log ( `[RotationService] Starting rotation item ${ currentIndex + 1 } /${ items . length } : ${ currentItem . title } ` ) ;
197227 const result = await startRotationStream ( rotation , currentItem ) ;
198228 if ( result . success ) {
229+ failedRotationStarts . delete ( streamKey ) ;
199230 activeRotationStreams . set ( streamKey , {
200231 rotationId : rotation . id ,
201232 itemId : currentItem . id ,
202233 streamId : result . streamId
203234 } ) ;
235+ } else if ( result . code === 'UNSUPPORTED_COPY_MODE_MEDIA' ) {
236+ failedRotationStarts . delete ( streamKey ) ;
237+ await moveRotationToNextScheduledItem (
238+ rotation ,
239+ items ,
240+ currentIndex ,
241+ `Item "${ currentItem . title } " di-skip karena media tidak kompatibel dengan copy mode YouTube.`
242+ ) ;
243+ } else {
244+ failedRotationStarts . set ( streamKey , windowKey ) ;
245+ console . error ( `[RotationService] Failed to start item ${ currentIndex + 1 } /${ items . length } : ${ result . error } ` ) ;
204246 }
205247 loggedAlreadyRunning . delete ( streamKey ) ;
206248 } else {
@@ -217,6 +259,17 @@ async function checkRotations() {
217259
218260async function startRotationStream ( rotation , item ) {
219261 try {
262+ let actualVideoId = item . video_id ;
263+ if ( item . video_id && item . video_id . startsWith ( 'playlist:' ) ) {
264+ actualVideoId = item . video_id . substring ( 9 ) ;
265+ }
266+
267+ await streamingService . validateCopyModeCompatibilityForInput ( {
268+ videoId : actualVideoId ,
269+ useAdvancedSettings : false ,
270+ isYouTubeApi : true
271+ } ) ;
272+
220273 const user = await User . findById ( rotation . user_id ) ;
221274 if ( ! user ) {
222275 console . error ( '[RotationService] User not found' ) ;
@@ -354,11 +407,6 @@ async function startRotationStream(rotation, item) {
354407 }
355408 }
356409
357- let actualVideoId = item . video_id ;
358- if ( item . video_id && item . video_id . startsWith ( 'playlist:' ) ) {
359- actualVideoId = item . video_id . substring ( 9 ) ;
360- }
361-
362410 const stream = await Stream . create ( {
363411 title : item . title ,
364412 video_id : actualVideoId ,
@@ -383,12 +431,19 @@ async function startRotationStream(rotation, item) {
383431 end_time : rotation . end_time
384432 } ) ;
385433
386- await streamingService . startStream ( stream . id ) ;
434+ const startResult = await streamingService . startStream ( stream . id ) ;
435+ if ( ! startResult . success ) {
436+ return {
437+ success : false ,
438+ error : startResult . error ,
439+ code : startResult . code || null
440+ } ;
441+ }
387442
388443 return { success : true , streamId : stream . id , broadcastId : broadcast . id } ;
389444 } catch ( error ) {
390445 console . error ( '[RotationService] Error starting rotation stream:' , error ) ;
391- return { success : false , error : error . message } ;
446+ return { success : false , error : error . message , code : error . code || null } ;
392447 }
393448}
394449
@@ -518,6 +573,7 @@ async function pauseRotation(rotationId) {
518573 activeRotationStreams . delete ( streamKey ) ;
519574 loggedAlreadyRunning . delete ( streamKey ) ;
520575 }
576+ failedRotationStarts . delete ( streamKey ) ;
521577 }
522578
523579 await Rotation . update ( rotationId , { status : 'paused' } ) ;
@@ -540,6 +596,7 @@ async function stopRotation(rotationId) {
540596 activeRotationStreams . delete ( streamKey ) ;
541597 loggedAlreadyRunning . delete ( streamKey ) ;
542598 }
599+ failedRotationStarts . delete ( streamKey ) ;
543600 }
544601
545602 await Rotation . update ( rotationId , { status : 'inactive' , current_index : 0 } ) ;
@@ -564,6 +621,7 @@ function shutdown() {
564621 } ) ;
565622 }
566623 activeRotationStreams . clear ( ) ;
624+ failedRotationStarts . clear ( ) ;
567625}
568626
569627module . exports = {
0 commit comments