Skip to content

Commit

Permalink
Replace ffmpeg with play-dl stream for non-caching streams
Browse files Browse the repository at this point in the history
  • Loading branch information
MarcoCoreDuo committed May 25, 2024
1 parent b0cdaaf commit f3c3406
Show file tree
Hide file tree
Showing 4 changed files with 32 additions and 83 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]

### Changed
- Switched ytdl-core to play-dl
- Switched ytdl-core to play-dl (should resolve issues with longer videos)

## [2.8.1] - 2024-04-28

Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,6 @@
"libsodium-wrappers": "^0.7.9",
"make-dir": "^3.1.0",
"node-emoji": "^1.10.0",
"node-fetch": "^3.3.2",
"nodesplash": "^0.1.1",
"ora": "^6.1.0",
"p-event": "^5.0.1",
Expand Down
73 changes: 31 additions & 42 deletions src/services/player.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import {VoiceChannel, Snowflake} from 'discord.js';
import {Readable} from 'stream';
import hasha from 'hasha';
import {InfoData, video_info} from 'play-dl';
import {InfoData, video_info, stream_from_info} from 'play-dl';
import {WriteStream} from 'fs-capacitor';
import ffmpeg from 'fluent-ffmpeg';
import shuffle from 'array-shuffle';
import fetch from 'node-fetch';
import {
AudioPlayer,
AudioPlayerState,
Expand Down Expand Up @@ -76,6 +75,8 @@ export default class {
private nowPlaying: QueuedSong | null = null;
private playPositionInterval: NodeJS.Timeout | undefined;
private lastSongURL = '';
private type: StreamType | undefined;
private loudness: number | undefined;

private positionInSeconds = 0;
private readonly fileCache: FileCacheProvider;
Expand Down Expand Up @@ -444,49 +445,40 @@ export default class {
// Not yet cached, must download
const info = await video_info(song.url);

if (info.LiveStreamData.isLive) {
const hlsUrl = info.LiveStreamData.hlsManifestUrl;
const MAX_CACHE_LENGTH_SECONDS = 30 * 60; // 30 minutes
// Don't cache livestreams or long videos
shouldCacheVideo = !info.video_details.live && info.video_details.durationInSec < MAX_CACHE_LENGTH_SECONDS && !options.seek;

if (hlsUrl === null) {
throw new Error('No HLS manifest URL found.');
if (!shouldCacheVideo) {
const stream = await stream_from_info(info, {seek: options.seek});
debug('Not caching video');
debug('Spawned play-dl stream');
if (!info.video_details.live) {
this.loudness = info.format[info.format.length - 1].loudnessDb;
this.loudness = this.loudness ? 2 ** (-this.loudness / 10) : 1;
debug('Loudness:', this.loudness);
}

const audioBitrates: Record<number, number> = {128: 96, 127: 96, 120: 128, 96: 256, 95: 256, 94: 128, 93: 128};
debug('Audio format:', stream.type);
this.type = stream.type;

let formats: Array<{itag: number; url: string; audioBitrate?: number; loudnessDb: undefined}> = [];
return stream.stream;
}

const m3u8_data = await fetch(hlsUrl).then(async res => res.text());
format = info.format.at(info.format.length - 1);

m3u8_data
.split('\n')
.filter(line => /^https?:\/\//.test(line))
.forEach(line => {
let itag: RegExpExecArray | number | null = /\/itag\/(\d+)\//.exec(line);
if (itag !== null) {
itag = parseInt(itag[1], 10);
formats.unshift({itag, url: line, audioBitrate: audioBitrates[itag], loudnessDb: undefined});
if (format?.mimeType?.slice(0, 5) !== 'audio') { // Legacy video
const formats = info.format
.filter(format => format.averageBitrate)
.sort((a, b) => {
if (a && b) {
return b.averageBitrate! - a.averageBitrate!;
}
});

formats = formats.sort((a, b) => (b as unknown as {audioBitrate: number}).audioBitrate - (a as unknown as {audioBitrate: number}).audioBitrate);

format = formats.find(format => [128, 127, 120, 96, 95, 94, 93].includes(format.itag));
} else {
format = info.format.at(info.format.length - 1);

if (format?.mimeType?.slice(0, 5) !== 'audio') { // Legacy video
const formats = info.format
.filter(format => format.averageBitrate)
.sort((a, b) => {
if (a && b) {
return b.averageBitrate! - a.averageBitrate!;
}

return 0;
});
return 0;
});

format = formats.find(format => !format.bitrate) ?? formats[0];
}
format = formats.find(format => !format.bitrate) ?? formats[0];
}

if (!format) {
Expand All @@ -497,10 +489,6 @@ export default class {
debug('Using format', format);
ffmpegInput = format.url!;

// Don't cache livestreams or long videos
const MAX_CACHE_LENGTH_SECONDS = 30 * 60; // 30 minutes
shouldCacheVideo = !info.video_details.live && info.video_details.durationInSec < MAX_CACHE_LENGTH_SECONDS && !options.seek;

debug(shouldCacheVideo ? 'Caching video' : 'Not caching video');

ffmpegInputOptions.push(...[
Expand Down Expand Up @@ -643,7 +631,7 @@ export default class {

private createAudioStream(stream: Readable) {
return createAudioResource(stream, {
inputType: StreamType.WebmOpus,
inputType: this.type ?? StreamType.WebmOpus,
inlineVolume: true,
});
}
Expand All @@ -658,6 +646,7 @@ export default class {

private setAudioPlayerVolume(level?: number) {
// Audio resource expects a float between 0 and 1 to represent level percentage
this.audioResource?.volume?.setVolume((level ?? this.getVolume()) / 100);
this.loudness = this.loudness ?? 1;
this.audioResource?.volume?.setVolume((level ? level * this.loudness : (this.getVolume()) / 100) * this.loudness);
}
}
39 changes: 0 additions & 39 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1249,11 +1249,6 @@ data-uri-to-buffer@3:
resolved "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz"
integrity sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==

data-uri-to-buffer@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz#d8feb2b2881e6a4f58c2e08acfd0e2834e26222e"
integrity sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==

data-view-buffer@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.1.tgz#8ea6326efec17a2e42620696e671d7d5a8bc66b2"
Expand Down Expand Up @@ -2077,14 +2072,6 @@ fastq@^1.6.0:
dependencies:
reusify "^1.0.4"

fetch-blob@^3.1.2, fetch-blob@^3.1.4:
version "3.2.0"
resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.2.0.tgz#f09b8d4bbd45adc6f0c20b7e787e793e309dcce9"
integrity sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==
dependencies:
node-domexception "^1.0.0"
web-streams-polyfill "^3.0.3"

figures@^3.0.0:
version "3.2.0"
resolved "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz"
Expand Down Expand Up @@ -2212,13 +2199,6 @@ form-data@^3.0.0:
combined-stream "^1.0.8"
mime-types "^2.1.12"

formdata-polyfill@^4.0.10:
version "4.0.10"
resolved "https://registry.yarnpkg.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz#24807c31c9d402e002ab3d8c720144ceb8848423"
integrity sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==
dependencies:
fetch-blob "^3.1.2"

formidable@^1.2.2:
version "1.2.6"
resolved "https://registry.npmjs.org/formidable/-/formidable-1.2.6.tgz"
Expand Down Expand Up @@ -3474,11 +3454,6 @@ node-addon-api@^5.0.0:
resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.0.0.tgz"
integrity sha512-CvkDw2OEnme7ybCykJpVcKH+uAOLV2qLqiyla128dN9TkEWfrYmxG6C2boDe5KcNQqZF3orkqzGgOMvZ/JNekA==

node-domexception@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5"
integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==

node-emoji@^1.10.0:
version "1.11.0"
resolved "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz"
Expand All @@ -3493,15 +3468,6 @@ node-fetch@^2.6.1, node-fetch@^2.6.7:
dependencies:
whatwg-url "^5.0.0"

node-fetch@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.3.2.tgz#d1e889bacdf733b4ff3b2b243eb7a12866a0b78b"
integrity sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==
dependencies:
data-uri-to-buffer "^4.0.0"
fetch-blob "^3.1.4"
formdata-polyfill "^4.0.10"

nodesplash@^0.1.1:
version "0.1.1"
resolved "https://registry.npmjs.org/nodesplash/-/nodesplash-0.1.1.tgz"
Expand Down Expand Up @@ -5092,11 +5058,6 @@ wcwidth@^1.0.1:
dependencies:
defaults "^1.0.3"

web-streams-polyfill@^3.0.3:
version "3.3.3"
resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz#2073b91a2fdb1fbfbd401e7de0ac9f8214cecb4b"
integrity sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==

webidl-conversions@^3.0.0:
version "3.0.1"
resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz"
Expand Down

0 comments on commit f3c3406

Please sign in to comment.