diff options
author | Chocobozzz <me@florianbigard.com> | 2020-11-06 09:09:36 +0100 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2020-11-09 15:33:04 +0100 |
commit | 3bc68dfd6183078fb56b53e24e74f889c85c4ae0 (patch) | |
tree | eac38175bf815227f39bf0327c7399b85b05ad08 | |
parent | f8c00564e7e66c7c9d65ea044a4c1485df0e4c7c (diff) | |
download | PeerTube-3bc68dfd6183078fb56b53e24e74f889c85c4ae0.tar.gz PeerTube-3bc68dfd6183078fb56b53e24e74f889c85c4ae0.tar.zst PeerTube-3bc68dfd6183078fb56b53e24e74f889c85c4ae0.zip |
Fix audio sync after saving replay
hls.js seems to not correctly handle audio gaps with fragmented mp4
(but can with a ts playlist)
-rw-r--r-- | client/package.json | 2 | ||||
-rw-r--r-- | client/src/app/shared/shared-video-miniature/video-actions-dropdown.component.ts | 4 | ||||
-rw-r--r-- | client/yarn.lock | 8 | ||||
-rw-r--r-- | server.ts | 4 | ||||
-rw-r--r-- | server/helpers/ffmpeg-utils.ts | 30 | ||||
-rw-r--r-- | server/lib/job-queue/handlers/video-live-ending.ts | 4 |
6 files changed, 27 insertions, 25 deletions
diff --git a/client/package.json b/client/package.json index cb6f77166..152f8445f 100644 --- a/client/package.json +++ b/client/package.json | |||
@@ -80,7 +80,7 @@ | |||
80 | "extract-text-webpack-plugin": "4.0.0-beta.0", | 80 | "extract-text-webpack-plugin": "4.0.0-beta.0", |
81 | "file-loader": "^6.0.0", | 81 | "file-loader": "^6.0.0", |
82 | "focus-visible": "^5.0.2", | 82 | "focus-visible": "^5.0.2", |
83 | "hls.js": "^0.14.9", | 83 | "hls.js": "^0.14.16", |
84 | "html-loader": "^1.0.0", | 84 | "html-loader": "^1.0.0", |
85 | "html-webpack-plugin": "^4.0.3", | 85 | "html-webpack-plugin": "^4.0.3", |
86 | "https-browserify": "^1.0.0", | 86 | "https-browserify": "^1.0.0", |
diff --git a/client/src/app/shared/shared-video-miniature/video-actions-dropdown.component.ts b/client/src/app/shared/shared-video-miniature/video-actions-dropdown.component.ts index 18b4a2f3c..321f7b09f 100644 --- a/client/src/app/shared/shared-video-miniature/video-actions-dropdown.component.ts +++ b/client/src/app/shared/shared-video-miniature/video-actions-dropdown.component.ts | |||
@@ -93,7 +93,7 @@ export class VideoActionsDropdownComponent implements OnChanges { | |||
93 | ngOnChanges () { | 93 | ngOnChanges () { |
94 | if (this.loaded) { | 94 | if (this.loaded) { |
95 | this.loaded = false | 95 | this.loaded = false |
96 | this.playlistAdd.reload() | 96 | if (this.playlistAdd) this.playlistAdd.reload() |
97 | } | 97 | } |
98 | 98 | ||
99 | this.buildActions() | 99 | this.buildActions() |
@@ -277,7 +277,7 @@ export class VideoActionsDropdownComponent implements OnChanges { | |||
277 | { | 277 | { |
278 | label: $localize`Display live information`, | 278 | label: $localize`Display live information`, |
279 | handler: ({ video }) => this.showLiveInfoModal(video), | 279 | handler: ({ video }) => this.showLiveInfoModal(video), |
280 | isDisplayed: () => this.isVideoLiveInfoAvailable(), | 280 | isDisplayed: () => this.displayOptions.liveInfo && this.isVideoLiveInfoAvailable(), |
281 | iconName: 'live' | 281 | iconName: 'live' |
282 | }, | 282 | }, |
283 | { | 283 | { |
diff --git a/client/yarn.lock b/client/yarn.lock index cdafe1458..d3603a4a9 100644 --- a/client/yarn.lock +++ b/client/yarn.lock | |||
@@ -5586,10 +5586,10 @@ hex-color-regex@^1.1.0: | |||
5586 | resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" | 5586 | resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" |
5587 | integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== | 5587 | integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== |
5588 | 5588 | ||
5589 | hls.js@^0.14.9: | 5589 | hls.js@^0.14.16: |
5590 | version "0.14.9" | 5590 | version "0.14.16" |
5591 | resolved "https://registry.yarnpkg.com/hls.js/-/hls.js-0.14.9.tgz#e85be87d94385ed9947155716578f7c568957d15" | 5591 | resolved "https://registry.yarnpkg.com/hls.js/-/hls.js-0.14.16.tgz#4ff68a1fa7260a43d316270e9bc7f7bdf93c5731" |
5592 | integrity sha512-5j1ONTvIzcIxCtg2eafikFbZ3b/9fDhR6u871LmK7jZ44/Qdc2G4xaSBCwcVK61gz7kTyiobaAhB++2M4J58rQ== | 5592 | integrity sha512-VACiO99DQFBpflR4fI+6GVHUZn35R0SGGQo0XTDZOm2BUXbeuDHTghTC/k2/3wGln6KBmG8/bXIcDIzDsY2UEg== |
5593 | dependencies: | 5593 | dependencies: |
5594 | eventemitter3 "^4.0.3" | 5594 | eventemitter3 "^4.0.3" |
5595 | url-toolkit "^2.1.6" | 5595 | url-toolkit "^2.1.6" |
@@ -142,14 +142,14 @@ if (isTestInstance()) { | |||
142 | } | 142 | } |
143 | 143 | ||
144 | // For the logger | 144 | // For the logger |
145 | morgan.token('remote-addr', req => { | 145 | morgan.token('remote-addr', (req: express.Request) => { |
146 | if (CONFIG.LOG.ANONYMIZE_IP === true || req.get('DNT') === '1') { | 146 | if (CONFIG.LOG.ANONYMIZE_IP === true || req.get('DNT') === '1') { |
147 | return anonymize(req.ip, 16, 16) | 147 | return anonymize(req.ip, 16, 16) |
148 | } | 148 | } |
149 | 149 | ||
150 | return req.ip | 150 | return req.ip |
151 | }) | 151 | }) |
152 | morgan.token('user-agent', req => { | 152 | morgan.token('user-agent', (req: express.Request) => { |
153 | if (req.get('DNT') === '1') { | 153 | if (req.get('DNT') === '1') { |
154 | return useragent.parse(req.get('user-agent')).family | 154 | return useragent.parse(req.get('user-agent')).family |
155 | } | 155 | } |
diff --git a/server/helpers/ffmpeg-utils.ts b/server/helpers/ffmpeg-utils.ts index 3b794b8a2..7a54b4642 100644 --- a/server/helpers/ffmpeg-utils.ts +++ b/server/helpers/ffmpeg-utils.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import * as ffmpeg from 'fluent-ffmpeg' | 1 | import * as ffmpeg from 'fluent-ffmpeg' |
2 | import { readFile, remove, writeFile } from 'fs-extra' | 2 | import { outputFile, readFile, remove, writeFile } from 'fs-extra' |
3 | import { dirname, join } from 'path' | 3 | import { dirname, join } from 'path' |
4 | import { VideoFileMetadata } from '@shared/models/videos/video-file-metadata' | 4 | import { VideoFileMetadata } from '@shared/models/videos/video-file-metadata' |
5 | import { getMaxBitrate, getTargetBitrate, VideoResolution } from '../../shared/models/videos' | 5 | import { getMaxBitrate, getTargetBitrate, VideoResolution } from '../../shared/models/videos' |
@@ -423,8 +423,14 @@ function runLiveMuxing (rtmpUrl: string, outPath: string, deleteSegments: boolea | |||
423 | } | 423 | } |
424 | 424 | ||
425 | async function hlsPlaylistToFragmentedMP4 (hlsDirectory: string, segmentFiles: string[], outputPath: string) { | 425 | async function hlsPlaylistToFragmentedMP4 (hlsDirectory: string, segmentFiles: string[], outputPath: string) { |
426 | const concatFile = 'concat.txt' | 426 | const concatFilePath = join(hlsDirectory, 'concat.txt') |
427 | const concatFilePath = join(hlsDirectory, concatFile) | 427 | |
428 | function cleaner () { | ||
429 | remove(concatFilePath) | ||
430 | .catch(err => logger.error('Cannot remove concat file in %s.', hlsDirectory, { err })) | ||
431 | } | ||
432 | |||
433 | // First concat the ts files to a mp4 file | ||
428 | const content = segmentFiles.map(f => 'file ' + f) | 434 | const content = segmentFiles.map(f => 'file ' + f) |
429 | .join('\n') | 435 | .join('\n') |
430 | 436 | ||
@@ -434,25 +440,25 @@ async function hlsPlaylistToFragmentedMP4 (hlsDirectory: string, segmentFiles: s | |||
434 | command.inputOption('-safe 0') | 440 | command.inputOption('-safe 0') |
435 | command.inputOption('-f concat') | 441 | command.inputOption('-f concat') |
436 | 442 | ||
437 | command.outputOption('-c copy') | 443 | command.outputOption('-c:v copy') |
444 | command.audioFilter('aresample=async=1:first_pts=0') | ||
438 | command.output(outputPath) | 445 | command.output(outputPath) |
439 | 446 | ||
440 | command.run() | 447 | return runCommand(command, cleaner) |
448 | } | ||
441 | 449 | ||
442 | function cleaner () { | 450 | async function runCommand (command: ffmpeg.FfmpegCommand, onEnd?: Function) { |
443 | remove(concatFilePath) | 451 | command.run() |
444 | .catch(err => logger.error('Cannot remove concat file in %s.', hlsDirectory, { err })) | ||
445 | } | ||
446 | 452 | ||
447 | return new Promise<string>((res, rej) => { | 453 | return new Promise<string>((res, rej) => { |
448 | command.on('error', err => { | 454 | command.on('error', err => { |
449 | cleaner() | 455 | if (onEnd) onEnd() |
450 | 456 | ||
451 | rej(err) | 457 | rej(err) |
452 | }) | 458 | }) |
453 | 459 | ||
454 | command.on('end', () => { | 460 | command.on('end', () => { |
455 | cleaner() | 461 | if (onEnd) onEnd() |
456 | 462 | ||
457 | res() | 463 | res() |
458 | }) | 464 | }) |
@@ -501,7 +507,7 @@ function addDefaultLiveHLSParams (command: ffmpeg.FfmpegCommand, outPath: string | |||
501 | command.outputOption('-hls_flags delete_segments') | 507 | command.outputOption('-hls_flags delete_segments') |
502 | } | 508 | } |
503 | 509 | ||
504 | command.outputOption(`-hls_segment_filename ${join(outPath, '%v-%d.ts')}`) | 510 | command.outputOption(`-hls_segment_filename ${join(outPath, '%v-%04d.ts')}`) |
505 | command.outputOption('-master_pl_name master.m3u8') | 511 | command.outputOption('-master_pl_name master.m3u8') |
506 | command.outputOption(`-f hls`) | 512 | command.outputOption(`-f hls`) |
507 | 513 | ||
diff --git a/server/lib/job-queue/handlers/video-live-ending.ts b/server/lib/job-queue/handlers/video-live-ending.ts index 2b900998a..3892260c4 100644 --- a/server/lib/job-queue/handlers/video-live-ending.ts +++ b/server/lib/job-queue/handlers/video-live-ending.ts | |||
@@ -70,10 +70,6 @@ async function saveLive (video: MVideo, live: MVideoLive) { | |||
70 | const segmentFiles = files.filter(f => f.startsWith(shouldStartWith) && f.endsWith('.ts')) | 70 | const segmentFiles = files.filter(f => f.startsWith(shouldStartWith) && f.endsWith('.ts')) |
71 | await hlsPlaylistToFragmentedMP4(hlsDirectory, segmentFiles, mp4TmpPath) | 71 | await hlsPlaylistToFragmentedMP4(hlsDirectory, segmentFiles, mp4TmpPath) |
72 | 72 | ||
73 | for (const file of segmentFiles) { | ||
74 | await remove(join(hlsDirectory, file)) | ||
75 | } | ||
76 | |||
77 | if (!duration) { | 73 | if (!duration) { |
78 | duration = await getDurationFromVideoFile(mp4TmpPath) | 74 | duration = await getDurationFromVideoFile(mp4TmpPath) |
79 | } | 75 | } |