/* eslint-disable no-param-reassign */ /* eslint-disable no-shadow */ /* eslint-disable import/no-unresolved */ /* eslint-disable global-require */ /* global angular notyf i18next MediaService l1Player hotkeys GithubClient isElectron require getLocalStorageValue getPlayer getPlayerAsync addPlayerListener smoothScrollTo lastfm */ function getCSSStringFromSetting(setting) { let { backgroundAlpha } = setting; if (backgroundAlpha === 0) { // NOTE: background alpha 0 results total transparent // which will cause mouse leave event not trigger // correct in windows platform for lyic window if disable // hardware accelerate backgroundAlpha = 0.01; } return `div.content.lyric-content{ font-size: ${setting.fontSize}px; color: ${setting.color}; background: rgba(36, 36, 36, ${backgroundAlpha}); } div.content.lyric-content span.contentTrans { font-size: ${setting.fontSize - 4}px; } `; } angular.module('listenone').controller('PlayController', [ '$scope', '$timeout', '$log', '$anchorScroll', '$location', '$rootScope', ($scope, $timeout, $log, $anchorScroll, $location, $rootScope) => { $scope.menuHidden = true; $scope.volume = l1Player.status.volume; $scope.mute = l1Player.status.muted; $scope.settings = { playmode: 0, nowplaying_track_id: -1, }; $scope.lyricArray = []; $scope.lyricLineNumber = -1; $scope.lastTrackId = null; $scope.enableGloablShortcut = false; $scope.isChrome = !isElectron(); $scope.isMac = false; $scope.currentDuration = '0:00'; $scope.currentPosition = '0:00'; if (!$scope.isChrome) { // eslint-disable-next-line no-undef $scope.isMac = process.platform === 'darwin'; } function switchMode(mode) { // playmode 0:loop 1:shuffle 2:repeat one switch (mode) { case 0: l1Player.setLoopMode('all'); break; case 1: l1Player.setLoopMode('shuffle'); break; case 2: l1Player.setLoopMode('one'); break; default: } } $scope.loadLocalSettings = () => { const defaultSettings = { playmode: 0, nowplaying_track_id: -1, volume: 90, }; const localSettings = localStorage.getObject('player-settings'); if (localSettings === null) { $scope.settings = defaultSettings; $scope.saveLocalSettings(); } else { $scope.settings = localSettings; } // apply settings switchMode($scope.settings.playmode); $scope.volume = $scope.settings.volume; if ($scope.volume === null) { $scope.volume = 90; $scope.saveLocalSettings(); } else { l1Player.setVolume($scope.volume); } $scope.enableGlobalShortCut = localStorage.getObject( 'enable_global_shortcut' ); $scope.enableLyricFloatingWindow = localStorage.getObject( 'enable_lyric_floating_window' ); $scope.enableLyricTranslation = localStorage.getObject( 'enable_lyric_translation' ); $scope.enableLyricFloatingWindowTranslation = localStorage.getObject( 'enable_lyric_floating_window_translation' ); $scope.enableAutoChooseSource = getLocalStorageValue( 'enable_auto_choose_source', true ); $scope.autoChooseSourceList = getLocalStorageValue( 'auto_choose_source_list', ['kuwo', 'qq', 'migu'] ); $scope.enableStopWhenClose = isElectron() || getLocalStorageValue('enable_stop_when_close', true); $scope.enableNowplayingCoverBackground = getLocalStorageValue( 'enable_nowplaying_cover_background', false ); $scope.enableNowplayingBitrate = getLocalStorageValue( 'enable_nowplaying_bitrate', false ); $scope.enableNowplayingPlatform = getLocalStorageValue( 'enable_nowplaying_platform', false ); const defaultFloatWindowSetting = { fontSize: 20, color: '#ffffff', backgroundAlpha: 0.2, }; $scope.floatWindowSetting = getLocalStorageValue( 'float_window_setting', defaultFloatWindowSetting ); $scope.applyGlobalShortcut(); $scope.openLyricFloatingWindow(); }; // electron global shortcuts $scope.applyGlobalShortcut = (toggle) => { if (!isElectron()) { return; } let message = ''; if (toggle === true) { $scope.enableGlobalShortCut = !$scope.enableGlobalShortCut; } if ($scope.enableGlobalShortCut === true) { message = 'enable_global_shortcut'; } else { message = 'disable_global_shortcut'; } // check if globalShortcuts is allowed localStorage.setObject( 'enable_global_shortcut', $scope.enableGlobalShortCut ); const { ipcRenderer } = require('electron'); ipcRenderer.send('control', message); }; $scope.openLyricFloatingWindow = (toggle) => { if (!isElectron()) { return; } let message = ''; if (toggle === true) { $scope.enableLyricFloatingWindow = !$scope.enableLyricFloatingWindow; } if ($scope.enableLyricFloatingWindow === true) { message = 'enable_lyric_floating_window'; } else { message = 'disable_lyric_floating_window'; } localStorage.setObject( 'enable_lyric_floating_window', $scope.enableLyricFloatingWindow ); const { ipcRenderer } = require('electron'); ipcRenderer.send( 'control', message, getCSSStringFromSetting($scope.floatWindowSetting) ); }; if (isElectron()) { const { webFrame, ipcRenderer } = require('electron'); // webFrame.setVisualZoomLevelLimits(1, 3); ipcRenderer.on('setZoomLevel', (event, level) => { webFrame.setZoomLevel(level); }); ipcRenderer.on('lyricWindow', (event, arg) => { if (arg === 'float_window_close') { $scope.openLyricFloatingWindow(true); } else if ( arg === 'float_window_font_small' || arg === 'float_window_font_large' ) { const MIN_FONT_SIZE = 12; const MAX_FONT_SIZE = 50; const offset = arg === 'float_window_font_small' ? -1 : 1; $scope.floatWindowSetting.fontSize += offset; if ($scope.floatWindowSetting.fontSize < MIN_FONT_SIZE) { $scope.floatWindowSetting.fontSize = MIN_FONT_SIZE; } else if ($scope.floatWindowSetting.fontSize > MAX_FONT_SIZE) { $scope.floatWindowSetting.fontSize = MAX_FONT_SIZE; } } else if ( arg === 'float_window_background_light' || arg === 'float_window_background_dark' ) { const MIN_BACKGROUND_ALPHA = 0; const MAX_BACKGROUND_ALPHA = 1; const offset = arg === 'float_window_background_light' ? -0.1 : 0.1; $scope.floatWindowSetting.backgroundAlpha += offset; if ( $scope.floatWindowSetting.backgroundAlpha < MIN_BACKGROUND_ALPHA ) { $scope.floatWindowSetting.backgroundAlpha = MIN_BACKGROUND_ALPHA; } else if ( $scope.floatWindowSetting.backgroundAlpha > MAX_BACKGROUND_ALPHA ) { $scope.floatWindowSetting.backgroundAlpha = MAX_BACKGROUND_ALPHA; } } else if (arg === 'float_window_font_change_color') { const floatWindowlyricColors = [ '#ffffff', '#65d29f', '#3c87eb', '#ec63af', '#4f5455', '#eb605b', ]; const currentIndex = floatWindowlyricColors.indexOf( $scope.floatWindowSetting.color ); const nextIndex = (currentIndex + 1) % floatWindowlyricColors.length; $scope.floatWindowSetting.color = floatWindowlyricColors[nextIndex]; } localStorage.setObject( 'float_window_setting', $scope.floatWindowSetting ); const { ipcRenderer } = require('electron'); const message = 'update_lyric_floating_window_css'; ipcRenderer.send( 'control', message, getCSSStringFromSetting($scope.floatWindowSetting) ); }); } $scope.saveLocalSettings = () => { localStorage.setObject('player-settings', $scope.settings); }; $scope.changePlaymode = () => { const playmodeCount = 3; $scope.settings.playmode = ($scope.settings.playmode + 1) % playmodeCount; switchMode($scope.settings.playmode); $scope.saveLocalSettings(); }; $rootScope.openGithubAuth = GithubClient.github.openAuthUrl; $rootScope.GithubLogout = () => { GithubClient.github.logout(); $scope.$evalAsync(() => { $scope.githubStatus = 0; $scope.githubStatusText = GithubClient.github.getStatusText(); }); }; $rootScope.updateGithubStatus = () => { GithubClient.github.updateStatus((data) => { $scope.$evalAsync(() => { $scope.githubStatus = data; $scope.githubStatusText = GithubClient.github.getStatusText(); }); }); }; $scope.togglePlaylist = () => { const anchor = `song${l1Player.status.playing.id}`; $scope.menuHidden = !$scope.menuHidden; if (!$scope.menuHidden) { $anchorScroll(anchor); } }; $scope.toggleMuteStatus = () => { // mute function is indeed toggle mute status. l1Player.toggleMute(); }; $scope.myProgress = 0; $scope.changingProgress = false; $scope.copyrightNotice = () => { notyf.info(i18next.t('_COPYRIGHT_ISSUE'), true); }; $scope.failAllNotice = () => { notyf.warning(i18next.t('_FAIL_ALL_NOTICE'), true); }; $rootScope.$on('track:myprogress', (event, data) => { $scope.$evalAsync(() => { // should use apply to force refresh ui $scope.myProgress = data; }); }); function parseLyric(lyric, tlyric) { const lines = lyric.split('\n'); let result = []; const timeResult = []; if (typeof tlyric !== 'string') { tlyric = ''; } const linesTrans = tlyric.split('\n'); const resultTrans = []; const timeResultTrans = []; if (tlyric === '') { linesTrans.splice(0); } function rightPadding(str, length, padChar) { const newstr = str + new Array(length - str.length + 1).join(padChar); return newstr; } const process = (result, timeResult, translationFlag) => (line, index) => { const tagReg = /\[\D*:([^\]]+)\]/g; const tagRegResult = tagReg.exec(line); if (tagRegResult) { const lyricObject = {}; lyricObject.seconds = 0; [lyricObject.content] = tagRegResult; result.push(lyricObject); return; } const timeReg = /\[(\d{2,})\:(\d{2})(?:\.(\d{1,3}))?\]/g; // eslint-disable-line no-useless-escape let timeRegResult = null; // eslint-disable-next-line no-cond-assign while ((timeRegResult = timeReg.exec(line)) !== null) { const htmlUnescapes = { '&': '&', '<': '<', '>': '>', '"': '"', ''': "'", ''': "'", }; timeResult.push({ content: line .replace(timeRegResult[0], '') .replace( /&(?:amp|lt|gt|quot|#39|apos);/g, (match) => htmlUnescapes[match] ), seconds: parseInt(timeRegResult[1], 10) * 60 * 1000 + // min parseInt(timeRegResult[2], 10) * 1000 + // sec (timeRegResult[3] ? parseInt(rightPadding(timeRegResult[3], 3, '0'), 10) : 0), // microsec translationFlag, index, }); } }; lines.forEach(process(result, timeResult, false)); linesTrans.forEach(process(resultTrans, timeResultTrans, true)); // sort time line result = timeResult.concat(timeResultTrans).sort((a, b) => { const keyA = a.seconds; const keyB = b.seconds; // Compare the 2 dates if (keyA < keyB) return -1; if (keyA > keyB) return 1; if (a.translationFlag !== b.translationFlag) { if (a.translationFlag === false) { return -1; } return 1; } if (a.index < b.index) return -1; if (a.index > b.index) return 1; return 0; }); // disable tag info, because music provider always write // tag info in lyric timeline. // result.push.apply(result, timeResult); // result = timeResult; // executed up there for (let i = 0; i < result.length; i += 1) { result[i].lineNumber = i; } return result; } const mode = isElectron() || getLocalStorageValue('enable_stop_when_close', true) ? 'front' : 'background'; getPlayer(mode).setMode(mode); if (mode === 'front') { if (!isElectron()) { // avoid background keep playing when change to front mode getPlayerAsync('background', (player) => { player.pause(); }); } } addPlayerListener(mode, (msg, sender, sendResponse) => { if ( typeof msg.type === 'string' && msg.type.split(':')[0] === 'BG_PLAYER' ) { switch (msg.type.split(':').slice(1).join('')) { case 'READY': { break; } case 'PLAY_FAILED': { notyf.info(i18next.t('_COPYRIGHT_ISSUE'), true); break; } case 'VOLUME': { $scope.$evalAsync(() => { $scope.volume = msg.data; }); break; } case 'FRAME_UPDATE': { // 'currentTrack:position' // update lyric position if (!l1Player.status.playing.id) break; const currentSeconds = msg.data.pos; let lastObject = null; let lastObjectTrans = null; $scope.lyricArray.forEach((lyric) => { if (currentSeconds >= lyric.seconds / 1000) { if (lyric.translationFlag !== true) { lastObject = lyric; } else { lastObjectTrans = lyric; } } }); if ( lastObject && lastObject.lineNumber !== $scope.lyricLineNumber ) { const lineElement = document.querySelector( `.page .playsong-detail .detail-songinfo .lyric p[data-line="${lastObject.lineNumber}"]` ); const windowHeight = document.querySelector( '.page .playsong-detail .detail-songinfo .lyric' ).offsetHeight; const adjustOffset = 30; const offset = lineElement.offsetTop - windowHeight / 2 + adjustOffset; smoothScrollTo(document.querySelector('.lyric'), offset, 500); $scope.lyricLineNumber = lastObject.lineNumber; if ( lastObjectTrans && lastObjectTrans.lineNumber !== $scope.lyricLineNumberTrans ) { $scope.lyricLineNumberTrans = lastObjectTrans.lineNumber; } if (isElectron()) { const { ipcRenderer } = require('electron'); const currentLyric = $scope.lyricArray[lastObject.lineNumber].content; let currentLyricTrans = ''; if ( $scope.enableLyricFloatingWindowTranslation === true && lastObjectTrans ) { currentLyricTrans = $scope.lyricArray[lastObjectTrans.lineNumber].content; } ipcRenderer.send('currentLyric', { lyric: currentLyric, tlyric: currentLyricTrans, }); } } // 'currentTrack:duration' (() => { const durationSec = Math.floor(msg.data.duration); const durationStr = `${Math.floor(durationSec / 60)}:${`0${ durationSec % 60 }`.substr(-2)}`; if ( msg.data.duration === 0 || $scope.currentDuration === durationStr ) { return; } $scope.currentDuration = durationStr; })(); // 'track:progress' if ($scope.changingProgress === false) { $scope.$evalAsync(() => { if (msg.data.duration === 0) { $scope.myProgress = 0; } else { $scope.myProgress = (msg.data.pos / msg.data.duration) * 100; } const posSec = Math.floor(msg.data.pos); const posStr = `${Math.floor(posSec / 60)}:${`0${ posSec % 60 }`.substr(-2)}`; $scope.currentPosition = posStr; }); } break; } case 'LOAD': { $scope.currentPlaying = msg.data; if (msg.data.id === undefined) { break; } $scope.currentPlaying.platformText = i18next.t( $scope.currentPlaying.platform ); $scope.myProgress = 0; if ($scope.lastTrackId === msg.data.id) { break; } const current = localStorage.getObject('player-settings') || {}; current.nowplaying_track_id = msg.data.id; localStorage.setObject('player-settings', current); // update lyric $scope.lyricArray = []; $scope.lyricLineNumber = -1; $scope.lyricLineNumberTrans = -1; smoothScrollTo(document.querySelector('.lyric'), 0, 300); const track = msg.data; $rootScope.page_title = { title: track.title, artist: track.artist, status: 'playing', }; if (lastfm.isAuthorized()) { lastfm.sendNowPlaying(track.title, track.artist, () => {}); } MediaService.getLyric( msg.data.id, msg.data.album_id, track.lyric_url, track.tlyric_url ).success((res) => { const { lyric, tlyric } = res; if (!lyric) { return; } $scope.lyricArray = parseLyric(lyric, tlyric); }); $scope.lastTrackId = msg.data.id; if (isElectron()) { const { ipcRenderer } = require('electron'); ipcRenderer.send('currentLyric', track.title); ipcRenderer.send('trackPlayingNow', track); } break; } case 'MUTE': { // 'music:mute' $scope.$evalAsync(() => { $scope.mute = msg.data; }); break; } case 'PLAYLIST': { // 'player:playlist' $scope.$evalAsync(() => { $scope.playlist = msg.data; localStorage.setObject('current-playing', msg.data); }); break; } case 'PLAY_STATE': { // 'music:isPlaying' $scope.$evalAsync(() => { $scope.isPlaying = !!msg.data.isPlaying; }); let title = 'Listen 1'; if ($rootScope.page_title !== undefined) { title = ''; if (msg.data.isPlaying) { $rootScope.page_title.status = 'playing'; } else { $rootScope.page_title.status = 'paused'; } if ($rootScope.page_title.status !== '') { if ($rootScope.page_title.status === 'playing') { title += '▶ '; } else if ($rootScope.page_title.status === 'paused') { title += '❚❚ '; } } title += $rootScope.page_title.title; if ($rootScope.page_title.artist !== '') { title += ` - ${$rootScope.page_title.artist}`; } } $rootScope.document_title = title; if (isElectron()) { const { ipcRenderer } = require('electron'); if (msg.data.isPlaying) { ipcRenderer.send('isPlaying', true); } else { ipcRenderer.send('isPlaying', false); } } if (msg.data.reason === 'Ended') { if (!lastfm.isAuthorized()) { break; } // send lastfm scrobble const track = l1Player.getTrackById(l1Player.status.playing.id); lastfm.scrobble( l1Player.status.playing.playedFrom, track.title, track.artist, track.album, () => {} ); } break; } case 'RETRIEVE_URL_SUCCESS': { $scope.currentPlaying = msg.data; // update translate whenever set value $scope.currentPlaying.platformText = i18next.t( $scope.currentPlaying.platform ); break; } case 'RETRIEVE_URL_FAIL': { $scope.copyrightNotice(); break; } case 'RETRIEVE_URL_FAIL_ALL': { $scope.failAllNotice(); break; } default: break; } } if (sendResponse !== undefined) { sendResponse(); } }); // connect player should run after all addListener function finished l1Player.connectPlayer(); // define keybind // description: '播放/暂停', hotkeys('p', l1Player.togglePlayPause); // description: '上一首', hotkeys('[', l1Player.prev); // description: '下一首', hotkeys(']', l1Player.next); // description: '静音/取消静音', hotkeys('m', l1Player.toggleMute); // description: '打开/关闭播放列表', hotkeys('l', $scope.togglePlaylist); // description: '切换播放模式(顺序/随机/单曲循环)', hotkeys('s', $scope.changePlaymode); // description: '音量增加', hotkeys('u', () => { $timeout(() => { l1Player.adjustVolume(true); }); }); // description: '音量减少', hotkeys('d', () => { $timeout(() => { l1Player.adjustVolume(false); }); }); $scope.toggleLyricTranslation = () => { $scope.enableLyricTranslation = !$scope.enableLyricTranslation; localStorage.setObject( 'enable_lyric_translation', $scope.enableLyricTranslation ); }; $scope.toggleLyricFloatingWindowTranslation = () => { $scope.enableLyricFloatingWindowTranslation = !$scope.enableLyricFloatingWindowTranslation; localStorage.setObject( 'enable_lyric_floating_window_translation', $scope.enableLyricFloatingWindowTranslation ); }; if (isElectron()) { require('electron').ipcRenderer.on('globalShortcut', (event, message) => { if (message === 'right') { l1Player.next(); } else if (message === 'left') { l1Player.prev(); } else if (message === 'space') { l1Player.togglePlayPause(); } }); } $scope.setAutoChooseSource = (toggle) => { if (toggle === true) { $scope.enableAutoChooseSource = !$scope.enableAutoChooseSource; } localStorage.setObject( 'enable_auto_choose_source', $scope.enableAutoChooseSource ); }; $scope.enableSource = (source) => { if ($scope.autoChooseSourceList.indexOf(source) > -1) { return; } $scope.autoChooseSourceList = [...$scope.autoChooseSourceList, source]; localStorage.setObject( 'auto_choose_source_list', $scope.autoChooseSourceList ); }; $scope.disableSource = (source) => { if ($scope.autoChooseSourceList.indexOf(source) === -1) { return; } $scope.autoChooseSourceList = $scope.autoChooseSourceList.filter( (i) => i !== source ); localStorage.setObject( 'auto_choose_source_list', $scope.autoChooseSourceList ); }; $scope.setStopWhenClose = (status) => { $scope.enableStopWhenClose = status; localStorage.setObject( 'enable_stop_when_close', $scope.enableStopWhenClose ); }; $scope.setNowplayingCoverBackground = (toggle) => { if (toggle === true) { $scope.enableNowplayingCoverBackground = !$scope.enableNowplayingCoverBackground; } localStorage.setObject( 'enable_nowplaying_cover_background', $scope.enableNowplayingCoverBackground ); }; $scope.setNowplayingBitrate = (toggle) => { if (toggle === true) { $scope.enableNowplayingBitrate = !$scope.enableNowplayingBitrate; } localStorage.setObject( 'enable_nowplaying_bitrate', $scope.enableNowplayingBitrate ); }; $scope.setNowplayingPlatform = (toggle) => { if (toggle === true) { $scope.enableNowplayingPlatform = !$scope.enableNowplayingPlatform; } localStorage.setObject( 'enable_nowplaying_platform', $scope.enableNowplayingPlatform ); }; }, ]);