-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrecord.js
132 lines (112 loc) · 3.31 KB
/
record.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
const hls = require("hls-parser");
const logger = require("./logger");
const pathJoin = require("path").join;
const mkdirp = require("mkdirp");
const config = require("./config");
const notify = require("./notify");
const { axiosGet, sleep, downloadWithRetry } = require("./common");
const { getPlayUrl, isLive } = require("./api");
const db = require("./db");
async function loadAndParse(url) {
let res = await axiosGet(url);
let playlist = hls.parse(res.data);
return playlist;
}
async function resolvePlaylist(url) {
let playlist = await loadAndParse(url);
if (playlist.segments) {
return url;
}
if (playlist.variants) {
let url = playlist.variants[0].uri;
logger.info("Switching playlist URL to " + url);
return resolvePlaylist(url);
}
throw new Error("Got invalid playlist");
}
async function record(room, recordDoc, url) {
let lastSeqNumber = 0;
let dirName = `${room.roomId}_${new Date().toISOString().replace(/:/g, "_")}`;
let dirPath = pathJoin(config.dataDir, "records", dirName);
await mkdirp(dirPath);
await db.recordsDao.update(recordDoc._id, {
dirName: dirName,
});
while (true) {
let playlist = await loadAndParse(url);
let host = new URL(url).host;
for (const segment of playlist.segments) {
const seqNumber = segment.mediaSequenceNumber;
if (seqNumber > lastSeqNumber) {
if (seqNumber - lastSeqNumber > 1) {
logger.warn(`Skipped ${seqNumber - lastSeqNumber} segments!`);
}
let segmentUrl = `https://${host}${segment.uri}`;
logger.debug(`[${seqNumber}] ${segmentUrl}`);
lastSeqNumber = seqNumber;
let destPath = pathJoin(dirPath, `${seqNumber}_${segment.duration}.ts`);
if (!config.noDownload) {
downloadWithRetry(segmentUrl, destPath).then(
() => {
logger.info(`Downloaded [${seqNumber}] => ${destPath}`);
},
(err) => {
logger.error(`Failed to download [${seqNumber}]`, err);
}
);
} else {
logger.info(
`Should download [${seqNumber}] => ${destPath}, but skipping`
);
}
}
}
if (playlist.endlist) {
break;
} else {
lastPlaylist = playlist;
await sleep(playlist.targetDuration * 1000);
}
}
}
async function recordLoop(room) {
let url = await resolvePlaylist(await getPlayUrl(room.roomId));
let recordDoc = {
roomId: room.roomId,
title: room.title,
startTime: new Date(),
playlistUrl: url,
};
recordDoc = await db.recordsDao.create(recordDoc);
await db.roomsDao.update(room.roomId, {
recording: true,
});
notify.sendStart(room);
try {
await record(room, recordDoc, url);
notify.sendEnd(room);
} catch (error) {
await db.recordsDao.update(recordDoc._id, {
error: error.stack || error.toString(),
});
notify.sendError(room, error);
throw error;
} finally {
await db.recordsDao.update(recordDoc._id, {
endTime: new Date(),
});
await db.roomsDao.update(room.roomId, {
recording: false,
});
}
}
exports.startRecord = async function (room) {
do {
try {
await recordLoop(room);
} catch (error) {
logger.error("Record loop failed", error);
}
await sleep(5 * 1000);
} while (await isLive(room.roomId));
};