]>
Commit | Line | Data |
---|---|---|
49b80bd9 | 1 | import { rename, writeJson } from 'fs-extra' |
be3ec61a | 2 | import PQueue from 'p-queue' |
8ebf2a5d | 3 | import { basename } from 'path' |
cfd57d2c | 4 | import { mapToJSON } from '@server/helpers/core-utils' |
8ebf2a5d | 5 | import { logger, loggerTagsFactory } from '@server/helpers/logger' |
cfd57d2c | 6 | import { MStreamingPlaylistVideo } from '@server/types/models' |
8ebf2a5d | 7 | import { buildSha256Segment } from '../hls' |
cfd57d2c | 8 | import { storeHLSFileFromPath } from '../object-storage' |
8ebf2a5d C |
9 | |
10 | const lTags = loggerTagsFactory('live') | |
11 | ||
12 | class LiveSegmentShaStore { | |
13 | ||
cfd57d2c C |
14 | private readonly segmentsSha256 = new Map<string, string>() |
15 | ||
16 | private readonly videoUUID: string | |
be3ec61a | 17 | |
cfd57d2c | 18 | private readonly sha256Path: string |
be3ec61a C |
19 | private readonly sha256PathTMP: string |
20 | ||
cfd57d2c C |
21 | private readonly streamingPlaylist: MStreamingPlaylistVideo |
22 | private readonly sendToObjectStorage: boolean | |
9ab330b9 | 23 | private readonly writeQueue = new PQueue({ concurrency: 1 }) |
cfd57d2c C |
24 | |
25 | constructor (options: { | |
26 | videoUUID: string | |
27 | sha256Path: string | |
28 | streamingPlaylist: MStreamingPlaylistVideo | |
29 | sendToObjectStorage: boolean | |
30 | }) { | |
31 | this.videoUUID = options.videoUUID | |
be3ec61a | 32 | |
cfd57d2c | 33 | this.sha256Path = options.sha256Path |
be3ec61a C |
34 | this.sha256PathTMP = options.sha256Path + '.tmp' |
35 | ||
cfd57d2c C |
36 | this.streamingPlaylist = options.streamingPlaylist |
37 | this.sendToObjectStorage = options.sendToObjectStorage | |
8ebf2a5d C |
38 | } |
39 | ||
cfd57d2c C |
40 | async addSegmentSha (segmentPath: string) { |
41 | logger.debug('Adding live sha segment %s.', segmentPath, lTags(this.videoUUID)) | |
8ebf2a5d C |
42 | |
43 | const shaResult = await buildSha256Segment(segmentPath) | |
44 | ||
cfd57d2c C |
45 | const segmentName = basename(segmentPath) |
46 | this.segmentsSha256.set(segmentName, shaResult) | |
8ebf2a5d | 47 | |
9ab330b9 C |
48 | try { |
49 | await this.writeToDisk() | |
50 | } catch (err) { | |
51 | logger.error('Cannot write sha segments to disk.', { err }) | |
52 | } | |
8ebf2a5d C |
53 | } |
54 | ||
cfd57d2c | 55 | async removeSegmentSha (segmentPath: string) { |
8ebf2a5d C |
56 | const segmentName = basename(segmentPath) |
57 | ||
cfd57d2c | 58 | logger.debug('Removing live sha segment %s.', segmentPath, lTags(this.videoUUID)) |
8ebf2a5d | 59 | |
cfd57d2c | 60 | if (!this.segmentsSha256.has(segmentName)) { |
0c9668f7 C |
61 | logger.warn( |
62 | 'Unknown segment in live segment hash store for video %s and segment %s.', | |
63 | this.videoUUID, segmentPath, lTags(this.videoUUID) | |
64 | ) | |
8ebf2a5d C |
65 | return |
66 | } | |
67 | ||
cfd57d2c | 68 | this.segmentsSha256.delete(segmentName) |
8ebf2a5d | 69 | |
cfd57d2c | 70 | await this.writeToDisk() |
8ebf2a5d C |
71 | } |
72 | ||
9ab330b9 C |
73 | private writeToDisk () { |
74 | return this.writeQueue.add(async () => { | |
49b80bd9 | 75 | // Atomic write: use rename instead of move that is not atomic |
be3ec61a | 76 | await writeJson(this.sha256PathTMP, mapToJSON(this.segmentsSha256)) |
49b80bd9 | 77 | await rename(this.sha256PathTMP, this.sha256Path) |
8ebf2a5d | 78 | |
9ab330b9 C |
79 | if (this.sendToObjectStorage) { |
80 | const url = await storeHLSFileFromPath(this.streamingPlaylist, this.sha256Path) | |
cfd57d2c | 81 | |
9ab330b9 C |
82 | if (this.streamingPlaylist.segmentsSha256Url !== url) { |
83 | this.streamingPlaylist.segmentsSha256Url = url | |
84 | await this.streamingPlaylist.save() | |
85 | } | |
cfd57d2c | 86 | } |
9ab330b9 | 87 | }) |
8ebf2a5d C |
88 | } |
89 | } | |
90 | ||
91 | export { | |
92 | LiveSegmentShaStore | |
93 | } |