diff options
author | Chocobozzz <me@florianbigard.com> | 2019-01-29 08:37:25 +0100 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2019-02-11 09:13:02 +0100 |
commit | 092092969633bbcf6d4891a083ea497a7d5c3154 (patch) | |
tree | 69e82fe4f60c444cca216830e96afe143a9dac71 /server/lib/hls.ts | |
parent | 4348a27d252a3349bafa7ef4859c0e2cf060c255 (diff) | |
download | PeerTube-092092969633bbcf6d4891a083ea497a7d5c3154.tar.gz PeerTube-092092969633bbcf6d4891a083ea497a7d5c3154.tar.zst PeerTube-092092969633bbcf6d4891a083ea497a7d5c3154.zip |
Add hls support on server
Diffstat (limited to 'server/lib/hls.ts')
-rw-r--r-- | server/lib/hls.ts | 110 |
1 files changed, 110 insertions, 0 deletions
diff --git a/server/lib/hls.ts b/server/lib/hls.ts new file mode 100644 index 000000000..10db6c3c3 --- /dev/null +++ b/server/lib/hls.ts | |||
@@ -0,0 +1,110 @@ | |||
1 | import { VideoModel } from '../models/video/video' | ||
2 | import { basename, dirname, join } from 'path' | ||
3 | import { HLS_PLAYLIST_DIRECTORY, CONFIG } from '../initializers' | ||
4 | import { outputJSON, pathExists, readdir, readFile, remove, writeFile, move } from 'fs-extra' | ||
5 | import { getVideoFileSize } from '../helpers/ffmpeg-utils' | ||
6 | import { sha256 } from '../helpers/core-utils' | ||
7 | import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' | ||
8 | import HLSDownloader from 'hlsdownloader' | ||
9 | import { logger } from '../helpers/logger' | ||
10 | import { parse } from 'url' | ||
11 | |||
12 | async function updateMasterHLSPlaylist (video: VideoModel) { | ||
13 | const directory = join(HLS_PLAYLIST_DIRECTORY, video.uuid) | ||
14 | const masterPlaylists: string[] = [ '#EXTM3U', '#EXT-X-VERSION:3' ] | ||
15 | const masterPlaylistPath = join(directory, VideoStreamingPlaylistModel.getMasterHlsPlaylistFilename()) | ||
16 | |||
17 | for (const file of video.VideoFiles) { | ||
18 | // If we did not generated a playlist for this resolution, skip | ||
19 | const filePlaylistPath = join(directory, VideoStreamingPlaylistModel.getHlsPlaylistFilename(file.resolution)) | ||
20 | if (await pathExists(filePlaylistPath) === false) continue | ||
21 | |||
22 | const videoFilePath = video.getVideoFilePath(file) | ||
23 | |||
24 | const size = await getVideoFileSize(videoFilePath) | ||
25 | |||
26 | const bandwidth = 'BANDWIDTH=' + video.getBandwidthBits(file) | ||
27 | const resolution = `RESOLUTION=${size.width}x${size.height}` | ||
28 | |||
29 | let line = `#EXT-X-STREAM-INF:${bandwidth},${resolution}` | ||
30 | if (file.fps) line += ',FRAME-RATE=' + file.fps | ||
31 | |||
32 | masterPlaylists.push(line) | ||
33 | masterPlaylists.push(VideoStreamingPlaylistModel.getHlsPlaylistFilename(file.resolution)) | ||
34 | } | ||
35 | |||
36 | await writeFile(masterPlaylistPath, masterPlaylists.join('\n') + '\n') | ||
37 | } | ||
38 | |||
39 | async function updateSha256Segments (video: VideoModel) { | ||
40 | const directory = join(HLS_PLAYLIST_DIRECTORY, video.uuid) | ||
41 | const files = await readdir(directory) | ||
42 | const json: { [filename: string]: string} = {} | ||
43 | |||
44 | for (const file of files) { | ||
45 | if (file.endsWith('.ts') === false) continue | ||
46 | |||
47 | const buffer = await readFile(join(directory, file)) | ||
48 | const filename = basename(file) | ||
49 | |||
50 | json[filename] = sha256(buffer) | ||
51 | } | ||
52 | |||
53 | const outputPath = join(directory, VideoStreamingPlaylistModel.getHlsSha256SegmentsFilename()) | ||
54 | await outputJSON(outputPath, json) | ||
55 | } | ||
56 | |||
57 | function downloadPlaylistSegments (playlistUrl: string, destinationDir: string, timeout: number) { | ||
58 | let timer | ||
59 | |||
60 | logger.info('Importing HLS playlist %s', playlistUrl) | ||
61 | |||
62 | const params = { | ||
63 | playlistURL: playlistUrl, | ||
64 | destination: CONFIG.STORAGE.TMP_DIR | ||
65 | } | ||
66 | const downloader = new HLSDownloader(params) | ||
67 | |||
68 | const hlsDestinationDir = join(CONFIG.STORAGE.TMP_DIR, dirname(parse(playlistUrl).pathname)) | ||
69 | |||
70 | return new Promise<string>(async (res, rej) => { | ||
71 | downloader.startDownload(err => { | ||
72 | clearTimeout(timer) | ||
73 | |||
74 | if (err) { | ||
75 | deleteTmpDirectory(hlsDestinationDir) | ||
76 | |||
77 | return rej(err) | ||
78 | } | ||
79 | |||
80 | move(hlsDestinationDir, destinationDir, { overwrite: true }) | ||
81 | .then(() => res()) | ||
82 | .catch(err => { | ||
83 | deleteTmpDirectory(hlsDestinationDir) | ||
84 | |||
85 | return rej(err) | ||
86 | }) | ||
87 | }) | ||
88 | |||
89 | timer = setTimeout(() => { | ||
90 | deleteTmpDirectory(hlsDestinationDir) | ||
91 | |||
92 | return rej(new Error('HLS download timeout.')) | ||
93 | }, timeout) | ||
94 | |||
95 | function deleteTmpDirectory (directory: string) { | ||
96 | remove(directory) | ||
97 | .catch(err => logger.error('Cannot delete path on HLS download error.', { err })) | ||
98 | } | ||
99 | }) | ||
100 | } | ||
101 | |||
102 | // --------------------------------------------------------------------------- | ||
103 | |||
104 | export { | ||
105 | updateMasterHLSPlaylist, | ||
106 | updateSha256Segments, | ||
107 | downloadPlaylistSegments | ||
108 | } | ||
109 | |||
110 | // --------------------------------------------------------------------------- | ||