djs-selfbot-v13 3.7.34 → 3.7.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "djs-selfbot-v13",
3
- "version": "3.7.34",
3
+ "version": "3.7.35",
4
4
  "description": "An unofficial discord.js fork for creating selfbots",
5
5
  "main": "./src/index.js",
6
6
  "types": "./typings/index.d.ts",
@@ -53,14 +53,44 @@ class WebRtcStreamSession extends EventEmitter {
53
53
  this._abort = new AbortController();
54
54
  this._playPromise = null;
55
55
  this._command = null;
56
+ this._ffmpegProc = null;
56
57
  this._started = false;
58
+ this._running = false;
59
+ this._paused = false;
60
+ this._stopped = false;
61
+ this._positionMs = 0;
62
+ this._runStartWall = 0;
63
+ this._pausedAt = 0;
64
+ this._pausedTotalMs = 0;
65
+ }
66
+
67
+ _getPositionMs() {
68
+ if (!this._running) return this._positionMs;
69
+ if (this._paused) return this._positionMs;
70
+ return this._positionMs + (Date.now() - this._runStartWall - this._pausedTotalMs);
71
+ }
72
+
73
+ _resetRunClock() {
74
+ this._runStartWall = Date.now();
75
+ this._pausedAt = 0;
76
+ this._pausedTotalMs = 0;
77
+ }
78
+
79
+ _signalFfmpeg(signal) {
80
+ const pid = this._ffmpegProc?.pid;
81
+ if (!pid) return;
82
+ try {
83
+ process.kill(pid, signal);
84
+ } catch {
85
+ // process already exited
86
+ }
57
87
  }
58
88
 
59
89
  /**
60
90
  * Construit la commande ffmpeg : transcode en H264 + Opus, sortie nut.
61
91
  * Pas de filtre azmq (compatibilité ffmpeg sans libzmq).
62
92
  */
63
- _buildFfmpeg(url) {
93
+ _buildFfmpeg(url, seekSec = 0) {
64
94
  const {
65
95
  fps,
66
96
  height = 720,
@@ -78,6 +108,8 @@ class WebRtcStreamSession extends EventEmitter {
78
108
  const output = new PassThrough();
79
109
  const command = ffmpeg(url);
80
110
 
111
+ if (seekSec > 0) command.seekInput(seekSec);
112
+
81
113
  const isHttp = typeof url === 'string' && /^https?:\/\//i.test(url);
82
114
  if (isHttp) {
83
115
  command.inputOptions([
@@ -123,13 +155,21 @@ class WebRtcStreamSession extends EventEmitter {
123
155
  return { command, output };
124
156
  }
125
157
 
126
- async _run(url, signal) {
158
+ async _run(url, signal, seekSec = 0) {
127
159
  await ensureFfmpegPath();
128
160
 
129
- const { command, output } = this._buildFfmpeg(url);
161
+ const { command, output } = this._buildFfmpeg(url, seekSec);
130
162
  this._command = command;
163
+ this._ffmpegProc = null;
164
+ this._running = true;
165
+ this._paused = false;
166
+ this._stopped = false;
167
+ this._resetRunClock();
131
168
 
132
- command.on('start', cmd => this.emit('debug', `[cmd] ${cmd}`));
169
+ command.on('start', cmd => {
170
+ this._ffmpegProc = command.ffmpegProc ?? null;
171
+ this.emit('debug', `[cmd] ${cmd}`);
172
+ });
133
173
  command.on('stderr', line => this.emit('debug', String(line)));
134
174
 
135
175
  command.on('error', (err, _stdout, stderr) => {
@@ -139,7 +179,12 @@ class WebRtcStreamSession extends EventEmitter {
139
179
  this.emit('error', new Error(stderr ? `${msg}\n${stderr}` : msg));
140
180
  });
141
181
 
142
- signal.addEventListener('abort', () => command.kill('SIGTERM'), { once: true });
182
+ const onAbort = () => {
183
+ this._running = false;
184
+ this._ffmpegProc = null;
185
+ command.kill('SIGTERM');
186
+ };
187
+ signal.addEventListener('abort', onAbort, { once: true });
143
188
  command.run();
144
189
 
145
190
  const { livestream } = this.options;
@@ -149,8 +194,17 @@ class WebRtcStreamSession extends EventEmitter {
149
194
  this._playPromise = playStream(output, this.streamer, playOptions, signal);
150
195
 
151
196
  this._playPromise
152
- .then(() => this.emit('finish'))
197
+ .then(() => {
198
+ this._running = false;
199
+ this._ffmpegProc = null;
200
+ if (!signal.aborted) {
201
+ this._positionMs = 0;
202
+ this.emit('finish');
203
+ }
204
+ })
153
205
  .catch(err => {
206
+ this._running = false;
207
+ this._ffmpegProc = null;
154
208
  if (signal.aborted || err?.name === 'AbortError') return;
155
209
  this.emit('error', err);
156
210
  });
@@ -171,14 +225,56 @@ class WebRtcStreamSession extends EventEmitter {
171
225
  void this._run(url, this._abort.signal);
172
226
  }
173
227
 
228
+ /**
229
+ * Met en pause la lecture (ffmpeg suspendu, connexion vocale conservée).
230
+ */
231
+ pause() {
232
+ if (!this._running || this._paused || this._stopped) return;
233
+ this._positionMs = this._getPositionMs();
234
+ this._pausedAt = Date.now();
235
+ this._signalFfmpeg('SIGSTOP');
236
+ this._paused = true;
237
+ }
238
+
239
+ /**
240
+ * Reprend la lecture (après pause ou stop).
241
+ */
242
+ async resume() {
243
+ if (this._stopped) {
244
+ const seekSec = this._positionMs / 1000;
245
+ this._stopped = false;
246
+ await this._run(this.options.url, this._abort.signal, seekSec);
247
+ return;
248
+ }
249
+ if (!this._running || !this._paused) return;
250
+ this._pausedTotalMs += Date.now() - this._pausedAt;
251
+ this._pausedAt = 0;
252
+ this._signalFfmpeg('SIGCONT');
253
+ this._paused = false;
254
+ }
255
+
256
+ /**
257
+ * Arrête la lecture en conservant la position et la connexion vocale.
258
+ */
174
259
  stop() {
260
+ if (!this._running) return;
261
+ this._positionMs = this._getPositionMs();
262
+ this._stopped = true;
263
+ this._paused = false;
264
+ this._running = false;
265
+ this._ffmpegProc = null;
175
266
  this._abort.abort();
267
+ this._abort = new AbortController();
176
268
  }
177
269
 
270
+ /**
271
+ * Relance depuis la dernière position enregistrée.
272
+ */
178
273
  async replay() {
179
- this.stop();
180
- this._abort = new AbortController();
181
- await this._run(this.options.url, this._abort.signal);
274
+ const seekSec = (this._running ? this._getPositionMs() : this._positionMs) / 1000;
275
+ if (this._running) this.stop();
276
+ this._stopped = false;
277
+ await this._run(this.options.url, this._abort.signal, seekSec);
182
278
  }
183
279
 
184
280
  disconnect() {
@@ -918,7 +918,7 @@ export class Client<Ready extends boolean = boolean> extends BaseClient {
918
918
  }
919
919
  >
920
920
  >;
921
- public startStream(options: StartStreamOptions): Promise<StreamSession>;
921
+ public startStream(options: StartStreamOptions): Promise<WebRtcStreamSession>;
922
922
 
923
923
  public on<K extends keyof ClientEvents>(event: K, listener: (...args: ClientEvents[K]) => Awaitable<void>): this;
924
924
  public on<S extends string | symbol>(
@@ -1208,10 +1208,30 @@ export interface StartStreamOptions {
1208
1208
  downloadHttp?: boolean;
1209
1209
  /** Max video bitrate in kbps. Default: bitrate * 1.5 */
1210
1210
  bitrateMax?: number;
1211
+ /** x264 tune (ex. film). */
1212
+ tune?: string;
1213
+ /** readrateInitialBurst pour sources live. */
1214
+ livestream?: boolean;
1211
1215
  /** Low-latency encoding and playback. Default true. */
1212
1216
  lowLatency?: boolean;
1213
1217
  }
1214
1218
 
1219
+ /** Session Go Live WebRTC retournée par {@link Client#startStream}. */
1220
+ export class WebRtcStreamSession extends EventEmitter {
1221
+ public readonly client: Client;
1222
+ public pause(): void;
1223
+ public resume(): Promise<void>;
1224
+ public stop(): void;
1225
+ public replay(): Promise<void>;
1226
+ public disconnect(): void;
1227
+ public on(event: 'finish', listener: () => void): this;
1228
+ public on(event: 'debug', listener: (message: string) => void): this;
1229
+ public on(event: 'error', listener: (error: Error) => void): this;
1230
+ public once(event: 'finish', listener: () => void): this;
1231
+ public once(event: 'debug', listener: (message: string) => void): this;
1232
+ public once(event: 'error', listener: (error: Error) => void): this;
1233
+ }
1234
+
1215
1235
  export class StreamSession extends EventEmitter {
1216
1236
  public readonly client: Client;
1217
1237
  public readonly voiceConnection: VoiceConnection;