Skip to content
Merged
31 changes: 25 additions & 6 deletions build.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,18 @@ function buildElectron() {
const preloadFiles = [
'preload/variables.js',
'preload/discord.js',
'preload/scrobbler.js',
'shared/md5.js',
'shared/rzt-api.js',
'shared/scrobbler.js',
'preload/api-server.js',
'shared/soundcloud-import.js',
'preload/bridge.js'
];

// Page Context Files
const pageFiles = [
'shared/styles.js',
'shared/rzt-api.js',
'page/variables.js',
'shared/themes.js',
'shared/navbar-sync.js',
Expand All @@ -48,7 +52,7 @@ function buildElectron() {

let preloadCode = '';
for (const file of preloadFiles) {
const filePath = path.join(electronSrcDir, file);
const filePath = file.startsWith('shared/') ? path.join(rootDir, file) : path.join(electronSrcDir, file);
if (!fs.existsSync(filePath)) {
console.error(`Error: Preload component file ${file} not found at ${filePath}`);
return false;
Expand Down Expand Up @@ -87,6 +91,14 @@ ${pageCode}
const destPath = path.join(rootDir, 'yandex-sync-electron', 'desktop-sync.js');
fs.writeFileSync(destPath, combinedCode, 'utf8');
console.log(`Successfully built Electron desktop-sync.js -> ${destPath}`);

// Also copy to installer assets
const installerAssetsDir = path.join(rootDir, 'yandex-sync-installer', 'assets');
if (fs.existsSync(installerAssetsDir)) {
const installerDestPath = path.join(installerAssetsDir, 'desktop-sync.js');
fs.writeFileSync(installerDestPath, combinedCode, 'utf8');
console.log(`Successfully copied desktop-sync.js -> ${installerDestPath}`);
}
return true;
}

Expand All @@ -102,6 +114,10 @@ function buildExtension() {
// Isolated Context Files
const isolatedFiles = [
'shared/styles.js',
'shared/md5.js',
'shared/rzt-api.js',
'shared/scrobbler.js',
'isolated/scrobbler-init.js',
'isolated/variables.js',
'shared/themes.js',
'shared/navbar-sync.js',
Expand All @@ -122,6 +138,7 @@ function buildExtension() {
const mainFiles = [
'main/variables.js',
'main/player-monitor.js',
'shared/settings-injector.js',
'shared/soundcloud-api.js',
'shared/custom-audio.js',
'shared/player-faker.js',
Expand Down Expand Up @@ -153,12 +170,14 @@ function buildExtension() {
fs.writeFileSync(destIsolated, isolatedCode, 'utf8');
console.log(`Successfully built Extension isolated.js -> ${destIsolated}`);

// Copy soundcloud-bridge.js as standalone isolated script
// Bundle soundcloud-bridge.js as standalone isolated script with shared/soundcloud-import.js
const bridgeSrc = path.join(extSrcDir, 'isolated', 'soundcloud-bridge.js');
const sharedImport = path.join(rootDir, 'shared', 'soundcloud-import.js');
const bridgeDest = path.join(rootDir, 'yandex-sync-extension', 'soundcloud-bridge.js');
if (fs.existsSync(bridgeSrc)) {
fs.copyFileSync(bridgeSrc, bridgeDest);
console.log(`Successfully copied soundcloud-bridge.js -> ${bridgeDest}`);
if (fs.existsSync(bridgeSrc) && fs.existsSync(sharedImport)) {
const combined = fs.readFileSync(sharedImport, 'utf8') + '\n\n' + fs.readFileSync(bridgeSrc, 'utf8');
fs.writeFileSync(bridgeDest, combined, 'utf8');
console.log(`Successfully built bundled soundcloud-bridge.js -> ${bridgeDest}`);
}

const destMain = path.join(rootDir, 'yandex-sync-extension', 'main.js');
Expand Down
141 changes: 139 additions & 2 deletions shared/custom-audio.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,133 @@
// CUSTOM AUDIO CONTROLLER
// ==========================================

// Global logger to print volume states from all places
window.logAllVolumes = function(contextMessage = "") {
let nativeSlider = null;
let nativeExponent = null;
let nativeAudioVol = null;
let customAudioVol = null;
let customSlider = null;

try {
const activePlayer = window.getActivePlayer && window.getActivePlayer();
if (activePlayer && activePlayer.playbackState?.playerState) {
nativeSlider = activePlayer.playbackState.playerState.volume?.value;
nativeExponent = activePlayer.playbackState.playerState.exponentVolume?.value;
}
} catch(e) {}

try {
const nativeAudio = document.querySelector('audio:not(#ym-sync-custom-audio)');
if (nativeAudio) {
nativeAudioVol = nativeAudio.volume;
}
} catch(e) {}

try {
if (window.CustomAudioController && window.CustomAudioController.audioElement) {
customAudioVol = window.CustomAudioController.audioElement.volume;
}
} catch(e) {}

try {
const slider = document.getElementById('sc-ov-volume-slider');
if (slider) {
customSlider = parseFloat(slider.value);
}
} catch(e) {}

console.log(
`%c[VOLUME-SYNC-DEBUG] ${contextMessage}\n` +
` -> Наш ползунок (Custom Slider): ${customSlider !== null ? customSlider.toFixed(4) : 'не найден'}\n` +
` -> Наш аудио-элемент (Custom Audio volume): ${customAudioVol !== null ? customAudioVol.toFixed(4) : 'не инициализирован'}\n` +
` -> Оригинальный Sonata volume: ${nativeSlider !== null ? nativeSlider.toFixed(4) : 'не найден'}\n` +
` -> Оригинальный Sonata exponentVolume: ${nativeExponent !== null ? nativeExponent.toFixed(4) : 'не найден'}\n` +
` -> Оригинальный аудио-элемент (DOM volume): ${nativeAudioVol !== null ? nativeAudioVol.toFixed(4) : 'не найден'}`,
"color: #ff9900; font-weight: bold;"
);
};

// Helper to get native Yandex Music player volume (returns exponent volume which aligns with the UI slider)
window.getNativeVolume = function() {
// 1. Try Sonata player exponent volume state (UI slider position)
try {
const activePlayer = window.getActivePlayer && window.getActivePlayer();
if (activePlayer && activePlayer.playbackState?.playerState?.exponentVolume) {
const vol = activePlayer.playbackState.playerState.exponentVolume.value;
if (typeof vol === 'number') return vol;
}
} catch (e) {
console.error("[VOLUME-DEBUG] getNativeVolume error:", e);
}

// 2. Try native audio element volume
const nativeAudio = document.querySelector('audio:not(#ym-sync-custom-audio)');
if (nativeAudio) {
return nativeAudio.volume;
}

// 3. Fallback to localStorage
try {
const stored = localStorage.getItem('volume') || localStorage.getItem('player-volume');
if (stored !== null) {
const parsed = parseFloat(stored);
if (!isNaN(parsed) && parsed >= 0 && parsed <= 1) {
// If it was stored as linear volume, convert back to exponent
if (parsed === 0) return 0;
return Math.max(0, Math.min(1, 1 + Math.log10(parsed) / 2));
}
}
} catch (e) {}

return 0.7;
};

// Helper to get native Yandex Music player exponent volume (actual audio output scale)
window.getNativeExponentVolume = function() {
return window.getNativeVolume();
};

// Helper to set native Yandex Music player volume
window.setNativeVolume = function(vol) {
if (window.logAllVolumes) {
window.logAllVolumes(`setNativeVolume вызвана с vol = ${vol}`);
}

// Translate the desired exponent volume (vol) to Yandex's linear volume
// volume = 10^(2 * (exponentVolume - 1))
const translatedVol = vol === 0 ? 0 : Math.max(0, Math.min(1, Math.pow(10, 2 * (vol - 1))));

// 1. Try Sonata active player API
try {
const activePlayer = window.getActivePlayer && window.getActivePlayer();
if (activePlayer && typeof activePlayer.setVolume === 'function') {
activePlayer.setVolume(translatedVol);
}
} catch (e) {
console.error("[VOLUME-DEBUG] setNativeVolume error:", e);
}

// 2. Set on native audio element
const nativeAudio = document.querySelector('audio:not(#ym-sync-custom-audio)');
if (nativeAudio) {
nativeAudio.volume = vol;
}

// 3. Set localStorage keys (store linear volume so Yandex's internal code reads it correctly)
try {
localStorage.setItem('volume', String(translatedVol));
localStorage.setItem('player-volume', String(translatedVol));
} catch (e) {}

// Log after short timeout to let MobX or other handlers apply changes
setTimeout(() => {
if (window.logAllVolumes) {
window.logAllVolumes("setNativeVolume: Применилось (100мс)");
}
}, 100);
};

const CustomAudioController = {
audioElement: null,
isPlaying: false,
Expand Down Expand Up @@ -30,8 +157,8 @@ const CustomAudioController = {
// Optionally play next track if we implement a custom queue
});

// Set volume to match native player on init, default to 0.7
this.audioElement.volume = 0.7;
// Set volume to match native player on init (use exponent volume for correct loudness)
this.audioElement.volume = window.getNativeExponentVolume ? window.getNativeExponentVolume() : 0.7;
}
},

Expand All @@ -49,6 +176,11 @@ const CustomAudioController = {
async playTrack(track, streamUrl) {
this.init();

// Sync volume with native player (use exponent volume for correct loudness)
if (window.getNativeExponentVolume) {
this.audioElement.volume = window.getNativeExponentVolume();
}

// 1. Pause native player
const activePlayer = window.getActivePlayer && window.getActivePlayer();
if (activePlayer && typeof activePlayer.pause === 'function') {
Expand Down Expand Up @@ -110,6 +242,11 @@ const CustomAudioController = {
async syncPlay(scTrackId, serverState) {
this.init();

// Sync volume with native player (use exponent volume for correct loudness)
if (window.getNativeExponentVolume) {
this.audioElement.volume = window.getNativeExponentVolume();
}

const numericId = String(scTrackId).replace('soundcloud:', '');

// If the same track is already loaded
Expand Down
12 changes: 11 additions & 1 deletion shared/lyrics/lrclib-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ function parseLrc(lrcText) {

function fetchLyrics(title, artist, durationMs) {
const requestTrackId = currentLyricsTrackId;
if (isLyricsLoading && window.ymTrackIdLoadingLyrics === requestTrackId) return;
console.log('[LRCLIB] fetchLyrics called:', { title, artist, durationMs, requestTrackId });
if (isLyricsLoading && window.ymTrackIdLoadingLyrics === requestTrackId) {
console.log('[LRCLIB] Lyrics already loading for this track, skipping invocation');
return;
}

isLyricsLoading = true;
window.ymTrackIdLoadingLyrics = requestTrackId;
Expand All @@ -47,13 +51,17 @@ function fetchLyrics(title, artist, durationMs) {
if (durationSec > 0) {
url += `&duration=${durationSec}`;
}
console.log('[LRCLIB] Search URL:', url);

const handleResponseData = data => {
console.log('[LRCLIB] Successfully loaded lyrics:', data);
if (requestTrackId !== currentLyricsTrackId) return;
isLyricsLoading = false;
window.ymTrackIdLoadingLyrics = null;
displayLyricsData(data);
};
const handleFailure = err => {
console.warn('[LRCLIB] Failed to load lyrics:', err);
if (requestTrackId !== currentLyricsTrackId) return;
isLyricsLoading = false;
window.ymTrackIdLoadingLyrics = null;
Expand All @@ -63,12 +71,14 @@ function fetchLyrics(title, artist, durationMs) {
if (window.__ymSyncBridge && typeof window.__ymSyncBridge.fetchLyrics === 'function') {
window.__ymSyncBridge.fetchLyrics(url).catch(err => {
let fallbackUrl = `https://lrclib.net/api/get?artist_name=${encodeURIComponent(cleanArtist)}&track_name=${encodeURIComponent(cleanTitle)}`;
console.log('[LRCLIB] Primary fetch failed, trying fallback URL:', fallbackUrl);
return window.__ymSyncBridge.fetchLyrics(fallbackUrl);
}).then(handleResponseData).catch(handleFailure);
} else {
fetch(url).then(res => {
if (res.status === 404) {
let fallbackUrl = `https://lrclib.net/api/get?artist_name=${encodeURIComponent(cleanArtist)}&track_name=${encodeURIComponent(cleanTitle)}`;
console.log('[LRCLIB] Primary fetch returned 404, trying fallback URL:', fallbackUrl);
return fetch(fallbackUrl);
}
return res;
Expand Down
Loading
Loading