Skip to content

Commit defcc7a

Browse files
committed
fix: Validate codec videos before streams
1 parent 5d14b74 commit defcc7a

3 files changed

Lines changed: 498 additions & 66 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "streamflow",
3-
"version": "2.2.1",
3+
"version": "2.2.2",
44
"description": "Cloud streaming solution with FFmpeg",
55
"main": "app.js",
66
"scripts": {

services/rotationService.js

Lines changed: 86 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ function getRedirectUri(user) {
1818

1919
let checkIntervalId = null;
2020
const activeRotationStreams = new Map();
21+
const failedRotationStarts = new Map();
2122
const loggedAlreadyRunning = new Set();
2223
const 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+
94126
async 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

218260
async 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

569627
module.exports = {

0 commit comments

Comments
 (0)