feat: migration to TypeScript part 2

Co-authored-by: Su-Yong <simssy2205@gmail.com>
This commit is contained in:
JellyBrick
2023-09-03 06:37:47 +09:00
parent 82bcadcd64
commit d30755e5fa
40 changed files with 523 additions and 296 deletions

View File

@ -1,15 +0,0 @@
const { ipcMain } = require('electron');
const { Innertube } = require('youtubei.js');
require('./config');
module.exports = async () => {
const yt = await Innertube.create();
ipcMain.handle('audio-url', async (_, videoID) => {
const info = await yt.getBasicInfo(videoID);
const url = info.streaming_data?.formats[0].decipher(yt.session.player);
return url;
});
};

15
plugins/crossfade/back.ts Normal file
View File

@ -0,0 +1,15 @@
import { ipcMain } from 'electron';
import { Innertube } from 'youtubei.js';
import config from './config';
export default async () => {
const yt = await Innertube.create();
ipcMain.handle('audio-url', async (_, videoID: string) => {
const info = await yt.getBasicInfo(videoID);
const url = info.streaming_data?.formats[0].decipher(yt.session.player);
return url;
});
};

View File

@ -1,4 +0,0 @@
const { PluginConfig } = require('../../config/dynamic');
const config = new PluginConfig('crossfade', { enableFront: true });
module.exports = { ...config };

View File

@ -0,0 +1,4 @@
import { PluginConfig } from '../../config/dynamic';
const config = new PluginConfig('crossfade', { enableFront: true });
export default { ...config } as PluginConfig<'crossfade'>;

View File

@ -18,7 +18,7 @@
'use strict';
// Internal utility: check if value is a valid volume level and throw if not
const validateVolumeLevel = (value) => {
const validateVolumeLevel = (value: number) => {
// Number between 0 and 1?
if (!Number.isNaN(value) && value >= 0 && value <= 1) {
// Yup, that's fine
@ -29,8 +29,51 @@ const validateVolumeLevel = (value) => {
}
};
type VolumeLogger = <Params extends unknown[]>(message: string, ...args: Params) => void;
interface VolumeFaderOptions {
/**
* logging `function(stuff, …)` for execution information (default: no logging)
*/
logger?: VolumeLogger;
/**
* either 'linear', 'logarithmic' or a positive number in dB (default: logarithmic)
*/
fadeScaling?: string | number;
/**
* media volume 01 to apply during setup (volume not touched by default)
*/
initialVolume?: number;
/**
* time in milliseconds to complete a fade (default: 1000 ms)
*/
fadeDuration?: number;
}
interface VolumeFade {
volume: {
start: number;
end: number;
};
time: {
start: number;
end: number;
};
callback?: () => void;
}
// Main class
class VolumeFader {
export class VolumeFader {
private media: HTMLMediaElement;
private logger: VolumeLogger | false;
private scale: {
internalToVolume: (level: number) => number;
volumeToInternal: (level: number) => number;
};
private fadeDuration: number = 1000;
private active: boolean = false;
private fade: VolumeFade | undefined;
/**
* VolumeFader Constructor
*
@ -38,13 +81,8 @@ class VolumeFader {
* @param options {Object} - an object with optional settings
* @throws {TypeError} if options.initialVolume or options.fadeDuration are invalid
*
* options:
* .logger: {Function} logging `function(stuff, …)` for execution information (default: no logging)
* .fadeScaling: {Mixed} either 'linear', 'logarithmic' or a positive number in dB (default: logarithmic)
* .initialVolume: {Number} media volume 01 to apply during setup (volume not touched by default)
* .fadeDuration: {Number} time in milliseconds to complete a fade (default: 1000 ms)
*/
constructor(media, options) {
constructor(media: HTMLMediaElement, options: VolumeFaderOptions) {
// Passed media element of correct type?
if (media instanceof HTMLMediaElement) {
// Save reference to media element
@ -70,8 +108,8 @@ class VolumeFader {
if (options.fadeScaling === 'linear') {
// Pass levels unchanged
this.scale = {
internalToVolume: (level) => level,
volumeToInternal: (level) => level,
internalToVolume: (level: number) => level,
volumeToInternal: (level: number) => level,
};
// Log setting
@ -79,7 +117,7 @@ class VolumeFader {
}
// No linear, but logarithmic fading…
else {
let dynamicRange;
let dynamicRange: number;
// Default dynamic range?
if (
@ -91,7 +129,8 @@ class VolumeFader {
}
// Custom dynamic range?
else if (
!Number.isNaN(options.fadeScaling)
typeof options.fadeScaling === 'number'
&& !Number.isNaN(options.fadeScaling)
&& options.fadeScaling > 0
) {
// Turn amplitude dB into a multiple of 10 power dB
@ -107,9 +146,9 @@ class VolumeFader {
// Use exponential/logarithmic scaler for expansion/compression
this.scale = {
internalToVolume: (level) =>
internalToVolume: (level: number) =>
this.exponentialScaler(level, dynamicRange),
volumeToInternal: (level) =>
volumeToInternal: (level: number) =>
this.logarithmicScaler(level, dynamicRange),
};
@ -193,7 +232,7 @@ class VolumeFader {
* @throws {TypeError} if fadeDuration is not a number greater than zero
* @return {Object} VolumeFader instance for chaining
*/
setFadeDuration(fadeDuration) {
setFadeDuration(fadeDuration: number) {
// If duration is a valid number > 0…
if (!Number.isNaN(fadeDuration) && fadeDuration > 0) {
// Set fade duration
@ -219,7 +258,7 @@ class VolumeFader {
* @throws {TypeError} if targetVolume is not in the range 01
* @return {Object} VolumeFader instance for chaining
*/
fadeTo(targetVolume, callback) {
fadeTo(targetVolume: number, callback?: () => void) {
// Validate volume and throw if invalid
validateVolumeLevel(targetVolume);
@ -250,11 +289,11 @@ class VolumeFader {
}
// Convenience shorthand methods for common fades
fadeIn(callback) {
fadeIn(callback: () => void) {
this.fadeTo(1, callback);
}
fadeOut(callback) {
fadeOut(callback: () => void) {
this.fadeTo(0, callback);
}
@ -313,7 +352,7 @@ class VolumeFader {
* @param {Number} dynamicRange - expanded output range, in multiples of 10 dB (float, 0)
* @return {Number} - expanded level (float, 01)
*/
exponentialScaler(input, dynamicRange) {
exponentialScaler(input: number, dynamicRange: number) {
// Special case: make zero (or any falsy input) return zero
if (input === 0) {
// Since the dynamic range is limited,
@ -336,7 +375,7 @@ class VolumeFader {
* @param {Number} dynamicRange - coerced input range, in multiples of 10 dB (float, 0)
* @return {Number} - compressed level (float, 01)
*/
logarithmicScaler(input, dynamicRange) {
logarithmicScaler(input: number, dynamicRange: number) {
// Special case: make zero (or any falsy input) return zero
if (input === 0) {
// Logarithm of zero would be -∞, which would map to zero anyway
@ -351,6 +390,6 @@ class VolumeFader {
}
}
module.exports = {
export default {
VolumeFader
};

View File

@ -1,38 +1,39 @@
const { ipcRenderer } = require('electron');
const { Howl } = require('howler');
/* eslint-disable @typescript-eslint/await-thenable */
/* renderer */
import { ipcRenderer } from 'electron';
import { Howl } from 'howler';
// Extracted from https://github.com/bitfasching/VolumeFader
const { VolumeFader } = require('./fader');
import { VolumeFader } from './fader';
let transitionAudio; // Howler audio used to fade out the current music
import configProvider from './config';
import defaultConfigs from '../../config/defaults';
import { ConfigType } from '../../config/dynamic';
let transitionAudio: Howl; // Howler audio used to fade out the current music
let firstVideo = true;
let waitForTransition;
let waitForTransition: Promise<unknown>;
/**
* @type {PluginConfig}
*/
const configProvider = require('./config');
const defaultConfig = defaultConfigs.plugins.crossfade;
const defaultConfig = require('../../config/defaults').plugins.crossfade;
let config: ConfigType<'crossfade'>;
let config;
const configGetNumber = (key: keyof ConfigType<'crossfade'>): number => Number(config[key]) || (defaultConfig[key] as number);
const configGetNumber = (key) => Number(config[key]) || defaultConfig[key];
const getStreamURL = async (videoID: string) => ipcRenderer.invoke('audio-url', videoID) as Promise<string>;
const getStreamURL = async (videoID) => {
return await ipcRenderer.invoke('audio-url', videoID);
};
const getVideoIDFromURL = (url) => new URLSearchParams(url.split('?')?.at(-1)).get('v');
const getVideoIDFromURL = (url: string) => new URLSearchParams(url.split('?')?.at(-1)).get('v');
const isReadyToCrossfade = () => transitionAudio && transitionAudio.state() === 'loaded';
const watchVideoIDChanges = (cb) => {
const watchVideoIDChanges = (cb: (id: string) => void) => {
window.navigation.addEventListener('navigate', (event) => {
const currentVideoID = getVideoIDFromURL(
event.currentTarget.currentEntry.url,
(event.currentTarget as Navigation).currentEntry?.url ?? '',
);
const nextVideoID = getVideoIDFromURL(event.destination.url);
const nextVideoID = getVideoIDFromURL(event.destination.url ?? '');
if (
nextVideoID
@ -51,7 +52,7 @@ const watchVideoIDChanges = (cb) => {
});
};
const createAudioForCrossfade = async (url) => {
const createAudioForCrossfade = (url: string) => {
if (transitionAudio) {
transitionAudio.unload();
}
@ -61,19 +62,19 @@ const createAudioForCrossfade = async (url) => {
html5: true,
volume: 0,
});
await syncVideoWithTransitionAudio();
syncVideoWithTransitionAudio();
};
const syncVideoWithTransitionAudio = async () => {
const video = document.querySelector('video');
const syncVideoWithTransitionAudio = () => {
const video = document.querySelector('video')!;
const videoFader = new VolumeFader(video, {
fadeScaling: configGetNumber('fadeScaling'),
fadeDuration: configGetNumber('fadeInDuration'),
});
await transitionAudio.play();
await transitionAudio.seek(video.currentTime);
transitionAudio.play();
transitionAudio.seek(video.currentTime);
video.addEventListener('seeking', () => {
transitionAudio.seek(video.currentTime);
@ -83,9 +84,9 @@ const syncVideoWithTransitionAudio = async () => {
transitionAudio.pause();
});
video.addEventListener('play', async () => {
await transitionAudio.play();
await transitionAudio.seek(video.currentTime);
video.addEventListener('play', () => {
transitionAudio.play();
transitionAudio.seek(video.currentTime);
// Fade in
const videoVolume = video.volume;
@ -102,7 +103,7 @@ const syncVideoWithTransitionAudio = async () => {
video.removeEventListener('timeupdate', transitionBeforeEnd);
// Go to next video - XXX: does not support "repeat 1" mode
document.querySelector('.next-button').click();
(document.querySelector('.next-button') as HTMLButtonElement).click();
}
};
@ -121,18 +122,18 @@ const onApiLoaded = () => {
});
};
const crossfade = async (cb) => {
const crossfade = (cb: () => void) => {
if (!isReadyToCrossfade()) {
cb();
return;
}
let resolveTransition;
waitForTransition = new Promise((resolve) => {
let resolveTransition: () => void;
waitForTransition = new Promise<void>((resolve) => {
resolveTransition = resolve;
});
const video = document.querySelector('video');
const video = document.querySelector('video')!;
const fader = new VolumeFader(transitionAudio._sounds[0]._node, {
initialVolume: video.volume,
@ -148,7 +149,7 @@ const crossfade = async (cb) => {
});
};
module.exports = async () => {
export default async () => {
config = await configProvider.getAll();
configProvider.subscribeAll((newConfig) => {

View File

@ -1,11 +1,16 @@
const prompt = require('custom-electron-prompt');
import prompt from 'custom-electron-prompt';
const config = require('./config');
import { BrowserWindow } from 'electron';
const promptOptions = require('../../providers/prompt-options');
const defaultOptions = require('../../config/defaults').plugins.crossfade;
import config from './config';
module.exports = (win) => [
import promptOptions from '../../providers/prompt-options';
import configOptions from '../../config/defaults';
import { ConfigType } from '../../config/dynamic';
const defaultOptions = configOptions.plugins.crossfade;
export default (win: BrowserWindow) => [
{
label: 'Advanced',
async click() {
@ -17,7 +22,7 @@ module.exports = (win) => [
},
];
async function promptCrossfadeValues(win, options) {
async function promptCrossfadeValues(win: BrowserWindow, options: ConfigType<'crossfade'>): Promise<Partial<ConfigType<'crossfade'>> | undefined> {
const res = await prompt(
{
title: 'Crossfade Options',
@ -29,8 +34,8 @@ async function promptCrossfadeValues(win, options) {
inputAttrs: {
type: 'number',
required: true,
min: 0,
step: 100,
min: '0',
step: '100',
},
},
{
@ -39,8 +44,8 @@ async function promptCrossfadeValues(win, options) {
inputAttrs: {
type: 'number',
required: true,
min: 0,
step: 100,
min: '0',
step: '100',
},
},
{
@ -50,7 +55,7 @@ async function promptCrossfadeValues(win, options) {
inputAttrs: {
type: 'number',
required: true,
min: 0,
min: '0',
},
},
{