Merge branch 'upcoming'
# Conflicts: # LICENSE # src/renderer/index.js
7
.gitignore
vendored
|
@ -4,6 +4,7 @@ dist
|
||||||
yarn*
|
yarn*
|
||||||
package-lock.json
|
package-lock.json
|
||||||
.yarnclean
|
.yarnclean
|
||||||
|
build
|
||||||
|
|
||||||
# Misc
|
# Misc
|
||||||
.idea
|
.idea
|
||||||
|
@ -308,3 +309,9 @@ GitHub.sublime-settings
|
||||||
#Service Worker mappings
|
#Service Worker mappings
|
||||||
src/renderer/sw.js.map
|
src/renderer/sw.js.map
|
||||||
src/renderer/workbox-962786f2.js.map
|
src/renderer/workbox-962786f2.js.map
|
||||||
|
/src/renderer/musickit-dev.js
|
||||||
|
|
||||||
|
#Mac certs
|
||||||
|
*.p12
|
||||||
|
keys.sh
|
||||||
|
|
||||||
|
|
15
Assets/Cider with text.svg
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg width="100%" height="100%" viewBox="0 0 577 205" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||||
|
<g transform="matrix(0.788347,0,0,0.788347,-131.654,-154.131)">
|
||||||
|
<g id="Release.afdesign" transform="matrix(0.260038,0,0,0.260038,297.279,325.53)">
|
||||||
|
<g transform="matrix(1,0,0,1,-501,-500)">
|
||||||
|
<path d="M501,21C765.367,21 980,235.633 980,500C980,764.367 765.367,979 501,979C236.633,979 22,764.367 22,500C22,235.633 236.633,21 501,21ZM501,169C683.684,169 832,317.316 832,500C832,682.684 683.684,831 501,831C318.316,831 170,682.684 170,500C170,317.316 318.316,169 501,169Z" style="fill:rgb(255,38,84);"/>
|
||||||
|
<path d="M501,224C653.053,224 776.5,347.447 776.5,499.5C776.5,651.553 653.053,775 501,775C348.947,775 225.5,651.553 225.5,499.5C225.5,347.447 348.947,224 501,224ZM589.165,492.207C595.163,495.672 595.163,504.328 589.165,507.793L439.502,594.256C433.502,597.722 426,593.392 426,586.463L426,413.537C426,406.608 433.502,402.278 439.502,405.744L589.165,492.207Z" style="fill:rgb(255,38,84);"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(10.0544,0,0,10.0544,-4614.58,-2607.36)">
|
||||||
|
<text x="505.982px" y="297.612px" style="font-family:'Inter-Bold', 'Inter';font-weight:700;font-size:16px;fill:rgb(235,235,235);">Cider</text>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
6
Assets/Release.svg
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg width="100%" height="100%" viewBox="0 0 1002 1000" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||||
|
<path d="M501,21C765.367,21 980,235.633 980,500C980,764.367 765.367,979 501,979C236.633,979 22,764.367 22,500C22,235.633 236.633,21 501,21ZM501,169C683.684,169 832,317.316 832,500C832,682.684 683.684,831 501,831C318.316,831 170,682.684 170,500C170,317.316 318.316,169 501,169Z" style="fill:rgb(255,38,84);"/>
|
||||||
|
<path d="M501,224C653.053,224 776.5,347.447 776.5,499.5C776.5,651.553 653.053,775 501,775C348.947,775 225.5,651.553 225.5,499.5C225.5,347.447 348.947,224 501,224ZM589.165,492.207C595.163,495.672 595.163,504.328 589.165,507.793L439.502,594.256C433.502,597.722 426,593.392 426,586.463L426,413.537C426,406.608 433.502,402.278 439.502,405.744L589.165,492.207Z" style="fill:rgb(255,38,84);"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
BIN
Assets/Sources/Cider with text.afdesign
Normal file
BIN
Assets/Sources/Release.afdesign
Normal file
BIN
Assets/Sources/cider-vinyl no raster.afdesign
Normal file
BIN
Assets/Sources/cider-vinyl-no raster 2.afdesign
Normal file
BIN
Assets/Sources/cider-vinyl.afdesign
Normal file
41
Assets/Vinyl Logo/cider-vinyl no raster.svg
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg width="100%" height="100%" viewBox="0 0 257 257" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||||
|
<g id="Cider-Vinyl" serif:id="Cider Vinyl" transform="matrix(2.36243,0,0,2.36243,-4.17297,-4.04967)">
|
||||||
|
<g id="Vinyl-Ring" serif:id="Vinyl Ring" transform="matrix(0.447755,0,0,0.447755,-245.339,-9.81703)">
|
||||||
|
<circle cx="673" cy="146.877" r="121.123" style="fill:url(#_Linear1);"/>
|
||||||
|
<clipPath id="_clip2">
|
||||||
|
<circle cx="673" cy="146.877" r="121.123"/>
|
||||||
|
</clipPath>
|
||||||
|
<g clip-path="url(#_clip2)">
|
||||||
|
<g transform="matrix(0.945365,0,0,0.945365,36.7693,8.02459)">
|
||||||
|
<circle cx="673" cy="48" r="101.585" style="fill:white;fill-opacity:0.02;"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g id="Vinyl-Surface" serif:id="Vinyl Surface" transform="matrix(0.423292,0,0,0.423292,-250.04,-148.398)">
|
||||||
|
<circle cx="723" cy="482.753" r="118.247" style="fill:rgb(15,15,15);"/>
|
||||||
|
<clipPath id="_clip3">
|
||||||
|
<circle cx="723" cy="482.753" r="118.247"/>
|
||||||
|
</clipPath>
|
||||||
|
<g clip-path="url(#_clip3)">
|
||||||
|
<g transform="matrix(1,0,0,1,594.877,354.63)">
|
||||||
|
<path d="M128.123,14.126C191.04,14.126 242.121,65.206 242.121,128.123C242.121,191.04 191.04,242.121 128.123,242.121C65.206,242.121 14.126,191.04 14.126,128.123C14.126,65.206 65.206,14.126 128.123,14.126ZM128.123,21.141C187.168,21.141 235.105,69.078 235.105,128.123C235.105,187.168 187.168,235.105 128.123,235.105C69.078,235.105 21.141,187.168 21.141,128.123C21.141,69.078 69.078,21.141 128.123,21.141ZM128.123,60.877C165.238,60.877 195.37,91.009 195.37,128.123C195.37,165.238 165.238,195.37 128.123,195.37C91.009,195.37 60.877,165.238 60.877,128.123C60.877,91.009 91.009,60.877 128.123,60.877ZM128.123,65.015C162.954,65.015 191.231,93.293 191.231,128.123C191.231,162.954 162.954,191.231 128.123,191.231C93.293,191.231 65.015,162.954 65.015,128.123C65.015,93.293 93.293,65.015 128.123,65.015Z" style="fill:rgb(20,20,20);"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1,0,0,1,594.877,354.63)">
|
||||||
|
<path d="M236.794,189.735L189.735,236.794L139.329,162.859L162.859,139.329L236.794,189.735ZM19.452,66.512L66.512,19.452L116.917,93.388L93.388,116.917L19.452,66.512Z" style="fill:rgb(37,37,37);"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g id="Play-Button-Fill-Color" serif:id="Play Button Fill Color" transform="matrix(0.399516,0,0,0.399516,30.431,30.431)">
|
||||||
|
<circle cx="64" cy="64" r="43" style="fill:white;"/>
|
||||||
|
</g>
|
||||||
|
<g id="Play-Button" serif:id="Play Button" transform="matrix(0.0920925,0,0,0.0920925,9.86165,9.94759)">
|
||||||
|
<path d="M501,224C653.053,224 776.5,347.447 776.5,499.5C776.5,651.553 653.053,775 501,775C348.947,775 225.5,651.553 225.5,499.5C225.5,347.447 348.947,224 501,224ZM589.165,492.207C595.163,495.672 595.163,504.328 589.165,507.793L439.502,594.256C433.502,597.722 426,593.392 426,586.463L426,413.537C426,406.608 433.502,402.278 439.502,405.744L589.165,492.207Z" style="fill:url(#_Linear4);"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="_Linear1" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1.22412e-13,242.247,-242.247,1.22412e-13,673,25.7534)"><stop offset="0" style="stop-color:rgb(255,38,84);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(183,0,39);stop-opacity:1"/></linearGradient>
|
||||||
|
<linearGradient id="_Linear4" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(3.3739e-14,551,-551,3.3739e-14,501,224)"><stop offset="0" style="stop-color:rgb(255,38,84);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(183,0,39);stop-opacity:1"/></linearGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 4.1 KiB |
42
Assets/Vinyl Logo/cider-vinyl textured.svg
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg width="100%" height="100%" viewBox="0 0 257 257" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||||
|
<g id="Cider-Vinyl" serif:id="Cider Vinyl" transform="matrix(2.36243,0,0,2.36243,-4.17297,-4.04967)">
|
||||||
|
<g id="Vinyl-Ring" serif:id="Vinyl Ring" transform="matrix(0.447755,0,0,0.447755,-245.339,-9.81703)">
|
||||||
|
<circle cx="673" cy="146.877" r="121.123" style="fill:url(#_Linear1);"/>
|
||||||
|
<clipPath id="_clip2">
|
||||||
|
<circle cx="673" cy="146.877" r="121.123"/>
|
||||||
|
</clipPath>
|
||||||
|
<g clip-path="url(#_clip2)">
|
||||||
|
<g transform="matrix(0.945365,0,0,0.945365,36.7693,8.02459)">
|
||||||
|
<circle cx="673" cy="48" r="101.585" style="fill:white;fill-opacity:0.02;"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g id="Vinyl-Surface" serif:id="Vinyl Surface" transform="matrix(0.423292,0,0,0.423292,-250.04,-148.398)">
|
||||||
|
<circle cx="723" cy="482.753" r="118.247" style="fill:url(#_Linear3);"/>
|
||||||
|
<clipPath id="_clip4">
|
||||||
|
<circle cx="723" cy="482.753" r="118.247"/>
|
||||||
|
</clipPath>
|
||||||
|
<g clip-path="url(#_clip4)">
|
||||||
|
<g transform="matrix(1,0,0,1,594.877,354.63)">
|
||||||
|
<path d="M128.123,14.126C191.04,14.126 242.121,65.206 242.121,128.123C242.121,191.04 191.04,242.121 128.123,242.121C65.206,242.121 14.126,191.04 14.126,128.123C14.126,65.206 65.206,14.126 128.123,14.126ZM128.123,21.141C187.168,21.141 235.105,69.078 235.105,128.123C235.105,187.168 187.168,235.105 128.123,235.105C69.078,235.105 21.141,187.168 21.141,128.123C21.141,69.078 69.078,21.141 128.123,21.141ZM128.123,60.877C165.238,60.877 195.37,91.009 195.37,128.123C195.37,165.238 165.238,195.37 128.123,195.37C91.009,195.37 60.877,165.238 60.877,128.123C60.877,91.009 91.009,60.877 128.123,60.877ZM128.123,65.015C162.954,65.015 191.231,93.293 191.231,128.123C191.231,162.954 162.954,191.231 128.123,191.231C93.293,191.231 65.015,162.954 65.015,128.123C65.015,93.293 93.293,65.015 128.123,65.015Z" style="fill:rgb(20,20,20);"/>
|
||||||
|
</g>
|
||||||
|
<g transform="matrix(1,0,0,1,594.877,354.63)">
|
||||||
|
<path d="M236.794,189.735L189.735,236.794L139.329,162.859L162.859,139.329L236.794,189.735ZM19.452,66.512L66.512,19.452L116.917,93.388L93.388,116.917L19.452,66.512Z" style="fill:rgb(37,37,37);"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g id="Play-Button-Fill-Color" serif:id="Play Button Fill Color" transform="matrix(0.399516,0,0,0.399516,30.431,30.431)">
|
||||||
|
<circle cx="64" cy="64" r="43" style="fill:white;"/>
|
||||||
|
</g>
|
||||||
|
<g id="Play-Button" serif:id="Play Button" transform="matrix(0.0920925,0,0,0.0920925,9.86165,9.94759)">
|
||||||
|
<path d="M501,224C653.053,224 776.5,347.447 776.5,499.5C776.5,651.553 653.053,775 501,775C348.947,775 225.5,651.553 225.5,499.5C225.5,347.447 348.947,224 501,224ZM589.165,492.207C595.163,495.672 595.163,504.328 589.165,507.793L439.502,594.256C433.502,597.722 426,593.392 426,586.463L426,413.537C426,406.608 433.502,402.278 439.502,405.744L589.165,492.207Z" style="fill:url(#_Linear5);"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="_Linear1" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1.22412e-13,242.247,-242.247,1.22412e-13,673,25.7534)"><stop offset="0" style="stop-color:rgb(255,38,84);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(183,0,39);stop-opacity:1"/></linearGradient>
|
||||||
|
<linearGradient id="_Linear3" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(2.24529e-13,236.493,-236.493,2.24529e-13,723,364.507)"><stop offset="0" style="stop-color:rgb(15,15,15);stop-opacity:1"/><stop offset="1" style="stop-color:black;stop-opacity:1"/></linearGradient>
|
||||||
|
<linearGradient id="_Linear5" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(3.3739e-14,551,-551,3.3739e-14,501,224)"><stop offset="0" style="stop-color:rgb(255,38,84);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(183,0,39);stop-opacity:1"/></linearGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 4.4 KiB |
262
index.js
|
@ -1,262 +0,0 @@
|
||||||
require('v8-compile-cache');
|
|
||||||
const { app, components } = require('electron'), { resolve, join } = require("path"),
|
|
||||||
CiderBase = require('./src/main/cider-base');
|
|
||||||
const customProtocols = require('./package.json').fileAssociations[0].protocols
|
|
||||||
console.log(customProtocols)
|
|
||||||
const comps = components;
|
|
||||||
|
|
||||||
|
|
||||||
// Analytics for debugging.
|
|
||||||
const ElectronSentry = require("@sentry/electron");
|
|
||||||
ElectronSentry.init({ dsn: "https://68c422bfaaf44dea880b86aad5a820d2@o954055.ingest.sentry.io/6112214" });
|
|
||||||
|
|
||||||
const configDefaults = {
|
|
||||||
"general": {
|
|
||||||
"close_behavior": 0, // 0 = close, 1 = minimize, 2 = minimize to tray
|
|
||||||
"startup_behavior": 0, // 0 = nothing, 1 = open on startup
|
|
||||||
"discord_rpc": 1, // 0 = disabled, 1 = enabled as Cider, 2 = enabled as Apple Music
|
|
||||||
"discordClearActivityOnPause": 1, // 0 = disabled, 1 = enabled
|
|
||||||
"volume": 1
|
|
||||||
},
|
|
||||||
"home": {
|
|
||||||
"followedArtists": [],
|
|
||||||
"favoriteItems": []
|
|
||||||
},
|
|
||||||
"libraryPrefs": {
|
|
||||||
"songs": {
|
|
||||||
"sort": "name",
|
|
||||||
"sortOrder": "asc",
|
|
||||||
"size": "normal"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"audio": {
|
|
||||||
"quality": "990",
|
|
||||||
"seamless_audio": true,
|
|
||||||
"normalization": false,
|
|
||||||
"spatial": false,
|
|
||||||
"spatial_properties": {
|
|
||||||
"presets": [],
|
|
||||||
"gain": 0.8,
|
|
||||||
"listener_position": [0, 0, 0],
|
|
||||||
"audio_position": [0, 0, 0],
|
|
||||||
"room_dimensions": {
|
|
||||||
"width": 32,
|
|
||||||
"height": 12,
|
|
||||||
"depth": 32
|
|
||||||
},
|
|
||||||
"room_materials": {
|
|
||||||
"left": 'metal',
|
|
||||||
"right": 'metal',
|
|
||||||
"front": 'brick-bare',
|
|
||||||
"back": 'brick-bare',
|
|
||||||
"down": 'acoustic-ceiling-tiles',
|
|
||||||
"up": 'acoustic-ceiling-tiles',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"visual": {
|
|
||||||
"theme": "",
|
|
||||||
"scrollbars": 0, // 0 = show on hover, 2 = always hide, 3 = always show
|
|
||||||
"refresh_rate": 0,
|
|
||||||
"animated_artwork": "limited", // 0 = always, 1 = limited, 2 = never
|
|
||||||
"animated_artwork_qualityLevel": 1,
|
|
||||||
"bg_artwork_rotation": false,
|
|
||||||
"hw_acceleration": "default", // default, webgpu, disabled
|
|
||||||
"videoRes": 720
|
|
||||||
},
|
|
||||||
"lyrics": {
|
|
||||||
"enable_mxm": false,
|
|
||||||
"mxm_karaoke": false,
|
|
||||||
"mxm_language": "en",
|
|
||||||
"enable_yt": false,
|
|
||||||
},
|
|
||||||
"lastfm": {
|
|
||||||
"enabled": false,
|
|
||||||
"scrobble_after": 50,
|
|
||||||
"auth_token": "",
|
|
||||||
"enabledRemoveFeaturingArtists": true,
|
|
||||||
"NowPlaying": "true"
|
|
||||||
},
|
|
||||||
"advanced": {
|
|
||||||
"AudioContext": false,
|
|
||||||
"experiments": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const merge = (target, source) => {
|
|
||||||
// Iterate through `source` properties and if an `Object` set property to merge of `target` and `source` properties
|
|
||||||
for (const key of Object.keys(source)) {
|
|
||||||
if (source[key] instanceof Object) Object.assign(source[key], merge(target[key], source[key]))
|
|
||||||
}
|
|
||||||
// Join `target` and modified `source`
|
|
||||||
Object.assign(target || {}, source)
|
|
||||||
return target
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const Store = require("electron-store");
|
|
||||||
app.cfg = new Store({
|
|
||||||
defaults: configDefaults
|
|
||||||
});
|
|
||||||
let currentCfg = app.cfg.get()
|
|
||||||
app.cfg.set(merge(configDefaults, currentCfg))
|
|
||||||
|
|
||||||
app.paths = {
|
|
||||||
ciderCache: resolve(app.getPath("userData"), "CiderCache"),
|
|
||||||
themes: resolve(app.getPath("userData"), "Themes"),
|
|
||||||
plugins: resolve(app.getPath("userData"), "Plugins"),
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (app.cfg.get("visual.hw_acceleration")) {
|
|
||||||
default:
|
|
||||||
case "default":
|
|
||||||
app.commandLine.appendSwitch('enable-accelerated-mjpeg-decode')
|
|
||||||
app.commandLine.appendSwitch('enable-accelerated-video')
|
|
||||||
app.commandLine.appendSwitch('disable-gpu-driver-bug-workarounds')
|
|
||||||
app.commandLine.appendSwitch('ignore-gpu-blacklist')
|
|
||||||
app.commandLine.appendSwitch('enable-native-gpu-memory-buffers')
|
|
||||||
app.commandLine.appendSwitch('enable-accelerated-video-decode');
|
|
||||||
app.commandLine.appendSwitch('enable-gpu-rasterization');
|
|
||||||
app.commandLine.appendSwitch('enable-native-gpu-memory-buffers');
|
|
||||||
app.commandLine.appendSwitch('enable-oop-rasterization');
|
|
||||||
break;
|
|
||||||
case "webgpu":
|
|
||||||
console.info("WebGPU is enabled.");
|
|
||||||
app.commandLine.appendSwitch('enable-unsafe-webgpu')
|
|
||||||
break;
|
|
||||||
case "disabled":
|
|
||||||
console.info("Hardware acceleration is disabled.");
|
|
||||||
app.commandLine.appendSwitch('disable-gpu')
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creating the Application Window and Calling all the Functions
|
|
||||||
function CreateWindow() {
|
|
||||||
if (app.isQuiting) {
|
|
||||||
app.quit();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** CIDER **/
|
|
||||||
const ciderwin = require("./src/main/cider-base")
|
|
||||||
app.win = ciderwin
|
|
||||||
app.win.Start()
|
|
||||||
/** CIDER **/
|
|
||||||
}
|
|
||||||
|
|
||||||
if (process.platform === "linux") {
|
|
||||||
app.commandLine.appendSwitch('disable-features', 'MediaSessionService');
|
|
||||||
}
|
|
||||||
|
|
||||||
app.commandLine.appendSwitch('no-sandbox');
|
|
||||||
// app.commandLine.appendSwitch('js-flags', '--max-old-space-size=1024')
|
|
||||||
|
|
||||||
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
* App Event Handlers
|
|
||||||
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
|
|
||||||
|
|
||||||
app.whenReady().then(async() => {
|
|
||||||
if (process.platform === "win32") {
|
|
||||||
app.commandLine.appendSwitch('high-dpi-support', 'true')
|
|
||||||
app.commandLine.appendSwitch('force-device-scale-factor', '1')
|
|
||||||
app.commandLine.appendSwitch('disable-pinch');
|
|
||||||
}
|
|
||||||
if (comps == null) {
|
|
||||||
app.on("widevine-ready", () => {
|
|
||||||
console.log('[Cider] Application is Ready. Creating Window.')
|
|
||||||
if (!app.isPackaged) {
|
|
||||||
console.info('[Cider] Running in development mode.')
|
|
||||||
require('vue-devtools').install()
|
|
||||||
}
|
|
||||||
CreateWindow()
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
await comps.whenReady();
|
|
||||||
console.log('components ready:', comps.status());
|
|
||||||
|
|
||||||
console.log('[Cider] Application is Ready. Creating Window.')
|
|
||||||
if (!app.isPackaged) {
|
|
||||||
console.info('[Cider] Running in development mode.')
|
|
||||||
require('vue-devtools').install()
|
|
||||||
}
|
|
||||||
CreateWindow()
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
app.on('before-quit', () => {
|
|
||||||
console.warn(`${app.getName()} exited.`);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Widevine Stuff
|
|
||||||
app.on('widevine-ready', (version, lastVersion) => {
|
|
||||||
if (null !== lastVersion) {
|
|
||||||
console.log('[Cider][Widevine] Widevine ' + version + ', upgraded from ' + lastVersion + ', is ready to be used!')
|
|
||||||
} else {
|
|
||||||
console.log('[Cider][Widevine] Widevine ' + version + ' is ready to be used!')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
app.on('widevine-update-pending', (currentVersion, pendingVersion) => {
|
|
||||||
console.log('[Cider][Widevine] Widevine ' + currentVersion + ' is ready to be upgraded to ' + pendingVersion + '!')
|
|
||||||
})
|
|
||||||
|
|
||||||
app.on('widevine-error', (error) => {
|
|
||||||
console.log('[Cider][Widevine] Widevine installation encountered an error: ' + error)
|
|
||||||
app.exit()
|
|
||||||
})
|
|
||||||
|
|
||||||
if (process.defaultApp) {
|
|
||||||
if (process.argv.length >= 2) {
|
|
||||||
customProtocols.forEach((customProtocol) => {
|
|
||||||
app.setAsDefaultProtocolClient(customProtocol, process.execPath, [resolve(process.argv[1])])
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
/*
|
|
||||||
* cider - Custom Cider Protocol
|
|
||||||
* ame - Custom AME Protocol (Backwards Compat.)
|
|
||||||
* itms - iTunes HTTP Protocol
|
|
||||||
* itmss - iTunes HTTPS Protocol
|
|
||||||
* musics - macOS Client Protocol
|
|
||||||
* music - macOS Client Protocol
|
|
||||||
*/
|
|
||||||
customProtocols.forEach((customProtocol) => {
|
|
||||||
app.setAsDefaultProtocolClient(customProtocol)
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
app.on('open-url', (event, url) => {
|
|
||||||
event.preventDefault()
|
|
||||||
if (customProtocols.some(protocol => url.includes(protocol))) {
|
|
||||||
CiderBase.LinkHandler(url)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
app.on('second-instance', (_e, argv) => {
|
|
||||||
console.warn(`[InstanceHandler][SecondInstanceHandler] Second Instance Started with args: [${argv.join(', ')}]`)
|
|
||||||
|
|
||||||
// Checks if first instance is authorized and if second instance has protocol args
|
|
||||||
argv.forEach((value) => {
|
|
||||||
if (customProtocols.some(protocol => value.includes(protocol))) {
|
|
||||||
CiderBase.LinkHandler(value);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (argv.includes("--force-quit")) {
|
|
||||||
console.warn('[InstanceHandler][SecondInstanceHandler] Force Quit found. Quitting App.');
|
|
||||||
// app.isQuiting = true
|
|
||||||
app.quit()
|
|
||||||
} else if (CiderBase.win && true) { // If a Second Instance has Been Started
|
|
||||||
console.warn('[InstanceHandler][SecondInstanceHandler] Showing window.');
|
|
||||||
app.win.show()
|
|
||||||
app.win.focus()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!app.requestSingleInstanceLock() && true) {
|
|
||||||
console.warn("[InstanceHandler] Existing Instance is Blocking Second Instance.");
|
|
||||||
app.quit();
|
|
||||||
// app.isQuiting = true
|
|
||||||
}
|
|
61
package.json
|
@ -4,8 +4,9 @@
|
||||||
"productName": "Cider",
|
"productName": "Cider",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "A new look into listening and enjoying music in style and performance.",
|
"description": "A new look into listening and enjoying music in style and performance.",
|
||||||
"license": "MIT",
|
"license": "AGPL-3.0",
|
||||||
"author": "Cider Collective <cryptofyre@cryptofyre.org> (https://cider.sh)",
|
"main": "./build/index.js",
|
||||||
|
"author": "Cider Collective <cryptofyre@cider.sh> (https://cider.sh)",
|
||||||
"repository": "https://github.com/ciderapp/Cider.git",
|
"repository": "https://github.com/ciderapp/Cider.git",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/ciderapp/Cider/issues?q=is%3Aopen+is%3Aissue+label%3Abug"
|
"url": "https://github.com/ciderapp/Cider/issues?q=is%3Aopen+is%3Aissue+label%3Abug"
|
||||||
|
@ -13,10 +14,14 @@
|
||||||
"homepage": "https://cider.sh/",
|
"homepage": "https://cider.sh/",
|
||||||
"buildResources": "resources",
|
"buildResources": "resources",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"init": "yarn install --force",
|
"build": "tsc",
|
||||||
"start": "electron . --enable-accelerated-mjpeg-decode --enable-accelerated-video --disable-gpu-driver-bug-workarounds --ignore-gpu-blacklist --enable-native-gpu-memory-buffers",
|
"watch": "tsc --watch",
|
||||||
|
"start": "run-script-os",
|
||||||
|
"start:win32": "npm run build && set ELECTRON_ENABLE_LOGGING=true && electron ./build/index.js --enable-accelerated-mjpeg-decode --enable-accelerated-video --disable-gpu-driver-bug-workarounds --ignore-gpu-blacklist --enable-native-gpu-memory-buffers",
|
||||||
|
"start:linux": "npm run build && export ELECTRON_ENABLE_LOGGING=true && electron ./build/index.js --enable-accelerated-mjpeg-decode --enable-accelerated-video --disable-gpu-driver-bug-workarounds --ignore-gpu-blacklist --enable-native-gpu-memory-buffers",
|
||||||
|
"start:darwin": "npm run build && export ELECTRON_ENABLE_LOGGING=true && electron ./build/index.js --enable-accelerated-mjpeg-decode --enable-accelerated-video --disable-gpu-driver-bug-workarounds --ignore-gpu-blacklist --enable-native-gpu-memory-buffers",
|
||||||
"pack": "electron-builder --dir",
|
"pack": "electron-builder --dir",
|
||||||
"dist": "electron-builder",
|
"dist": "npm run build && electron-builder",
|
||||||
"msft": "electron-builder -c msft-package.json",
|
"msft": "electron-builder -c msft-package.json",
|
||||||
"postinstall": "electron-builder install-app-deps"
|
"postinstall": "electron-builder install-app-deps"
|
||||||
},
|
},
|
||||||
|
@ -25,29 +30,39 @@
|
||||||
"discord-rpc": "^4.0.1",
|
"discord-rpc": "^4.0.1",
|
||||||
"ejs": "^3.1.6",
|
"ejs": "^3.1.6",
|
||||||
"electron-fetch": "^1.7.4",
|
"electron-fetch": "^1.7.4",
|
||||||
"electron-log": "^4.4.3",
|
"electron-log": "^4.4.4",
|
||||||
|
"electron-notarize": "^1.1.1",
|
||||||
|
"electron-packager": "^15.4.0",
|
||||||
"electron-store": "^8.0.1",
|
"electron-store": "^8.0.1",
|
||||||
"electron-updater": "^4.6.1",
|
"electron-updater": "^4.6.1",
|
||||||
"electron-window-state": "^5.0.3",
|
"electron-window-state": "^5.0.3",
|
||||||
"express": "^4.17.2",
|
"express": "^4.17.2",
|
||||||
"get-port": "^5.1.1",
|
"get-port": "^5.1.1",
|
||||||
|
"jsonc": "^2.0.0",
|
||||||
"lastfmapi": "^0.1.1",
|
"lastfmapi": "^0.1.1",
|
||||||
|
"mdns-js": "github:bitfocus/node-mdns-js",
|
||||||
"mpris-service": "^2.1.2",
|
"mpris-service": "^2.1.2",
|
||||||
"music-metadata": "^7.11.4",
|
"music-metadata": "^7.11.4",
|
||||||
|
"qrcode": "^1.5.0",
|
||||||
"qrcode-terminal": "^0.12.0",
|
"qrcode-terminal": "^0.12.0",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
|
"run-script-os": "^1.1.6",
|
||||||
"source-map-support": "^0.5.21",
|
"source-map-support": "^0.5.21",
|
||||||
"v8-compile-cache": "^2.3.0",
|
"v8-compile-cache": "^2.3.0",
|
||||||
"ws": "^8.3.0",
|
"ws": "^8.4.2",
|
||||||
"xml2js": "^0.4.23",
|
"xml2js": "^0.4.23",
|
||||||
"youtube-search-without-api-key": "^1.0.7"
|
"youtube-search-without-api-key": "^1.0.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/discord-rpc": "^4.0.0",
|
||||||
|
"@types/express": "^4.17.13",
|
||||||
|
"@types/ws": "^8.2.2",
|
||||||
"electron": "https://github.com/castlabs/electron-releases.git",
|
"electron": "https://github.com/castlabs/electron-releases.git",
|
||||||
"electron-builder": "^22.14.5",
|
"electron-builder": "^22.14.5",
|
||||||
"electron-webpack": "^2.8.2",
|
"electron-webpack": "^2.8.2",
|
||||||
"musickit-typescript": "^1.2.4",
|
"musickit-typescript": "^1.2.4",
|
||||||
|
"typescript": "^4.5.4",
|
||||||
"vue-devtools": "^5.1.4",
|
"vue-devtools": "^5.1.4",
|
||||||
"webpack": "~5.65.0"
|
"webpack": "~5.65.0"
|
||||||
},
|
},
|
||||||
|
@ -74,12 +89,14 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"build": {
|
"build": {
|
||||||
"electronVersion": "16.0.6",
|
"electronVersion": "16.0.7",
|
||||||
"electronDownload": {
|
"electronDownload": {
|
||||||
"version": "16.0.6+wvcus",
|
"version": "16.0.7+wvcus",
|
||||||
"mirror": "https://github.com/castlabs/electron-releases/releases/download/v"
|
"mirror": "https://github.com/castlabs/electron-releases/releases/download/v"
|
||||||
},
|
},
|
||||||
"appId": "cider",
|
"appId": "cider",
|
||||||
|
"afterPack": "./resources/afterPack.js",
|
||||||
|
"afterSign": "./resources/notarize.js",
|
||||||
"protocols": [
|
"protocols": [
|
||||||
{
|
{
|
||||||
"name": "Cider",
|
"name": "Cider",
|
||||||
|
@ -95,9 +112,9 @@
|
||||||
],
|
],
|
||||||
"extends": null,
|
"extends": null,
|
||||||
"files": [
|
"files": [
|
||||||
"**/*",
|
"./build/**/*",
|
||||||
"./src/**/*",
|
"./resources/icons/icon.*",
|
||||||
"./resources/icons/icon.*"
|
"./src/**/*"
|
||||||
],
|
],
|
||||||
"linux": {
|
"linux": {
|
||||||
"target": [
|
"target": [
|
||||||
|
@ -119,6 +136,13 @@
|
||||||
"backgroundColor": "transparent",
|
"backgroundColor": "transparent",
|
||||||
"setBuildNumber": true
|
"setBuildNumber": true
|
||||||
},
|
},
|
||||||
|
"nsis": {
|
||||||
|
"oneClick": false,
|
||||||
|
"perMachine": false,
|
||||||
|
"allowToChangeInstallationDirectory": true,
|
||||||
|
"license": "LICENSE",
|
||||||
|
"deleteAppDataOnUninstall": true
|
||||||
|
},
|
||||||
"win": {
|
"win": {
|
||||||
"target": [
|
"target": [
|
||||||
"nsis"
|
"nsis"
|
||||||
|
@ -126,13 +150,20 @@
|
||||||
"icon": "resources/icons/icon.ico"
|
"icon": "resources/icons/icon.ico"
|
||||||
},
|
},
|
||||||
"directories": {
|
"directories": {
|
||||||
"buildResources": "."
|
"buildResources": ".",
|
||||||
|
"output": "dist"
|
||||||
},
|
},
|
||||||
"mac": {
|
"mac": {
|
||||||
|
"hardenedRuntime": true,
|
||||||
|
"gatekeeperAssess": false,
|
||||||
"icon": "./resources/icons/icon.icns",
|
"icon": "./resources/icons/icon.icns",
|
||||||
"category": "public.app-category.music",
|
"category": "public.app-category.music",
|
||||||
"entitlements": "resources/entitlements.mac.plist",
|
"entitlements": "./resources/entitlements.mac.plist",
|
||||||
"darkModeSupport": true
|
"entitlementsInherit": "./resources/entitlements.mac.plist",
|
||||||
|
"darkModeSupport": true,
|
||||||
|
"target": [
|
||||||
|
"dmg"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
18
resources/afterPack.js
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
exports.default = function(context) {
|
||||||
|
const { execSync } = require('child_process')
|
||||||
|
|
||||||
|
if (process.platform !== 'darwin')
|
||||||
|
return
|
||||||
|
|
||||||
|
console.log('Castlabs-evs update start')
|
||||||
|
execSync('python3 -m pip install --upgrade castlabs-evs')
|
||||||
|
console.log('Castlabs-evs update complete')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
console.log('VMP signing start')
|
||||||
|
|
||||||
|
execSync('python3 -m castlabs_evs.vmp -n sign-pkg dist/mac',{stdio: 'inherit'})
|
||||||
|
|
||||||
|
console.log('VMP signing complete')
|
||||||
|
}
|
|
@ -2,11 +2,13 @@
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
|
<!-- https://github.com/electron/electron-notarize#prerequisites -->
|
||||||
|
<key>com.apple.security.cs.allow-jit</key>
|
||||||
|
<true/>
|
||||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<!-- https://github.cm/electron-userland/electron-builder/issues/3940 -->
|
||||||
<key>com.apple.security.cs.disable-library-validation</key>
|
<key>com.apple.security.cs.disable-library-validation</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
|
|
||||||
<false/>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
BIN
resources/icons/icon-test.ico
Normal file
After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 303 KiB After Width: | Height: | Size: 295 KiB |
20
resources/notarize.js
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
|
||||||
|
|
||||||
|
require('dotenv').config();
|
||||||
|
const { notarize } = require('electron-notarize');
|
||||||
|
|
||||||
|
exports.default = async function notarizing(context) {
|
||||||
|
const { electronPlatformName, appOutDir } = context;
|
||||||
|
if (electronPlatformName !== 'darwin') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const appName = context.packager.appInfo.productFilename;
|
||||||
|
|
||||||
|
return await notarize({
|
||||||
|
appBundleId: 'com.ciderapp.cider',
|
||||||
|
appPath: `${appOutDir}/${appName}.app`,
|
||||||
|
appleId: process.env.APPLEID,
|
||||||
|
appleIdPassword: process.env.APPLEIDPASS,
|
||||||
|
});
|
||||||
|
};
|
8
src/i18n/README.md
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# Cider i18n
|
||||||
|
Some notes about Cider's i18n support.
|
||||||
|
* Localization files are stored in jsonc format aka "JSON with Comments"
|
||||||
|
* The default language is English.
|
||||||
|
* The default language is used for messages that are not translated.
|
||||||
|
* Try when possible to keep the messages the similar in length to the English ones.
|
||||||
|
* Most of the strings in the content area are provided and translated by Apple themselves, and do not need to be translated.
|
||||||
|
* The language Apple Music uses are dependent on the storefront region.
|
279
src/i18n/el_GR.jsonc
Normal file
|
@ -0,0 +1,279 @@
|
||||||
|
{
|
||||||
|
|
||||||
|
// i18n Info
|
||||||
|
"i18n.languageName": "Ελληνικά",
|
||||||
|
"i18n.languageNameEnglish": "Greek",
|
||||||
|
"i18n.category": "main",
|
||||||
|
"i18n.authors": "@down-bad",
|
||||||
|
|
||||||
|
// App info
|
||||||
|
"app.name": "Cider",
|
||||||
|
|
||||||
|
"date.format": "${d} ${m}, ${y}",
|
||||||
|
|
||||||
|
// Dialogs
|
||||||
|
"dialog.cancel": "Ακύρωση",
|
||||||
|
"dialog.ok": "ΟΚ",
|
||||||
|
|
||||||
|
// Notification
|
||||||
|
"notification.updatingLibrarySongs": "Ενημέρωση βιβλιοθήκης τραγουδιών...",
|
||||||
|
"notification.updatingLibraryAlbums": "Ενημέρωση βιβλιοθήκης άλμπουμ...",
|
||||||
|
"notification.updatingLibraryArtists": "Ενημέρωση βιβλιοθήκης καλλιτεχνών...",
|
||||||
|
// Terms
|
||||||
|
"term.appleInc": "Apple Inc.",
|
||||||
|
"term.appleMusic": "Apple Music",
|
||||||
|
"term.applePodcasts": "Apple Podcasts",
|
||||||
|
"term.itunes": "iTunes",
|
||||||
|
"term.github": "GitHub",
|
||||||
|
"term.discord": "Discord",
|
||||||
|
"term.learnMore": "Μάθετε περισσότερα",
|
||||||
|
"term.accountSettings": "Ρυθμίσεις λογαριασμού",
|
||||||
|
"term.logout": "Αποσύνδεση",
|
||||||
|
"term.login": "Σύνδεση",
|
||||||
|
"term.about": "Σχετικά με",
|
||||||
|
"term.privateSession": "Ιδιωτική περίοδος λειτουργίας",
|
||||||
|
"term.queue": "Ουρά",
|
||||||
|
"term.search": "Εύρεση",
|
||||||
|
"term.library": "Βιβλιοθήκη",
|
||||||
|
"term.listenNow": "Ακρόαση",
|
||||||
|
"term.browse": "Περιήγηση",
|
||||||
|
"term.radio": "Ράδιο",
|
||||||
|
"term.recentlyAdded": "Πρόσφατες προσθήκες",
|
||||||
|
"term.songs": "Τραγούδια",
|
||||||
|
"term.albums": "Άλμπουμ",
|
||||||
|
"term.artists": "Καλλιτέχνες",
|
||||||
|
"term.podcasts": "Podcast",
|
||||||
|
"term.playlists": "Λίστες αναπαραγωγής",
|
||||||
|
"term.playlist": "Λίστα αναπαραγωγής",
|
||||||
|
"term.play": "Αναπαραγωγή",
|
||||||
|
"term.pause": "Παύση",
|
||||||
|
"term.previous": "Προηγούμενο",
|
||||||
|
"term.next": "Επόμενο",
|
||||||
|
"term.shuffle": "Τυχαία σειρά",
|
||||||
|
"term.repeat": "Επανάληψη",
|
||||||
|
"term.volume": "Ένταση",
|
||||||
|
"term.mute": "Σίγαση",
|
||||||
|
"term.unmute": "Κατάργηση σίγασης",
|
||||||
|
"term.share": "Κοινή Χρήση",
|
||||||
|
"term.settings": "Ρυθμίσεις",
|
||||||
|
"term.seeAll": "Προβολή όλων",
|
||||||
|
"term.sortBy": "Ταξινόμηση κατά",
|
||||||
|
"term.sortBy.album": "Άλμπουμ",
|
||||||
|
"term.sortBy.artist": "Καλλιτέχνη",
|
||||||
|
"term.sortBy.name": "Όνομα",
|
||||||
|
"term.sortBy.genre": "Είδος",
|
||||||
|
"term.sortBy.releaseDate": "Ημερομηνία κυκλοφορίας",
|
||||||
|
"term.sortBy.duration": "Διάρκεια",
|
||||||
|
"term.sortOrder": "Α-Ω",
|
||||||
|
"term.sortOrder.ascending": "Αύξουσα",
|
||||||
|
"term.sortOrder.descending": "Φθίνουσα",
|
||||||
|
"term.viewAs": "Προβολή ως",
|
||||||
|
"term.viewAs.coverArt": "Εξώφυλλο",
|
||||||
|
"term.viewAs.list": "Λίστα",
|
||||||
|
"term.size": "Μέγεθος",
|
||||||
|
"term.size.normal": "Κανονικό",
|
||||||
|
"term.size.compact": "Συμπαγή",
|
||||||
|
"term.enable": "Ενεργοποίηση",
|
||||||
|
"term.disable": "Απενεργοποίηση",
|
||||||
|
"term.enabled": "Ενεργοποιημένο",
|
||||||
|
"term.disabled": "Απενεργοποιημένο",
|
||||||
|
"term.connect": "Σύνδεση",
|
||||||
|
"term.connecting": "Γίνεται σύνδεση",
|
||||||
|
"term.disconnect": "Αποσύνδεση",
|
||||||
|
"term.authed": "Επικυρωμένο",
|
||||||
|
"term.confirm": "Σίγουρα;",
|
||||||
|
"term.more": "Περισσότερα",
|
||||||
|
"term.less": "Λιγότερα",
|
||||||
|
"term.showMore": "Εμφάνιση περισσότερων",
|
||||||
|
"term.showLess": "Εμφάνιση λιγότερων",
|
||||||
|
"term.topSongs" : "Κορυφαία τραγούδια",
|
||||||
|
"term.latestReleases": "Τελευταίες κυκλοφορίες",
|
||||||
|
"term.time.added": "Προστέθηκε",
|
||||||
|
"term.time.released": "Κυκλοφόρησε",
|
||||||
|
"term.time.updated": "Ενημερώθηκε",
|
||||||
|
"term.fullscreenView": "Πλήρης οθόνη",
|
||||||
|
"term.defaultView": "Κανονική οθόνη",
|
||||||
|
"term.spacializedAudioSetting": "Χωρική ρύθμιση ήχου",
|
||||||
|
"term.clearAll": "Εκκαθάριση όλων",
|
||||||
|
"term.recentStations": "Πρόσφατοι σταθμοί",
|
||||||
|
"term.language": "Γλώσσα",
|
||||||
|
"term.noLyrics": "Φόρτωση... / Δεν βρέθηκαν στίχοι. / Ορχηστικό.",
|
||||||
|
"term.copyright": "Copyright",
|
||||||
|
"term.rightsReserved": "Όλα τα δικαιώματα διατηρούνται.",
|
||||||
|
"term.sponsor": "Χορήγησε αυτό το έργο",
|
||||||
|
"term.ciderTeam": "Ομάδα Cider",
|
||||||
|
"term.developer": "Προγραμματιστής",
|
||||||
|
"term.socialTeam": "Κοινωνική Ομάδα",
|
||||||
|
"term.contributors": "Συνεισφέροντες",
|
||||||
|
"term.equalizer": "Ισοσταθμιστής",
|
||||||
|
"term.reset": "Επαναφορά",
|
||||||
|
"term.tracks": "τραγούδια", // Assume x amount of tracks. e.g. 50 tracks
|
||||||
|
|
||||||
|
|
||||||
|
// Home
|
||||||
|
"home.title": "Αρχική",
|
||||||
|
"home.recentlyPlayed": "Έπαιξαν πρόσφατα",
|
||||||
|
"home.recentlyAdded": "Πρόσφατες προσθήκες",
|
||||||
|
"home.artistsFeed": "Ροή των καλλιτεχνών σου",
|
||||||
|
"home.artistsFeed.noArtist": "Ακολούθησε μερικούς καλλιτέχνες πρώτα και οι τελευταίες κυκλοφορίες τους θα εμφανίζονται εδώ",
|
||||||
|
"home.madeForYou": "Δημιουργήθηκε για εσάς",
|
||||||
|
"home.friendsListeningTo": "Οι φίλοι σου ακούν",
|
||||||
|
"home.followedArtists": "Καλλιτέχνες που ακολουθείτε",
|
||||||
|
// Errors
|
||||||
|
"error.appleMusicSubRequired": "Το Apple Music απαιτεί μια συνδρομή.",
|
||||||
|
"error.connectionError": "Δεν είναι δυνατή η σύνδεση με το Apple Music.",
|
||||||
|
"error.noResults": "Κανένα αποτέλεσμα.",
|
||||||
|
"error.noResults.description": "Δοκιμάστε μια νέα αναζήτηση.",
|
||||||
|
|
||||||
|
//Podcasts
|
||||||
|
"podcast.followOnCider": "Ακολούθηση στο Cider",
|
||||||
|
"podcast.followedOnCider": "Ακολουθείτε στο Cider",
|
||||||
|
"podcast.subscribeOnItunes": "Συνδρομή στο iTunes",
|
||||||
|
"podcast.subscribedOnItunes": "Συνδρομητής στο iTunes",
|
||||||
|
"podcast.itunesStore": "iTunes Store",
|
||||||
|
"podcast.episodes": "Επεισόδια",
|
||||||
|
"podcast.playEpisode": "Αναπαραγωγή επεισοδίου",
|
||||||
|
"podcast.website": "Ιστότοπος Podcast",
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
"action.addToLibrary": "Προσθήκη στη βιβλιοθήκη",
|
||||||
|
"action.addToLibrary.success": "Προστέθηκε στη βιβλιοθήκη",
|
||||||
|
"action.addToLibrary.error": "Σφάλμα Προσθήκης στη βιβλιοθήκης",
|
||||||
|
"action.removeFromLibrary": "Αφαίρεση από τη βιβλιοθήκη",
|
||||||
|
"action.removeFromLibrary.success": "Αφαιρέθηκε από τη βιβλιοθήκη",
|
||||||
|
"action.addToQueue": "Προσθήκη στην ουρά",
|
||||||
|
"action.addToQueue.success": "Προστέθηκε στην ουρά",
|
||||||
|
"action.addToQueue.error": "Προστέθηκε στην ουρά",
|
||||||
|
"action.removeFromQueue": "Αφαίρεση από την ουρά",
|
||||||
|
"action.removeFromQueue.success": "Αφαιρέθηκε από την ουρά",
|
||||||
|
"action.removeFromQueue.error": "Σφάλμα Αφαίρεσης από την ουρά",
|
||||||
|
"action.addToPlaylist": "Προσθήκη σε λίστα",
|
||||||
|
"action.removeFromPlaylist": "Αφαίρεση από λίστα",
|
||||||
|
"action.addToFavorites": "Προσθήκη στα αγαπημένα",
|
||||||
|
"action.follow": "Ακολούθηση",
|
||||||
|
"action.follow.success": "Ακολουθήθηκε",
|
||||||
|
"action.follow.error": "Σφάλμα ακολούθησης",
|
||||||
|
"action.unfollow": "Διακοπή ακολούθησης",
|
||||||
|
"action.unfollow.success": "Έγινε διακοπή ακολούθησης",
|
||||||
|
"action.unfollow.error": "Σφάλμα διακοπής ακολούθησης ",
|
||||||
|
"action.playNext": "Αναπαραγωγή ως επόμενου",
|
||||||
|
"action.playLater": "Αναπαραγωγή αργότερα",
|
||||||
|
"action.startRadio": "Έναρξη ραδιοφώνου",
|
||||||
|
"action.goToArtist": "Μετάβαση σε καλλιτέχνη",
|
||||||
|
"action.goToAlbum": "Μετάβαση σε άλμπουμ",
|
||||||
|
"action.moveToTop": "Μετακίνηση στη κορυφή",
|
||||||
|
"action.share": "Κοινή χρήση",
|
||||||
|
"action.rename": "Μετονομασία",
|
||||||
|
"action.love": "Μου αρέσει πολύ",
|
||||||
|
"action.unlove": "Αναίρεση \"Μου αρέσει\"",
|
||||||
|
"action.dislike": "Δεν μου αρέσει",
|
||||||
|
"action.undoDislike": "Αναίρεση \"Δεν μου αρέσει\"",
|
||||||
|
"action.showWebRemoteQR": "Εμφάνιση Web Remote QR",
|
||||||
|
"action.playTracksNext": "Αναπαραγωγή ${app.selectedMediaItems.length} τραγουδιών ως επόμενων",
|
||||||
|
"action.playTracksLater": "Αναπαραγωγή ${app.selectedMediaItems.length} τραγουδιών αργότερα",
|
||||||
|
"action.removeTracks": "Αφαίρεση ${self.selectedItems.length} τραγουδιών από την ουρά",
|
||||||
|
|
||||||
|
// Settings - Audio
|
||||||
|
"settings.header.audio": "Ήχος",
|
||||||
|
"settings.header.audio.description": "Προσαρμογή ρυθμίσεων ήχου για το Cider.",
|
||||||
|
"settings.option.audio.quality": "Ποιότητα Ήχου", // Dropdown
|
||||||
|
"settings.header.audio.quality.high": "Υψηλή",
|
||||||
|
"settings.header.audio.quality.low": "Χαμηλή",
|
||||||
|
"settings.header.audio.quality.auto": "Αυτόματη",
|
||||||
|
"settings.option.audio.seamlessTransition": "Αδιάκοπη Μετάβαση Ήχου", // Toggle
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality": "Ενεργοποίηση Προηγμένης Λειτουργικότητας", // Toggle
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.description": "Ενεργοποιώντας τη λειτουργικότητα AudioContext θα επιτρέψει σε επεκταμένες δυνατότητες ήχου όπως Κανονικοποίηση Έντασης Ήχου, Ισοσταθμιστές και Οπτικοποιητές, ωστόσο σε κάποια συστήματα μπορεί να προκαλέσει τραύλισμα ήχου.",
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.audioNormalization": "Κανονικοποίηση Έντασης Ήχου", // Toggle
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.audioNormalization.description": "Κανονικοποιεί την ένταση για μεμονωμένα κομμάτια για μια πιο ομοιόμορφη εμπειρία ακρόασης.",
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization": "Χωρικοποίηση Ήχου", // Toggle
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization.description": "Πιο τρισδιάστατος και χωρικοποιημένος ήχος (σημείωση: Αυτό δεν είναι Dolby Atmos)",
|
||||||
|
// Settings - Visual
|
||||||
|
"settings.header.visual": "Οπτικά",
|
||||||
|
"settings.header.visual.description": "Προσαρμογή οπτικών ρυθμίσεων για το Cider.",
|
||||||
|
"settings.option.visual.windowBackgroundStyle": "Στυλ Φόντου Παραθύρου", // Toggle
|
||||||
|
"settings.header.visual.windowBackgroundStyle.none": "Κανένα",
|
||||||
|
"settings.header.visual.windowBackgroundStyle.artwork": "Εξώφυλλο",
|
||||||
|
"settings.option.visual.animatedArtwork": "Κινούμενο Εξώφυλλο", // Dropdown
|
||||||
|
"settings.header.visual.animatedArtwork.always": "Πάντα",
|
||||||
|
"settings.header.visual.animatedArtwork.limited": "Περιορισμός σε σελίδες και ειδικές καταχωρήσεις",
|
||||||
|
"settings.header.visual.animatedArtwork.disable": "Απενεργοποιημένο παντού",
|
||||||
|
"settings.option.visual.animatedArtworkQuality": "Ποιότητα Κινούμενου Εξωφύλλου", // Dropdown
|
||||||
|
"settings.header.visual.animatedArtworkQuality.low": "Χαμηλή",
|
||||||
|
"settings.header.visual.animatedArtworkQuality.medium": "Μέτρια",
|
||||||
|
"settings.header.visual.animatedArtworkQuality.high": "Υψηλή",
|
||||||
|
"settings.header.visual.animatedArtworkQuality.veryHigh": "Πολύ Υψηλή",
|
||||||
|
"settings.header.visual.animatedArtworkQuality.extreme": "Ακραία",
|
||||||
|
"settings.option.visual.animatedWindowBackground": "Κινούμενο Φόντο Παραθύρου", // Toggle
|
||||||
|
"settings.option.visual.hardwareAcceleration": "Επιτάχυνση Υλικού", // Dropdown
|
||||||
|
"settings.option.visual.hardwareAcceleration.description": "Απαιτεί επανεκκίνηση",
|
||||||
|
"settings.header.visual.hardwareAcceleration.default": "Προεπιλογή",
|
||||||
|
"settings.header.visual.hardwareAcceleration.webGPU": "WebGPU",
|
||||||
|
// Refer to term.disabled for the disabled option
|
||||||
|
"settings.option.visual.showPersonalInfo": "Εμφάνιση Προσωπικών Στοιχείων", // Toggle
|
||||||
|
// Settings - General (Reserved)
|
||||||
|
"settings.header.general": "Γενικά",
|
||||||
|
"settings.header.general.description": "Προσαρμογή γενικών ρυθμίσεων για το Cider.",
|
||||||
|
|
||||||
|
// Settings - Lyrics
|
||||||
|
"settings.header.lyrics": "Στίχοι",
|
||||||
|
"settings.header.lyrics.description": "Προσαρμογή ρυθμίσεων στίχων για το Cider.",
|
||||||
|
"settings.option.lyrics.enableMusixmatch": "Ενεργοποίηση Στίχων Musixmatch", // Toggle
|
||||||
|
"settings.option.lyrics.enableMusixmatchKaraoke": "Ενεργοποίηση Λειτουργίας Καραόκε (Musixmatch μόνο)", // Toggle
|
||||||
|
"settings.option.lyrics.musixmatchPreferredLanguage": "Προτιμώμενη Γλώσσα Μετάφρασης Musixmatch", // Dropdown
|
||||||
|
"settings.option.lyrics.enableYoutubeLyrics": "Ενεργοποίηση Στίχων Youtube για Μουσικά Βίντεο", // Toggle
|
||||||
|
|
||||||
|
// Settings - Connectivity
|
||||||
|
"settings.header.connectivity": "Σύνδεση",
|
||||||
|
"settings.header.connectivity.description": "Προσαρμογή ρυθμίσεων σύνδεσης για το Cider.",
|
||||||
|
"settings.option.connectivity.discordRPC": "Discord Rich Presence", // Dropdown
|
||||||
|
// Refer to term.disabled for the disabled option
|
||||||
|
"settings.header.connectivity.discordRPC.cider": "Εμφάνιση ως 'Cider'",
|
||||||
|
"settings.header.connectivity.discordRPC.appleMusic": "Εμφάνιση ως 'Apple Music'",
|
||||||
|
"settings.option.connectivity.discordRPC.clearOnPause": "Εκκαθάριση του Discord Rich Presence στην Παύση", // Toggle
|
||||||
|
"settings.option.connectivity.lastfmScrobble": "LastFM Scrobbling", // Option to Connect
|
||||||
|
"settings.option.connectivity.lastfmScrobble.delay": "Καθυστέρηση LastFM Scrobble (%)",
|
||||||
|
"settings.option.connectivity.lastfmScrobble.nowPlaying": "Ενεργοποίηση LastFM \"Now Playing\"",
|
||||||
|
"settings.option.connectivity.lastfmScrobble.removeFeatured": "Αφαίρεση καλλιτεχνών feature από τον τίτλο του τραγουδιού (LastFM)",
|
||||||
|
"settings.option.connectivity.lastfmScrobble.filterLoop": "Φιλτράρισμα επανειλημμένου τραγουδιού (LastFM)",
|
||||||
|
// Refer to term.connect for the connect button
|
||||||
|
|
||||||
|
// Settings - Experimental
|
||||||
|
"settings.header.experimental": "Πειραματικές",
|
||||||
|
"settings.header.experimental.description": "Προσαρμογή πειραματικών ρυθμίσεων για το Cider.",
|
||||||
|
"settings.option.experimental.compactUI": "Συμπαγής Διεπαφή", // Toggle
|
||||||
|
"settings.option.experimental.closeButtonBehaviour": "Συμπεριφορά Κουμπιού Εξόδου",
|
||||||
|
"settings.option.experimental.closeButtonBehaviour.quit": "Έξοδος του Cider",
|
||||||
|
"settings.option.experimental.closeButtonBehaviour.minimizeTaskbar": "Ελαχιστοποίηση στη γραμμή εργασιών",
|
||||||
|
"settings.option.experimental.closeButtonBehaviour.minimizeTray": "Ελαχιστοποίηση στη γωνία γραμμής εργασιών",
|
||||||
|
// Refer to term.disabled & term.enabled
|
||||||
|
|
||||||
|
// Spatialization Menu
|
||||||
|
"spatial.spatialProperties" : "Χωρικές Ιδιότητες",
|
||||||
|
"spatial.width" : "Πλάτος",
|
||||||
|
"spatial.height" : "Ύψος",
|
||||||
|
"spatial.depth" : "Βάθος",
|
||||||
|
"spatial.gain" : "Απολαβή",
|
||||||
|
"spatial.roomMaterials" : "Υλικά Δωματίου",
|
||||||
|
"spatial.roomDimensions" : "Διαστάσεις Δωματίου",
|
||||||
|
"spatial.roomPositions" : "Θέσεις Δωματίου",
|
||||||
|
"spatial.setDimensions" : "Ορισμός Διαστάσεων",
|
||||||
|
"spatial.setPositions" : "Ορισμός Θέσεων",
|
||||||
|
"spatial.up" : "Πάνω",
|
||||||
|
"spatial.front" : "Πρόσοψη",
|
||||||
|
"spatial.left" : "Αριστερά",
|
||||||
|
"spatial.right" : "Δεξιά",
|
||||||
|
"spatial.back" : "Πίσω Όψη",
|
||||||
|
"spatial.down" : "Κάτω",
|
||||||
|
"spatial.listener" : "Ακροατής",
|
||||||
|
"spatial.audioSource" : "Πηγή Ήχου",
|
||||||
|
|
||||||
|
// Settings - Unfinished
|
||||||
|
"settings.header.unfinished": "Ημιτελής",
|
||||||
|
|
||||||
|
// Web Remote
|
||||||
|
"remote.web.title": "Cider Remote",
|
||||||
|
"remote.web.description": "Σαρώστε τον κωδικό QR για σύζευξη του Cider με το κινητό σας",
|
||||||
|
|
||||||
|
//About
|
||||||
|
"about.thanks": "Μεγάλα ευχαριστώ στην Ομάδα Cider Collective και σε όλους τους συνεισφέροντές μας."
|
||||||
|
}
|
10
src/i18n/en_GB.jsonc
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
// i18n Info
|
||||||
|
"i18n.languageName": "English (UK)", // name of language in native language
|
||||||
|
"i18n.languageNameEnglish": "English (UK)", // name of language in English
|
||||||
|
"i18n.category": "main", // main = real language, fun = fun community languages
|
||||||
|
"i18n.authors": "", // Authors, if you contribute to this file feel free to add your name seperated with a space
|
||||||
|
"date.format": "${d} ${m}, ${y}",
|
||||||
|
|
||||||
|
"home.friendsListeningTo": "Bruv's Listening To"
|
||||||
|
}
|
291
src/i18n/en_HODOR.jsonc
Normal file
|
@ -0,0 +1,291 @@
|
||||||
|
{ // HODOR
|
||||||
|
|
||||||
|
// i18n Info
|
||||||
|
"i18n.languageName": "HODOR", // name of language in native language
|
||||||
|
"i18n.languageNameEnglish": "HODOR", // name of language in English
|
||||||
|
"i18n.category": "fun", // main = real language, fun = fun community languages
|
||||||
|
"i18n.authors": "HODOR", // Authors, if you contribute to this file feel free to add your name seperated with a space
|
||||||
|
|
||||||
|
// App info
|
||||||
|
"app.name": "HODOR",
|
||||||
|
|
||||||
|
"date.format": "${m} ${d}, ${y}",
|
||||||
|
|
||||||
|
// Dialogs
|
||||||
|
"dialog.cancel": "HODOR",
|
||||||
|
"dialog.ok": "HODOR",
|
||||||
|
|
||||||
|
// Notification
|
||||||
|
"notification.updatingLibrarySongs": "HODOR HODOR HODOR...",
|
||||||
|
"notification.updatingLibraryAlbums": "HODOR HODOR HODOR...",
|
||||||
|
"notification.updatingLibraryArtists": "HODOR HODOR HODOR...",
|
||||||
|
// Terms
|
||||||
|
"term.appleInc": "HODOR Inc.",
|
||||||
|
"term.appleMusic": "HODOR HODOR",
|
||||||
|
"term.applePodcasts": "HODOR HODOR",
|
||||||
|
"term.itunes": "HODOR",
|
||||||
|
"term.github": "HODOR",
|
||||||
|
"term.discord": "HODOR",
|
||||||
|
"term.learnMore": "HODOR HODOR",
|
||||||
|
"term.accountSettings": "HODOR HODOR",
|
||||||
|
"term.logout": "HODOR",
|
||||||
|
"term.login": "HODOR",
|
||||||
|
"term.about": "HODOR",
|
||||||
|
"term.privateSession": "HODOR HODOR",
|
||||||
|
"term.queue": "HODOR",
|
||||||
|
"term.search": "HODOR",
|
||||||
|
"term.library": "HODOR",
|
||||||
|
"term.listenNow": "HODOR HODOR",
|
||||||
|
"term.browse": "HODOR",
|
||||||
|
"term.radio": "HODOR",
|
||||||
|
"term.recentlyAdded": "HODOR HODOR",
|
||||||
|
"term.songs": "HODOR",
|
||||||
|
"term.albums": "HODOR",
|
||||||
|
"term.artists": "HODOR",
|
||||||
|
"term.podcasts": "HODOR",
|
||||||
|
"term.playlists": "HODOR",
|
||||||
|
"term.playlist": "HODOR",
|
||||||
|
"term.play": "HODOR",
|
||||||
|
"term.pause": "HODOR",
|
||||||
|
"term.previous": "HODOR",
|
||||||
|
"term.next": "HODOR",
|
||||||
|
"term.shuffle": "HODOR",
|
||||||
|
"term.repeat": "HODOR",
|
||||||
|
"term.volume": "HODOR",
|
||||||
|
"term.mute": "HODOR",
|
||||||
|
"term.unmute": "HODOR",
|
||||||
|
"term.share": "HODOR",
|
||||||
|
"term.settings": "HODOR",
|
||||||
|
"term.seeAll": "HODOR HODOR",
|
||||||
|
"term.sortBy": "HODOR HODOR",
|
||||||
|
"term.sortBy.album": "HODOR",
|
||||||
|
"term.sortBy.artist": "HODOR",
|
||||||
|
"term.sortBy.name": "HODOR",
|
||||||
|
"term.sortBy.genre": "HODOR",
|
||||||
|
"term.sortBy.releaseDate": "HODOR HODOR",
|
||||||
|
"term.sortBy.duration": "HODOR",
|
||||||
|
"term.sortOrder": "HODOR-HODOR",
|
||||||
|
"term.sortOrder.ascending": "HODOR",
|
||||||
|
"term.sortOrder.descending": "HODOR",
|
||||||
|
"term.viewAs": "HODOR HODOR",
|
||||||
|
"term.viewAs.coverArt": "HODOR HODOR",
|
||||||
|
"term.viewAs.list": "HODOR",
|
||||||
|
"term.size": "HODOR",
|
||||||
|
"term.size.normal": "HODOR",
|
||||||
|
"term.size.compact": "HODOR",
|
||||||
|
"term.enable": "HODOR",
|
||||||
|
"term.disable": "HODOR",
|
||||||
|
"term.enabled": "HODOR",
|
||||||
|
"term.disabled": "HODOR",
|
||||||
|
"term.connect": "HODOR",
|
||||||
|
"term.connecting": "HODOR",
|
||||||
|
"term.disconnect": "HODOR",
|
||||||
|
"term.authed": "HODOR",
|
||||||
|
"term.confirm": "HODOR ?",
|
||||||
|
"term.more": "HODOR",
|
||||||
|
"term.less": "HODOR",
|
||||||
|
"term.showMore": "HODOR HODOR",
|
||||||
|
"term.showLess": "HODOR HODOR",
|
||||||
|
"term.topSongs" : "HODOR HODOR",
|
||||||
|
"term.latestReleases": "HODOR HODOR",
|
||||||
|
"term.time.added": "HODOR",
|
||||||
|
"term.time.released": "HODOR",
|
||||||
|
"term.time.updated": "HODOR",
|
||||||
|
"term.fullscreenView": "HODOR HODOR",
|
||||||
|
"term.defaultView": "HODOR HODOR",
|
||||||
|
"term.spacializedAudioSetting": "HODOR HODOR HODOR",
|
||||||
|
"term.clearAll": "HODOR HODOR",
|
||||||
|
"term.recentStations": "HODOR HODOR",
|
||||||
|
"term.language": "HODOR",
|
||||||
|
"term.noLyrics": "HODOR... / HODOR HODOR HODOR./ HODOR.",
|
||||||
|
"term.copyright": "HODOR",
|
||||||
|
"term.rightsReserved": "HODOR HODOR HODOR.",
|
||||||
|
"term.sponsor": "HODOR HODOR HODOR",
|
||||||
|
"term.ciderTeam": "HODOR HODOR",
|
||||||
|
"term.developer": "HODOR",
|
||||||
|
"term.socialTeam": "HODOR HODOR",
|
||||||
|
"term.contributors": "HODOR",
|
||||||
|
"term.equalizer": "HODOR",
|
||||||
|
"term.reset": "HODOR",
|
||||||
|
"term.tracks": "HODOR", // Assume x amount of tracks. e.g. 50 tracks
|
||||||
|
"term.time.hours": "HODOR",
|
||||||
|
"term.time.hour": "HODOR",
|
||||||
|
"term.time.minutes": "HODOR",
|
||||||
|
"term.time.minute": "HODOR",
|
||||||
|
"term.time.seconds": "HODOR",
|
||||||
|
"term.time.second": "HODOR",
|
||||||
|
"term.funLanguages": "HODOR",
|
||||||
|
|
||||||
|
// Home
|
||||||
|
"home.title": "HODOR",
|
||||||
|
"home.recentlyPlayed": "HODOR HODOR",
|
||||||
|
"home.recentlyAdded": "HODOR HODOR",
|
||||||
|
"home.artistsFeed": "HODOR HODOR HODOR",
|
||||||
|
"home.artistsFeed.noArtist": "HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR",
|
||||||
|
"home.madeForYou": "HODOR HODOR HODOR",
|
||||||
|
"home.friendsListeningTo": "HODOR HODOR HODOR",
|
||||||
|
"home.followedArtists": "HODOR HODOR",
|
||||||
|
// Errors
|
||||||
|
"error.appleMusicSubRequired": "HODOR HODOR HODOR HODOR HODOR.",
|
||||||
|
"error.connectionError": "HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR.",
|
||||||
|
"error.noResults": "HODOR HODOR.",
|
||||||
|
"error.noResults.description": "HODOR HODOR HODOR HODOR.",
|
||||||
|
|
||||||
|
//Podcasts
|
||||||
|
"podcast.followOnCider": "HODOR HODOR HODOR",
|
||||||
|
"podcast.followedOnCider": "HODOR HODOR HODOR",
|
||||||
|
"podcast.subscribeOnItunes": "HODOR HODOR HODOR",
|
||||||
|
"podcast.subscribedOnItunes": "HODOR HODOR HODOR",
|
||||||
|
"podcast.itunesStore": "HODOR HODOR",
|
||||||
|
"podcast.episodes": "HODOR",
|
||||||
|
"podcast.playEpisode": "HODOR HODOR",
|
||||||
|
"podcast.website": "HODOR HODOR",
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
"action.addToLibrary": "HODOR HODOR HODOR",
|
||||||
|
"action.addToLibrary.success": "HODOR HODOR HODOR",
|
||||||
|
"action.addToLibrary.error": "HODOR HODOR HODOR LiHODORbrary",
|
||||||
|
"action.removeFromLibrary": "HODOR HODOR HODOR",
|
||||||
|
"action.removeFromLibrary.success": "HODOR HODOR HODOR",
|
||||||
|
"action.addToQueue": "HODOR HODOR HODOR",
|
||||||
|
"action.addToQueue.success": "HODOR HODOR HODOR",
|
||||||
|
"action.addToQueue.error": "HODOR HODOR HODOR HODOR",
|
||||||
|
"action.removeFromQueue": "HODOR HODOR HODOR",
|
||||||
|
"action.removeFromQueue.success": "HODOR HODOR HODOR",
|
||||||
|
"action.removeFromQueue.error": "HODOR HODOR HODOR HODOR",
|
||||||
|
"action.addToPlaylist": "HODOR HODOR HODOR",
|
||||||
|
"action.removeFromPlaylist": "HODOR HODOR HODOR",
|
||||||
|
"action.addToFavorites": "HODOR HODOR HODOR",
|
||||||
|
"action.follow": "HODOR",
|
||||||
|
"action.follow.success": "HODOR",
|
||||||
|
"action.follow.error": "HODOR HODOR",
|
||||||
|
"action.unfollow": "HODOR",
|
||||||
|
"action.unfollow.success": "HODOR",
|
||||||
|
"action.unfollow.error": "HODOR HODOR",
|
||||||
|
"action.playNext": "HODOR HODOR",
|
||||||
|
"action.playLater": "HODOR HODOR",
|
||||||
|
"action.startRadio": "HODOR HODOR",
|
||||||
|
"action.goToArtist": "HODOR HODOR HODOR",
|
||||||
|
"action.goToAlbum": "HODOR HODOR HODOR",
|
||||||
|
"action.moveToTop": "HODOR HODOR HODOR",
|
||||||
|
"action.share": "HODOR",
|
||||||
|
"action.rename": "HODOR",
|
||||||
|
"action.love": "HODOR",
|
||||||
|
"action.unlove": "HODOR",
|
||||||
|
"action.dislike": "HODOR",
|
||||||
|
"action.undoDislike": "HODOR HODOR",
|
||||||
|
"action.showWebRemoteQR": "HODOR HODOR HODOR HODOR",
|
||||||
|
"action.playTracksNext": "HODOR ${app.selectedMediaItems.length} HODOR HODOR",
|
||||||
|
"action.playTracksLater": "HODOR ${app.selectedMediaItems.length} HODOR HODOR",
|
||||||
|
"action.removeTracks": "HODOR ${self.selectedItems.length} HODOR HODOR HODOR",
|
||||||
|
|
||||||
|
// Settings - General (Reserved)
|
||||||
|
"settings.header.general": "HODOR HODOR",
|
||||||
|
"settings.header.general.description": "HODOR HODOR HODOR HODOR.",
|
||||||
|
"settings.option.general.language": "HODOR",
|
||||||
|
|
||||||
|
// Language optgroups
|
||||||
|
"settings.option.general.language.main": "HODOR",
|
||||||
|
"settings.option.general.language.fun": "HODOR HODOR",
|
||||||
|
"settings.option.general.language.unsorted": "HODOR",
|
||||||
|
|
||||||
|
// Settings - Audio
|
||||||
|
"settings.header.audio": "HODOR",
|
||||||
|
"settings.header.audio.description": "HODOR HODOR HODOR HODOR HODOR HODOR.",
|
||||||
|
"settings.option.audio.quality": "HODOR HODOR", // Dropdown
|
||||||
|
"settings.header.audio.quality.high": "HODOR.",
|
||||||
|
"settings.header.audio.quality.low": "HODOR!",
|
||||||
|
"settings.header.audio.quality.auto": "HODOR",
|
||||||
|
"settings.option.audio.seamlessTransition": "HODOR HODOR HODOR", // Toggle
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality": "HODOR HODOR HODOR", // Toggle
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.description": "HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR , HODOR HODOR HODOR, HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR.",
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.audioNormalization": "HODOR HODOR", // Toggle
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.audioNormalization.description": "HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR.",
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization": "HODOR HODOR", // Toggle
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization.description": "HODOR HODOR HODOR HODOR HODOR HODOR 3-HODOR (HODOR: HODOR HODOR HODOR HODOR HODOR)",
|
||||||
|
// Settings - Visual
|
||||||
|
"settings.header.visual": "HODOR",
|
||||||
|
"settings.header.visual.description": "HODOR HODOR HODOR HODOR HODOR HODOR.",
|
||||||
|
"settings.option.visual.windowBackgroundStyle": "HODOR HODOR HODOR", // Toggle
|
||||||
|
"settings.header.visual.windowBackgroundStyle.none": "HODOR",
|
||||||
|
"settings.header.visual.windowBackgroundStyle.artwork": "HODOR",
|
||||||
|
"settings.option.visual.animatedArtwork": "HODOR HODOR", // Dropdown
|
||||||
|
"settings.header.visual.animatedArtwork.always": "HODOR",
|
||||||
|
"settings.header.visual.animatedArtwork.limited": "HODOR HODOR HODOR HODOR HODOR HODOR",
|
||||||
|
"settings.header.visual.animatedArtwork.disable": "HODOR HODOR",
|
||||||
|
"settings.option.visual.animatedArtworkQuality": "HODOR HODOR HODOR", // Dropdown
|
||||||
|
"settings.header.visual.animatedArtworkQuality.low": "HODOR..",
|
||||||
|
"settings.header.visual.animatedArtworkQuality.medium": "HODOR.",
|
||||||
|
"settings.header.visual.animatedArtworkQuality.high": "HODOR!",
|
||||||
|
"settings.header.visual.animatedArtworkQuality.veryHigh": "HODOR HODOR!",
|
||||||
|
"settings.header.visual.animatedArtworkQuality.extreme": "HODOOOR!!",
|
||||||
|
"settings.option.visual.animatedWindowBackground": "HODOR HODOR HODOR", // Toggle
|
||||||
|
"settings.option.visual.hardwareAcceleration": "HODOR HODOR", // Dropdown
|
||||||
|
"settings.option.visual.hardwareAcceleration.description": "HODOR HODOR",
|
||||||
|
"settings.header.visual.hardwareAcceleration.default": "HODOR.",
|
||||||
|
"settings.header.visual.hardwareAcceleration.webGPU": "HODOR!!",
|
||||||
|
// Refer to term.disabled for the disabled option
|
||||||
|
"settings.option.visual.showPersonalInfo": "HODOR HODOR HODOR?", // Toggle
|
||||||
|
|
||||||
|
// Settings - Lyrics
|
||||||
|
"settings.header.lyrics": "HODOR",
|
||||||
|
"settings.header.lyrics.description": "HODOR HODOR HODOR HODOR HODOR HODOR.",
|
||||||
|
"settings.option.lyrics.enableMusixmatch": "HODOR HODOR HODOR", // Toggle
|
||||||
|
"settings.option.lyrics.enableMusixmatchKaraoke": "HODOR HODOR HODOR (HODOR HODOR)", // Toggle
|
||||||
|
"settings.option.lyrics.musixmatchPreferredLanguage": "HODOR HODOR HODOR HODOR", // Dropdown
|
||||||
|
"settings.option.lyrics.enableYoutubeLyrics": "HODOR HODOR HODOR HODOR HODOR HODOR", // Toggle
|
||||||
|
|
||||||
|
// Settings - Connectivity
|
||||||
|
"settings.header.connectivity": "HODOR",
|
||||||
|
"settings.header.connectivity.description": "HODOR HODOR HODOR HODOR HODOR HODOR.",
|
||||||
|
"settings.option.connectivity.discordRPC": "HODOR HODOR HODOR", // Dropdown
|
||||||
|
// Refer to term.disabled for the disabled option
|
||||||
|
"settings.header.connectivity.discordRPC.cider": "HODOR HODOR 'HODOR'",
|
||||||
|
"settings.header.connectivity.discordRPC.appleMusic": "HODOR HODOR 'HODOR HODOR'",
|
||||||
|
"settings.option.connectivity.discordRPC.clearOnPause": "HODOR HODOR HODOR HODOR HODOR HODOR", // Toggle
|
||||||
|
"settings.option.connectivity.lastfmScrobble": "HODOR HODOR", // Option to Connect
|
||||||
|
"settings.option.connectivity.lastfmScrobble.delay": "HODOR HODOR HODOR (%)",
|
||||||
|
"settings.option.connectivity.lastfmScrobble.nowPlaying": "HODOR HODOR HODOR HODOR",
|
||||||
|
"settings.option.connectivity.lastfmScrobble.removeFeatured": "HODOR HODOR HODOR HODOR HODOR HODOR (HODOR)",
|
||||||
|
"settings.option.connectivity.lastfmScrobble.filterLoop": "HODOR HODOR HODOR (HODOR)",
|
||||||
|
// Refer to term.connect for the connect button
|
||||||
|
|
||||||
|
// Settings - Experimental
|
||||||
|
"settings.header.experimental": "HODOR",
|
||||||
|
"settings.header.experimental.description": "HODOR HODOR HODOR HODOR HODOR HODOR.",
|
||||||
|
"settings.option.experimental.compactUI": "HODOR UI", // Toggle
|
||||||
|
"settings.option.experimental.closeButtonBehaviour": "HODOR HODOR HODOR",
|
||||||
|
"settings.option.experimental.closeButtonBehaviour.quit": "HODOR HODOR",
|
||||||
|
"settings.option.experimental.closeButtonBehaviour.minimizeTaskbar": "HODOR HODOR HODOR",
|
||||||
|
"settings.option.experimental.closeButtonBehaviour.minimizeTray": "HODOR HODOR HODOR HODOR",
|
||||||
|
// Refer to term.disabled & term.enabled
|
||||||
|
|
||||||
|
// Spatialization Menu
|
||||||
|
"spatial.spatialProperties" : "HODOR HODOR",
|
||||||
|
"spatial.width" : "HODOR",
|
||||||
|
"spatial.height" : "HODOR",
|
||||||
|
"spatial.depth" : "HODOR",
|
||||||
|
"spatial.roomMaterials" : "HODOR HODOR",
|
||||||
|
"spatial.roomDimensions" : "HODOR HODOR",
|
||||||
|
"spatial.roomPositions" : "HODOR HODOR",
|
||||||
|
"spatial.setDimensions" : "HODOR HODOR",
|
||||||
|
"spatial.setPositions" : "HODOR HODOR",
|
||||||
|
"spatial.up" : "HODOR",
|
||||||
|
"spatial.front" : "HODOR",
|
||||||
|
"spatial.left" : "HODOR",
|
||||||
|
"spatial.right" : "HODOR",
|
||||||
|
"spatial.back" : "HODOR",
|
||||||
|
"spatial.down" : "HODOR",
|
||||||
|
"spatial.listener" : "HODOR",
|
||||||
|
"spatial.audioSource" : "HODOR HODOR",
|
||||||
|
|
||||||
|
// Settings - Unfinished
|
||||||
|
"settings.header.unfinished": "HODOR",
|
||||||
|
|
||||||
|
// Web Remote
|
||||||
|
"remote.web.title": "HODOR HODOR",
|
||||||
|
"remote.web.description": "HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR",
|
||||||
|
|
||||||
|
//About
|
||||||
|
"about.thanks": "HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR HODOR."
|
||||||
|
}
|
279
src/i18n/en_SGA.jsonc
Normal file
|
@ -0,0 +1,279 @@
|
||||||
|
{
|
||||||
|
|
||||||
|
// i18n Info
|
||||||
|
"i18n.languageName": "┤ᖋ|:ᖋᔮᒣ╎ᔮ ᖋ|:i!⍑ᖋᕊᒷᒣ", // name of language in native language
|
||||||
|
"i18n.languageNameEnglish": "Galactic Alphabet", // name of language in English
|
||||||
|
"i18n.category": "fun", // main = real language, fun = fun community languages
|
||||||
|
"i18n.authors": "@kyw504100", // Authors, if you contribute to this file feel free to add your name seperated with a space
|
||||||
|
|
||||||
|
// App info
|
||||||
|
"app.name": "ᔮ╎↸ᒷ∷",
|
||||||
|
|
||||||
|
"date.format": "${m} ${d}, ${y}",
|
||||||
|
|
||||||
|
// Dialogs
|
||||||
|
"dialog.cancel": "ᔮᖋリᔮᒷ|:",
|
||||||
|
"dialog.ok": "ᒍ·ǀ·",
|
||||||
|
|
||||||
|
// Notification
|
||||||
|
"notification.updatingLibrarySongs": "⚍i!↸ᖋᒣ╎リ┤ |:╎ᕊ∷ᖋ∷॥ ϟᒍリ┤ϟ...",
|
||||||
|
"notification.updatingLibraryAlbums": "⚍i!↸ᖋᒣ╎リ┤ |:╎ᕊ∷ᖋ∷॥ ᖋ|:ᕊ⚍ᒲϟ...",
|
||||||
|
"notification.updatingLibraryArtists": "⚍i!↸ᖋᒣ╎リ┤ |:╎ᕊ∷ᖋ∷॥ ᖋ∷ᒣ╎ϟᒣϟ...",
|
||||||
|
// Terms
|
||||||
|
"term.appleInc": "ᖋi!i!|:ᒷ ╎リᔮ.",
|
||||||
|
"term.appleMusic": "ᖋi!i!|:ᒷ ᒲ⚍ϟ╎ᔮ",
|
||||||
|
"term.applePodcasts": "ᖋi!i!|:ᒷ i!ᒍ↸ᔮᖋϟᒣϟ",
|
||||||
|
"term.itunes": "╎ᒣ⚍リᒷϟ",
|
||||||
|
"term.github": "┤╎ᒣ⍑⚍ᕊ",
|
||||||
|
"term.discord": "↸╎ϟᔮᒍ∷↸",
|
||||||
|
"term.learnMore": "|:ᒷᖋ∷リ ᒲᒍ∷ᒷ",
|
||||||
|
"term.accountSettings": "ᖋᔮᔮᒍ⚍リᒣ ϟᒷᒣᒣ╎リ┤ϟ",
|
||||||
|
"term.logout": "|:ᒍ┤ᒍ⚍ᒣ",
|
||||||
|
"term.login": "|:ᒍ┤╎リ",
|
||||||
|
"term.about": "ᖋᕊᒍ⚍ᒣ",
|
||||||
|
"term.privateSession": "I!∷╎⍊ᖋᒣᒷ ϟᒷϟϟ╎ᒍリ",
|
||||||
|
"term.queue": "ᑑ⚍ᒷ⚍ᒷ",
|
||||||
|
"term.search": "ϟᒷᖋ∷ᔮ⍑",
|
||||||
|
"term.library": "|:╎ᕊ∷ᖋ∷॥",
|
||||||
|
"term.listenNow": "|:╎ϟᒣᒷリ",
|
||||||
|
"term.browse": "リᒍ∴",
|
||||||
|
"term.radio": "∷ᖋ↸╎ᒍ",
|
||||||
|
"term.recentlyAdded": "∷ᒷᔮᒷリᒣ|:॥ ᖋ↸↸ᒷ↸",
|
||||||
|
"term.songs": "ϟᒍリ┤ϟ",
|
||||||
|
"term.albums": "ᖋ|:ᕊ⚍ᒲϟ",
|
||||||
|
"term.artists": "ᖋ∷ᒣ╎ϟᒣϟ",
|
||||||
|
"term.podcasts": "I!ᒍ↸ᔮᖋϟᒣϟ",
|
||||||
|
"term.playlists": "i!|:ᖋ॥|:╎ϟᒣϟ",
|
||||||
|
"term.playlist": "i!|:ᖋ॥|:╎ϟᒣ",
|
||||||
|
"term.play": "i!|:ᖋ॥",
|
||||||
|
"term.pause": "I!ᖋ⚍ϟᒷ",
|
||||||
|
"term.previous": "I!∷ᒷ⍊╎ᒍ⚍ϟ",
|
||||||
|
"term.next": "リᒷ/ᒣ",
|
||||||
|
"term.shuffle": "ϟ⍑⚍⎓⎓|:ᒷ",
|
||||||
|
"term.repeat": "∷ᒷi!ᒷᖋᒣ",
|
||||||
|
"term.volume": "⍊ᒍ|:⚍ᒲᒷ",
|
||||||
|
"term.mute": "ᒲ⚍ᒣᒷ",
|
||||||
|
"term.unmute": "⚍リᒲ⚍ᒣᒷ",
|
||||||
|
"term.share": "ϟ⍑ᖋ∷ᒷ",
|
||||||
|
"term.settings": "ϟᒷᒣᒣ╎リ┤ϟ",
|
||||||
|
"term.seeAll": "ϟᒷᒷ ᖋ|:|:",
|
||||||
|
"term.sortBy": "ϟᒍ∷ᒣ ᕊ॥",
|
||||||
|
"term.sortBy.album": "ᖋ|:ᕊ⚍ᒲ",
|
||||||
|
"term.sortBy.artist": "ᖋ∷ᒣ╎ϟᒣ",
|
||||||
|
"term.sortBy.name": "リᖋᒲᒷ",
|
||||||
|
"term.sortBy.genre": "┤ᒷリ∷ᒷ",
|
||||||
|
"term.sortBy.releaseDate": "∷ᒷ|:ᒷᖋϟᒷ ↸ᖋᒣᒷ",
|
||||||
|
"term.sortBy.duration": "↸⚍∷ᖋᒣ╎ᒍリ",
|
||||||
|
"term.sortOrder": "ᖋ-∩",
|
||||||
|
"term.sortOrder.ascending": "ᖋϟᔮᒷリ↸╎リ┤",
|
||||||
|
"term.sortOrder.descending": "↸ᒷϟᔮᒷリ↸╎リ┤",
|
||||||
|
"term.viewAs": "⍊╎ᒷ∴ ᖋϟ",
|
||||||
|
"term.viewAs.coverArt": "ᔮᒍ⍊ᒷ∷ ᖋ∷ᒣ",
|
||||||
|
"term.viewAs.list": "|:╎ϟᒣ",
|
||||||
|
"term.size": "ϟ╎∩ᒷ",
|
||||||
|
"term.size.normal": "リᒍ∷ᒲᖋ|:",
|
||||||
|
"term.size.compact": "ᔮᒍᒲi!ᖋᔮᒣ",
|
||||||
|
"term.enable": "ᒷリᖋᕊ|:ᒷ",
|
||||||
|
"term.disable": "↸╎ϟᖋᕊ|:ᒷ",
|
||||||
|
"term.enabled": "ᒷリᖋᕊ|:ᒷ↸",
|
||||||
|
"term.disabled": "↸╎ϟᖋᕊ|:ᒷ↸",
|
||||||
|
"term.connect": "ᔮᒍリリᒷᔮᒣ",
|
||||||
|
"term.connecting": "ᔮᒍリリᒷᔮᒣ╎リ┤",
|
||||||
|
"term.disconnect": "↸╎ϟᔮᒍリリᒷᔮᒣ",
|
||||||
|
"term.authed": "ᖋ⚍ᒣ⍑ᒷ↸",
|
||||||
|
"term.confirm": "ᔮᒍリ⎓╎∷ᒲ ?",
|
||||||
|
"term.more": "ᒲᒍ∷ᒷ",
|
||||||
|
"term.less": "|:ᒷϟϟ",
|
||||||
|
"term.showMore": "ϟ⍑ᒍ∴ ᒲᒍ∷ᒷ",
|
||||||
|
"term.showLess": "ϟ⍑ᒍ∴ |:ᒷϟϟ",
|
||||||
|
"term.topSongs" : "ᒣᒍi! ϟᒍリ┤ϟ",
|
||||||
|
"term.latestReleases": "|:ᖋᒣᒷϟᒣ ∷ᒷ|:ᒷᖋϟᒷϟ",
|
||||||
|
"term.time.added": "ᖋ↸↸ᒷ↸",
|
||||||
|
"term.time.released": "∷ᒷ|:ᒷᖋϟᒷ↸",
|
||||||
|
"term.time.updated": "⚍i!↸ᖋᒣᒷ↸",
|
||||||
|
"term.fullscreenView": "⎓⚍|:|:ϟᔮ∷ᒷᒷリ ⍊╎ᒷ∴",
|
||||||
|
"term.defaultView": "↸ᒷ⎓ᖋ⚍|:ᒣ ⍊╎ᒷ∴",
|
||||||
|
"term.spacializedAudioSetting": "ϟi!ᖋᔮ╎ᖋ|:╎∩ᒷ↸ ᖋ⚍↸╎ᒍ ϟᒷᒣᒣ╎リ┤",
|
||||||
|
"term.clearAll": "ᔮ|:ᒷᖋ∷ ᖋ|:|:",
|
||||||
|
"term.recentStations": "∷ᒷᔮᒷリᒣ ϟᒣᖋᒣ╎ᒍリϟ",
|
||||||
|
"term.language": "|:ᖋリ┤⚍ᖋ┤ᒷ",
|
||||||
|
"term.noLyrics": "|:ᒍᖋ↸╎リ┤... / |:॥∷╎ᔮϟ リᒍᒣ ⎓ᒍ⚍リ↸./ ╎リϟᒣ∷⚍ᒲᒷリᒣᖋ|:.",
|
||||||
|
"term.copyright": "ᔮᒍi!॥∷╎┤⍑ᒣ",
|
||||||
|
"term.rightsReserved": "ᖋ|:|: ∷╎┤⍑ᒣϟ ∷ᒷϟᒷ∷⍊ᒷ↸.",
|
||||||
|
"term.sponsor": "ϟi!ᒍリϟᒍ∷ ᒣ⍑╎ϟ i!∷ᒍ⋮ᒷᔮᒣ",
|
||||||
|
"term.ciderTeam": "ᔮ╎↸ᒷ∷ ᒣᒷᖋᒲ",
|
||||||
|
"term.developer": "↸ᒷ⍊ᒷ|:ᒍi!ᒷ∷",
|
||||||
|
"term.socialTeam": "ϟᒍᔮ╎ᖋ|: ᒣᒷᖋᒲ",
|
||||||
|
"term.contributors": "ᔮᒍリᒣ∷╎ᕊ⚍ᒣᒍ∷ϟ",
|
||||||
|
"term.equalizer": "ᒷᑑ⚍ᖋ|:╎∩ᒷ∷",
|
||||||
|
"term.reset": "∷ᒷϟᒷᒣ",
|
||||||
|
"term.tracks": "ᒣ∷ᖋᔮ·ǀ·ϟ", // Assume x amount of tracks. e.g. 50 tracks
|
||||||
|
|
||||||
|
|
||||||
|
// Home
|
||||||
|
"home.title": "⍑ᒍᒲᒷ",
|
||||||
|
"home.recentlyPlayed": "∷ᒷᔮᒷリᒣ|:॥ i!|:ᖋ॥ᒷ↸",
|
||||||
|
"home.recentlyAdded": "∷ᒷᔮᒷリᒣ|:॥ ᖋ↸↸ᒷ↸",
|
||||||
|
"home.artistsFeed": "॥ᒍ⚍∷ ᖋ∷ᒣ╎ϟᒣϟ ⎓ᒷᒷ↸",
|
||||||
|
"home.artistsFeed.noArtist": "⎓ᒍ|:|:ᒍ∴ ϟᒍᒲᒷ ᖋ∷ᒣ╎ϟᒣϟ ⎓╎∷ϟᒣ ᖋリ↸ ᒣ⍑ᒷ╎∷ |:ᖋᒣᒷϟᒣ ∷ᒷ|:ᒷᖋϟᒷϟ ∴╎|:|: ᕊᒷ ⍑ᒷ∷ᒷ",
|
||||||
|
"home.madeForYou": "ᒲᖋ↸ᒷ ⎓ᒍ∷ ॥ᒍ⚍",
|
||||||
|
"home.friendsListeningTo": "⎓∷╎ᒷリ↸ϟ |:╎ϟᒣᒷリ╎リ┤ ᒣᒍ",
|
||||||
|
"home.followedArtists": "⎓ᒍ|:|:ᒍ∴ᒷ↸ ᖋ∷ᒣ╎ϟᒣϟ",
|
||||||
|
// Errors
|
||||||
|
"error.appleMusicSubRequired": "ᖋi!i!|:ᒷ ᒲ⚍ϟ╎ᔮ ∷ᒷᑑ⚍╎∷ᒷϟ ᖋ ϟ⚍ᕊϟᔮ∷╎i!ᒣ╎ᒍリ.",
|
||||||
|
"error.connectionError": "ᒣ⍑ᒷ∷ᒷ ∴ᖋϟ ᖋ i!∷ᒍᕊ|:ᒷᒲ ᔮᒍリリᒷᔮᒣ╎リ┤ ᒣᒍ ᖋi!i!|:ᒷ ᒲ⚍ϟ╎ᔮ.",
|
||||||
|
"error.noResults": "リᒍ ∷ᒷϟ⚍|:ᒣϟ.",
|
||||||
|
"error.noResults.description": "ᒣ∷॥ ᖋ リᒷ∴ ϟᒷᖋ∷ᔮ⍑.",
|
||||||
|
|
||||||
|
//Podcasts
|
||||||
|
"podcast.followOnCider": "⎓ᒍ|:|:ᒍ∴ ᒍリ ᔮ╎↸ᒷ∷",
|
||||||
|
"podcast.followedOnCider": "⎓ᒍ|:|:ᒍ∴╎リ┤ ᒍリ ᔮ╎↸ᒷ∷",
|
||||||
|
"podcast.subscribeOnItunes": "ϟ⚍ᕊϟᔮ∷╎ᕊᒷ ᒍリ ╎ᒣ⚍リᒷϟ",
|
||||||
|
"podcast.subscribedOnItunes": "ϟ⚍ᕊϟᔮ∷╎ᕊᒷ↸ ᒍリ ╎ᒣ⚍リᒷϟ",
|
||||||
|
"podcast.itunesStore": "╎ᒣ⚍リᒷϟ ϟᒣᒍ∷ᒷ",
|
||||||
|
"podcast.episodes": "ᒷi!╎ϟᒍ↸ᒷϟ",
|
||||||
|
"podcast.playEpisode": "i!|:ᖋ॥ ᒷi!╎ϟᒍ↸ᒷ",
|
||||||
|
"podcast.website": "I!ᒍ↸ᔮᖋϟᒣ ∴ᒷᕊϟ╎ᒣᒷ",
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
"action.addToLibrary": "ᖋ↸↸ ᒣᒍ |:╎ᕊ∷ᖋ∷॥",
|
||||||
|
"action.addToLibrary.success": "ᖋ↸↸ᒷ↸ ᒣᒍ |:╎ᕊ∷ᖋ∷॥",
|
||||||
|
"action.addToLibrary.error": "ᒷ∷∷ᒍ∷ ᖋ↸↸╎リ┤ ᒣᒍ |:╎ᕊ∷ᖋ∷॥",
|
||||||
|
"action.removeFromLibrary": "∷ᒷᒲᒍ⍊ᒷ ⎓∷ᒍᒲ |:╎ᕊ∷ᖋ∷॥",
|
||||||
|
"action.removeFromLibrary.success": "∷ᒷᒲᒍ⍊ᒷ↸ ⎓∷ᒍᒲ |:╎ᕊ∷ᖋ∷॥",
|
||||||
|
"action.addToQueue": "ᖋ↸↸ ᒣᒍ ᑑ⚍ᒷ⚍ᒷ",
|
||||||
|
"action.addToQueue.success": "ᖋ↸↸ᒷ↸ ᒣᒍ ᑑ⚍ᒷ⚍ᒷ",
|
||||||
|
"action.addToQueue.error": "ᒷ∷∷ᒍ∷ ᖋ↸↸╎リ┤ ᒣᒍ ᑑ⚍ᒷ⚍ᒷ",
|
||||||
|
"action.removeFromQueue": "∷ᒷᒲᒍ⍊ᒷ ⎓∷ᒍᒲ ᑑ⚍ᒷ⚍ᒷ",
|
||||||
|
"action.removeFromQueue.success": "∷ᒷᒲᒍ⍊ᒷ↸ ⎓∷ᒍᒲ ᑑ⚍ᒷ⚍ᒷ",
|
||||||
|
"action.removeFromQueue.error": "ᒷ∷∷ᒍ∷ ∷ᒷᒲᒍ⍊╎リ┤ ⎓∷ᒍᒲ ᑑ⚍ᒷ⚍ᒷ",
|
||||||
|
"action.addToPlaylist": "ᖋ↸↸ ᒣᒍ i!|:ᖋ॥|:╎ϟᒣ",
|
||||||
|
"action.removeFromPlaylist": "∷ᒷᒲᒍ⍊ᒷ ⎓∷ᒍᒲ i!|:ᖋ॥|:╎ϟᒣ",
|
||||||
|
"action.addToFavorites": "ᖋ↸↸ ᒣᒍ ⎓ᖋ⍊ᒍ∷╎ᒣᒷϟ",
|
||||||
|
"action.follow": "⎓ᒍ|:|:ᒍ∴",
|
||||||
|
"action.follow.success": "⎓ᒍ|:|:ᒍ∴ᒷ↸",
|
||||||
|
"action.follow.error": "ᒷ∷∷ᒍ∷ ⎓ᒍ|:|:ᒍ∴╎リ┤",
|
||||||
|
"action.unfollow": "⚍リ⎓ᒍ|:|:ᒍ∴",
|
||||||
|
"action.unfollow.success": "⚍リ⎓ᒍ|:|:ᒍ∴ᒷ↸",
|
||||||
|
"action.unfollow.error": "ᒷ∷∷ᒍ∷ ⚍リ⎓ᒍ|:|:ᒍ∴╎リ┤",
|
||||||
|
"action.playNext": "i!|:ᖋ॥ リᒷ/ᒣ",
|
||||||
|
"action.playLater": "i!|:ᖋ॥ |:ᖋᒣᒷ∷",
|
||||||
|
"action.startRadio": "ϟᒣᖋ∷ᒣ ∷ᖋ↸╎ᒍ",
|
||||||
|
"action.goToArtist": "┤ᒍ ᒣᒍ ᖋ∷ᒣ╎ϟᒣ",
|
||||||
|
"action.goToAlbum": "┤ᒍ ᒣᒍ ᖋ|:ᕊ⚍ᒲ",
|
||||||
|
"action.moveToTop": "ᒲᒍ⍊ᒷ ᒣᒍ ᒣᒍi!",
|
||||||
|
"action.share": "ϟ⍑ᖋ∷ᒷ",
|
||||||
|
"action.rename": "∷ᒷリᖋᒲᒷ",
|
||||||
|
"action.love": "|:ᒍ⍊ᒷ",
|
||||||
|
"action.unlove": "⚍リ|:ᒍ⍊ᒷ",
|
||||||
|
"action.dislike": "↸╎ϟ|:╎·ǀ·ᒷ",
|
||||||
|
"action.undoDislike": "⚍リ↸ᒍ ↸╎ϟ|:╎·ǀ·ᒷ",
|
||||||
|
"action.showWebRemoteQR": "ϟ⍑ᒍ∴ ∴ᒷᕊ ∷ᒷᒲᒍᒣᒷ ᑑ∷",
|
||||||
|
"action.playTracksNext": "i!|:ᖋ॥ ${app.selectedMediaItems.length} ᒣ∷ᖋᔮ·ǀ·ϟ リᒷ/ᒣ",
|
||||||
|
"action.playTracksLater": "i!|:ᖋ॥ ${app.selectedMediaItems.length} ᒣ∷ᖋᔮ·ǀ·ϟ |:ᖋᒣᒷ∷",
|
||||||
|
"action.removeTracks": "∷ᒷᒲᒍ⍊ᒷ ${self.selectedItems.length} ᒣ∷ᖋᔮ·ǀ·ϟ ⎓∷ᒍᒲ ᑑ⚍ᒷ⚍ᒷ",
|
||||||
|
|
||||||
|
// Settings - Audio
|
||||||
|
"settings.header.audio": "ᖋ⚍↸╎ᒍ",
|
||||||
|
"settings.header.audio.description": "ᖋ↸⋮⚍ϟᒣ ᒣ⍑ᒷ ᖋ⚍↸╎ᒍ ϟᒷᒣᒣ╎リ┤ϟ ⎓ᒍ∷ ᔮ╎↸ᒷ∷.",
|
||||||
|
"settings.option.audio.quality": "ᖋ⚍↸╎ᒍ ᑑ⚍ᖋ|:╎ᒣ॥", // Dropdown
|
||||||
|
"settings.header.audio.quality.high": "⍑╎┤⍑",
|
||||||
|
"settings.header.audio.quality.low": "|:ᒍ∴",
|
||||||
|
"settings.header.audio.quality.auto": "ᖋ⚍ᒣᒍ",
|
||||||
|
"settings.option.audio.seamlessTransition": "ϟᒷᖋᒲ|:ᒷϟϟ ᖋ⚍↸╎ᒍ ᒣ∷ᖋリϟ╎ᒣ╎ᒍリ", // Toggle
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality": "ᒷリᖋᕊ|:ᒷ ᖋ↸⍊ᖋリᔮᒷ↸ ⎓⚍リᔮᒣ╎ᒍリᖋ|:╎ᒣ॥", // Toggle
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.description": "ᒷリᖋᕊ|:╎リ┤ ᖋ⚍↸╎ᒍᔮᒍリᒣᒷ/ᒣ ⎓⚍リᔮᒣ╎ᒍリᖋ|:╎ᒣ॥ ∴╎|:|: ᖋ|:|:ᒍ∴ ⎓ᒍ∷ ᒷ/ᒣᒷリ↸ᒷ↸ ᖋ⚍↸╎ᒍ ⎓ᒷᖋᒣ⚍∷ᒷϟ |:╎·ǀ·ᒷ ᖋ⚍↸╎ᒍ リᒍ∷ᒲᖋ|:╎∩ᖋᒣ╎ᒍリ , ᒷᑑ⚍ᖋ|:╎∩ᒷ∷ϟ ᖋリ↸ ⍊╎ϟ⚍ᖋ|:╎∩ᒷ∷ϟ, ⍑ᒍ∴ᒷ⍊ᒷ∷ ᒍリ ϟᒍᒲᒷ ϟ॥ϟᒣᒷᒲϟ ᒣ⍑╎ϟ ᒲᖋ॥ ᔮᖋ⚍ϟᒷ ϟᒣ⚍ᒣᒣᒷ∷╎リ┤ ╎リ ᖋ⚍↸╎ᒍ ᒣ∷ᖋᔮ·ǀ·ϟ.",
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.audioNormalization": "ᖋ⚍↸╎ᒍ リᒍ∷ᒲᖋ|:╎∩ᖋᒣ╎ᒍリ", // Toggle
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.audioNormalization.description": "リᒍ∷ᒲᖋ|:╎∩ᒷϟ i!ᒷᖋ·ǀ· ⍊ᒍ|:⚍ᒲᒷ ⎓ᒍ∷ ╎リ↸╎⍊╎↸⚍ᖋ|: ᒣ∷ᖋᔮ·ǀ·ϟ ᒣᒍ ᔮ∷ᒷᖋᒣᒷ ᖋ ᒲᒍ∷ᒷ ⚍リ╎⎓ᒍ∷ᒲ |:╎ϟᒣᒷリ╎リ┤ ᒷ/i!ᒷ∷╎ᒷリᔮᒷ.",
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization": "ᖋ⚍↸╎ᒍ ϟi!ᖋᒣ╎ᖋ|:╎∩ᖋᒣ╎ᒍリ", // Toggle
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization.description": "ϟi!ᖋᒣ╎ᖋ|:╎∩ᒷ ᖋ⚍↸╎ᒍ ᖋリ↸ ᒲᖋ·ǀ·ᒷ ᖋ⚍↸╎ᒍ ᒲᒍ∷ᒷ 3-↸╎ᒲᒷリϟ╎ᒍリᖋ|: (リᒍᒣᒷ: ᒣ⍑╎ϟ ╎ϟ リᒍᒣ ↸ᒍ|:ᕊ॥ ᖋᒣᒲᒍϟ)",
|
||||||
|
// Settings - Visual
|
||||||
|
"settings.header.visual": "⍊╎ϟ⚍ᖋ|:",
|
||||||
|
"settings.header.visual.description": "ᖋ↸⋮⚍ϟᒣ ᒣ⍑ᒷ ⍊╎ϟ⚍ᖋ| ϟᒷᒣᒣ╎リ┤ϟ ⎓ᒍ∷ ᔮ╎↸ᒷ∷.",
|
||||||
|
"settings.option.visual.windowBackgroundStyle": "∴╎リ↸ᒍ∴ ᕊᖋᔮ·ǀ·┤∷ᒍ⚍リ↸ ϟᒣ॥|:ᒷ", // Toggle
|
||||||
|
"settings.header.visual.windowBackgroundStyle.none": "リᒍリᒷ",
|
||||||
|
"settings.header.visual.windowBackgroundStyle.artwork": "ᖋ∷ᒣ∴ᒍ∷·ǀ·",
|
||||||
|
"settings.option.visual.animatedArtwork": "ᖋリ╎ᒲᖋᒣᒷ↸ ᖋ∷ᒣ∴ᒍ∷·ǀ·", // Dropdown
|
||||||
|
"settings.header.visual.animatedArtwork.always": "ᖋ|:∴ᖋ॥ϟ",
|
||||||
|
"settings.header.visual.animatedArtwork.limited": "|:╎ᒲ╎ᒣᒷ↸ ᒣᒍ i!ᖋ┤ᒷϟ ᖋリ↸ ϟi!ᒷᔮ╎ᖋ|: ᒷリᒣ∷╎ᒷϟ",
|
||||||
|
"settings.header.visual.animatedArtwork.disable": "↸╎ϟᖋᕊ|:ᒷ ᒷ⍊ᒷ∷॥∴⍑ᒷ∷ᒷ",
|
||||||
|
"settings.option.visual.animatedArtworkQuality": "ᖋリ╎ᒲᖋᒣᒷ↸ ᖋ∷ᒣ∴ᒍ∷·ǀ· ᑑ⚍ᖋ|:╎ᒣ॥", // Dropdown
|
||||||
|
"settings.header.visual.animatedArtworkQuality.low": "|:ᒍ∴",
|
||||||
|
"settings.header.visual.animatedArtworkQuality.medium": "ᒲᒷ↸╎⚍ᒲ",
|
||||||
|
"settings.header.visual.animatedArtworkQuality.high": "⍑╎┤⍑",
|
||||||
|
"settings.header.visual.animatedArtworkQuality.veryHigh": "⍊ᒷ∷॥ ⍑╎┤⍑",
|
||||||
|
"settings.header.visual.animatedArtworkQuality.extreme": "ᒷ/ᒣ∷ᒷᒲᒷ",
|
||||||
|
"settings.option.visual.animatedWindowBackground": "ᖋリ╎ᒲᖋᒣᒷ↸ ∴╎リ↸ᒍ∴ ᕊᖋᔮ·ǀ·┤∷ᒍ⚍リ↸", // Toggle
|
||||||
|
"settings.option.visual.hardwareAcceleration": "⍑ᖋ∷↸∴ᖋ∷ᒷ ᖋᔮᔮᒷ|:ᒷ∷ᖋᒣ╎ᒍリ", // Dropdown
|
||||||
|
"settings.option.visual.hardwareAcceleration.description": "∷ᒷᑑ⚍╎∷ᒷϟ ∷ᒷ|:ᖋ⚍リᔮ⍑",
|
||||||
|
"settings.header.visual.hardwareAcceleration.default": "↸ᒷ⎓ᖋ⚍|:ᒣ",
|
||||||
|
"settings.header.visual.hardwareAcceleration.webGPU": "∴ᒷᕊ┤i!⚍",
|
||||||
|
// Refer to term.disabled for the disabled option
|
||||||
|
"settings.option.visual.showPersonalInfo": "ϟ⍑ᒍ∴ i!ᒷ∷ϟᒍリᖋ|: ╎リ⎓ᒍ", // Toggle
|
||||||
|
// Settings - General (Reserved)
|
||||||
|
"settings.header.general": "┤ᒷリᒷ∷ᖋ|:",
|
||||||
|
"settings.header.general.description": "ᖋ↸⋮⚍ϟᒣ ᒣ⍑ᒷ ┤ᒷリᒷ∷ᖋ|: ϟᒷᒣᒣ╎リ┤ϟ ⎓ᒍ∷ ᔮ╎↸ᒷ∷.",
|
||||||
|
|
||||||
|
// Settings - Lyrics
|
||||||
|
"settings.header.lyrics": "|:॥∷╎ᔮϟ",
|
||||||
|
"settings.header.lyrics.description": "ᖋ↸⋮⚍ϟᒣ ᒣ⍑ᒷ |:॥∷╎ᔮϟ ϟᒷᒣᒣ╎リ┤ϟ ⎓ᒍ∷ ᔮ╎↸ᒷ∷.",
|
||||||
|
"settings.option.lyrics.enableMusixmatch": "ᒷリᖋᕊ|:ᒷ ᒲ⚍ϟ╎̇/ᒲᖋᒣᔮ⍑ |:॥∷╎ᔮϟ", // Toggle
|
||||||
|
"settings.option.lyrics.enableMusixmatchKaraoke": "ᒷリᖋᕊ|:ᒷ ·ǀ·ᖋ∷ᖋᒍ·ǀ·ᒷ ᒲᒍ↸ᒷ (ᒲ⚍ϟ╎̇/ᒲᖋᒣᔮ⍑ ᒍリ|:॥)", // Toggle
|
||||||
|
"settings.option.lyrics.musixmatchPreferredLanguage": "ᒲ⚍ϟ╎̇/ᒲᖋᒣᔮ⍑ ᒣ∷ᖋリϟ|:ᖋᒣ╎ᒍリ i!∷ᒷ⎓ᒷ∷∷ᒷ↸ |:ᖋリ┤⚍ᖋ┤ᒷ", // Dropdown
|
||||||
|
"settings.option.lyrics.enableYoutubeLyrics": "ᒷリᖋᕊ|:ᒷ ॥ᒍ⚍ᒣ⚍ᕊᒷ |:॥∷╎ᔮϟ ⎓ᒍ∷ ᒲ⚍ϟ╎ᔮ ⍊╎↸ᒷᒍϟ", // Toggle
|
||||||
|
|
||||||
|
// Settings - Connectivity
|
||||||
|
"settings.header.connectivity": "ᔮᒍリリᒷᔮᒣ╎⍊╎ᒣ॥",
|
||||||
|
"settings.header.connectivity.description": "ᖋ↸⋮⚍ϟᒣ ᒣ⍑ᒷ ᔮᒍリリᒷᔮᒣ╎⍊╎ᒣ॥ ϟᒷᒣᒣ╎リ┤ϟ ⎓ᒍ∷ ᔮ╎↸ᒷ∷.",
|
||||||
|
"settings.option.connectivity.discordRPC": "↸╎ϟᔮᒍ∷↸ ∷╎ᔮ⍑ i!∷ᒷϟᒷリᔮᒷ", // Dropdown
|
||||||
|
// Refer to term.disabled for the disabled option
|
||||||
|
"settings.header.connectivity.discordRPC.cider": "↸╎ϟi!|:ᖋ॥ ᖋϟ 'ᔮ╎↸ᒷ∷'",
|
||||||
|
"settings.header.connectivity.discordRPC.appleMusic": "↸╎ϟi!|:ᖋ॥ ᖋϟ 'ᖋi!i!|:ᒷ ᒲ⚍ϟ╎ᔮ'",
|
||||||
|
"settings.option.connectivity.discordRPC.clearOnPause": "ᔮ|:ᒷᖋ∷ ↸╎ϟᔮᒍ∷↸ ∷╎ᔮ⍑ i!∷ᒷϟᒷリᔮᒷ ᒍリ i!ᖋ⚍ϟᒷ", // Toggle
|
||||||
|
"settings.option.connectivity.lastfmScrobble": "|:ᖋϟᒣ⎓ᒲ ϟᔮ∷ᒍᕊᕊ|:╎リ┤", // Option to Connect
|
||||||
|
"settings.option.connectivity.lastfmScrobble.delay": "|:ᖋϟᒣ⎓ᒲ ϟᔮ∷ᒍᕊᕊ|:ᒷ ↸ᒷ|:ᖋ॥ (%)",
|
||||||
|
"settings.option.connectivity.lastfmScrobble.nowPlaying": "ᒷリᖋᕊ|:ᒷ |:ᖋϟᒣ⎓ᒲ リᒍ∴ i!|:ᖋ॥╎リ┤",
|
||||||
|
"settings.option.connectivity.lastfmScrobble.removeFeatured": "∷ᒷᒲᒍ⍊ᒷ ⎓ᒷᖋᒣ⚍∷╎リ┤ ᖋ∷ᒣ╎ϟᒣϟ ⎓∷ᒍᒲ ϟᒍリ┤ ᒣ╎ᒣ|:ᒷ (|:ᖋϟᒣ⎓ᒲ)",
|
||||||
|
"settings.option.connectivity.lastfmScrobble.filterLoop": "⎓╎|:ᒣᒷ∷ |:ᒍᒍi!ᒷ↸ ᒣ∷ᖋᔮ·ǀ· (|:ᖋϟᒣ⎓ᒲ)",
|
||||||
|
// Refer to term.connect for the connect button
|
||||||
|
|
||||||
|
// Settings - Experimental
|
||||||
|
"settings.header.experimental": "ᒷ/i!ᒷ∷╎ᒲᒷリᒣᖋ|:",
|
||||||
|
"settings.header.experimental.description": "ᖋ↸⋮⚍ϟᒣ ᒣ⍑ᒷ ᒷ/i!ᒷ∷╎ᒲᒷリᒣᖋ|: ϟᒷᒣᒣ╎リ┤ϟ ⎓ᒍ∷ ᔮ╎↸ᒷ∷.",
|
||||||
|
"settings.option.experimental.compactUI": "ᔮᒍᒲi!ᖋᔮᒣ ⚍╎", // Toggle
|
||||||
|
"settings.option.experimental.closeButtonBehaviour": "ᔮ|:ᒍϟᒷ ᕊ⚍ᒣᒣᒍリ ᕊᒷ⍑ᖋ⍊╎ᒍ⚍∷",
|
||||||
|
"settings.option.experimental.closeButtonBehaviour.quit": "ᑑ⚍╎ᒣ ᔮ╎↸ᒷ∷",
|
||||||
|
"settings.option.experimental.closeButtonBehaviour.minimizeTaskbar": "ᒲ╎リ╎ᒲ╎∩ᒷ ᒣᒍ ᒣᖋϟ·ǀ·ᕊᖋ∷",
|
||||||
|
"settings.option.experimental.closeButtonBehaviour.minimizeTray": "ᒲ╎リ╎ᒲ╎∩ᒷ ᒣᒍ ϟ॥ϟᒣᒷᒲ ᒣ∷ᖋ॥",
|
||||||
|
// Refer to term.disabled & term.enabled
|
||||||
|
|
||||||
|
// Spatialization Menu
|
||||||
|
"spatial.spatialProperties" : "ϟi!ᖋᒣ╎ᖋ|: i!∷ᒍi!ᒷ∷ᒣ╎ᒷϟ",
|
||||||
|
"spatial.width" : "∴╎↸ᒣ⍑",
|
||||||
|
"spatial.height" : "⍑ᒷ╎┤⍑ᒣ",
|
||||||
|
"spatial.depth" : "↸ᒷi!ᒣ⍑",
|
||||||
|
"spatial.gain" : "┤ᖋ╎リ",
|
||||||
|
"spatial.roomMaterials" : "∷ᒍᒍᒲ ᒲᖋᒣᒷ∷╎ᖋ|:ϟ",
|
||||||
|
"spatial.roomDimensions" : "∷ᒍᒍᒲ ↸╎ᒲᒷリϟ╎ᒍリϟ",
|
||||||
|
"spatial.roomPositions" : "∷ᒍᒍᒲ i!ᒍϟ╎ᒣ╎ᒍリϟ",
|
||||||
|
"spatial.setDimensions" : "ϟᒷᒣ ↸╎ᒲᒷリϟ╎ᒍリϟ",
|
||||||
|
"spatial.setPositions" : "ϟᒷᒣ i!ᒍϟ╎ᒣ╎ᒍリϟ",
|
||||||
|
"spatial.up" : "⚍i!",
|
||||||
|
"spatial.front" : "⎓∷ᒍリᒣ",
|
||||||
|
"spatial.left" : "|:ᒷ⎓ᒣ",
|
||||||
|
"spatial.right" : "∷╎┤⍑ᒣ",
|
||||||
|
"spatial.back" : "ᕊᖋᔮ·ǀ·",
|
||||||
|
"spatial.down" : "↸ᒍ∴リ",
|
||||||
|
"spatial.listener" : "|:╎ϟᒣᒷリᒷ∷",
|
||||||
|
"spatial.audioSource" : "ᖋ⚍↸╎ᒍ ϟᒍ⚍∷ᔮᒷ",
|
||||||
|
|
||||||
|
// Settings - Unfinished
|
||||||
|
"settings.header.unfinished": "⚍リ⎓╎リ╎ϟ⍑ᒷ↸",
|
||||||
|
|
||||||
|
// Web Remote
|
||||||
|
"remote.web.title": "ᔮ╎↸ᒷ∷ ∷ᒷᒲᒍᒣᒷ",
|
||||||
|
"remote.web.description": "ϟᔮᖋリ ᒣ⍑ᒷ ᑑ∷ ᔮᒍ↸ᒷ ᒣᒍ i!ᖋ╎∷ ॥ᒍ⚍∷ i!⍑ᒍリᒷ ⚍i! ∴╎ᒣ⍑ ᒣ⍑╎ϟ ᔮ╎↸ᒷ∷ ╎リϟᒣᖋリᔮᒷ",
|
||||||
|
|
||||||
|
// About
|
||||||
|
"about.thanks": "ᒲᖋ⋮ᒍ∷ ᒣ⍑ᖋリ·ǀ·ϟ ᒣᒍ ᒣ⍑ᒷ ᔮ╎↸ᒷ∷ ᔮᒍ|:|:ᒷᔮᒣ╎⍊ᒷ ᒣᒷᖋᒲ ᖋリ↸ ᖋ|:|: ᒍ⎓ ᒍ⚍∷ ᔮᒍリᒣ∷╎ᕊ⚍ᒣᒍ∷ϟ."
|
||||||
|
}
|
295
src/i18n/en_US.jsonc
Normal file
|
@ -0,0 +1,295 @@
|
||||||
|
{ // Base File
|
||||||
|
|
||||||
|
// i18n Info
|
||||||
|
"i18n.languageName": "English (US)", // name of language in native language
|
||||||
|
"i18n.languageNameEnglish": "English (US)", // name of language in English
|
||||||
|
"i18n.category": "main", // main = real language, fun = fun community languages
|
||||||
|
"i18n.authors": "@maikirakiwi", // Authors, if you contribute to this file feel free to add your name seperated with a space
|
||||||
|
|
||||||
|
// App info
|
||||||
|
"app.name": "Cider",
|
||||||
|
|
||||||
|
"date.format": "${m} ${d}, ${y}",
|
||||||
|
|
||||||
|
// Dialogs
|
||||||
|
"dialog.cancel": "Cancel",
|
||||||
|
"dialog.ok": "OK",
|
||||||
|
|
||||||
|
// Notification
|
||||||
|
"notification.updatingLibrarySongs": "Updating library songs...",
|
||||||
|
"notification.updatingLibraryAlbums": "Updating library albums...",
|
||||||
|
"notification.updatingLibraryArtists": "Updating library artists...",
|
||||||
|
// Terms
|
||||||
|
"term.appleInc": "Apple Inc.",
|
||||||
|
"term.appleMusic": "Apple Music",
|
||||||
|
"term.applePodcasts": "Apple Podcasts",
|
||||||
|
"term.itunes": "iTunes",
|
||||||
|
"term.github": "GitHub",
|
||||||
|
"term.discord": "Discord",
|
||||||
|
"term.learnMore": "Learn more",
|
||||||
|
"term.accountSettings": "Account Settings",
|
||||||
|
"term.logout": "Logout",
|
||||||
|
"term.login": "Login",
|
||||||
|
"term.about": "About",
|
||||||
|
"term.privateSession": "Private Session",
|
||||||
|
"term.queue": "Queue",
|
||||||
|
"term.search": "Search",
|
||||||
|
"term.library": "Library",
|
||||||
|
"term.listenNow": "Listen Now",
|
||||||
|
"term.browse": "Browse",
|
||||||
|
"term.radio": "Radio",
|
||||||
|
"term.recentlyAdded": "Recently Added",
|
||||||
|
"term.songs": "Songs",
|
||||||
|
"term.albums": "Albums",
|
||||||
|
"term.artists": "Artists",
|
||||||
|
"term.podcasts": "Podcasts",
|
||||||
|
"term.playlists": "Playlists",
|
||||||
|
"term.playlist": "Playlist",
|
||||||
|
"term.play": "Play",
|
||||||
|
"term.pause": "Pause",
|
||||||
|
"term.previous": "Previous",
|
||||||
|
"term.next": "Next",
|
||||||
|
"term.shuffle": "Shuffle",
|
||||||
|
"term.repeat": "Repeat",
|
||||||
|
"term.volume": "Volume",
|
||||||
|
"term.mute": "Mute",
|
||||||
|
"term.unmute": "Unmute",
|
||||||
|
"term.share": "Share",
|
||||||
|
"term.settings": "Settings",
|
||||||
|
"term.seeAll": "See All",
|
||||||
|
"term.sortBy": "Sort By",
|
||||||
|
"term.sortBy.album": "Album",
|
||||||
|
"term.sortBy.artist": "Artist",
|
||||||
|
"term.sortBy.name": "Name",
|
||||||
|
"term.sortBy.genre": "Genre",
|
||||||
|
"term.sortBy.releaseDate": "Release Date",
|
||||||
|
"term.sortBy.duration": "Duration",
|
||||||
|
"term.sortOrder": "A-Z",
|
||||||
|
"term.sortOrder.ascending": "Ascending",
|
||||||
|
"term.sortOrder.descending": "Descending",
|
||||||
|
"term.viewAs": "View As",
|
||||||
|
"term.viewAs.coverArt": "Cover Art",
|
||||||
|
"term.viewAs.list": "List",
|
||||||
|
"term.size": "Size",
|
||||||
|
"term.size.normal": "Normal",
|
||||||
|
"term.size.compact": "Compact",
|
||||||
|
"term.enable": "Enable",
|
||||||
|
"term.disable": "Disable",
|
||||||
|
"term.enabled": "Enabled",
|
||||||
|
"term.disabled": "Disabled",
|
||||||
|
"term.connect": "Connect",
|
||||||
|
"term.connecting": "Connecting",
|
||||||
|
"term.disconnect": "Disconnect",
|
||||||
|
"term.authed": "Authed",
|
||||||
|
"term.confirm": "Confirm ?",
|
||||||
|
"term.more": "More",
|
||||||
|
"term.less": "Less",
|
||||||
|
"term.showMore": "Show more",
|
||||||
|
"term.showLess": "Show less",
|
||||||
|
"term.topSongs" : "Top Songs",
|
||||||
|
"term.latestReleases": "Latest Releases",
|
||||||
|
"term.time.added": "Added",
|
||||||
|
"term.time.released": "Released",
|
||||||
|
"term.time.updated": "Updated",
|
||||||
|
"term.time.hours": "hours",
|
||||||
|
"term.time.hour": "hour",
|
||||||
|
"term.time.minutes": "minutes",
|
||||||
|
"term.time.minute": "minute",
|
||||||
|
"term.time.seconds": "seconds",
|
||||||
|
"term.time.second": "second",
|
||||||
|
"term.fullscreenView": "Fullscreen View",
|
||||||
|
"term.defaultView": "Default View",
|
||||||
|
"term.spacializedAudioSetting": "Spacialized Audio Setting",
|
||||||
|
"term.clearAll": "Clear All",
|
||||||
|
"term.recentStations": "Recent Stations",
|
||||||
|
"term.language": "Language",
|
||||||
|
"term.funLanguages": "Fun",
|
||||||
|
"term.noLyrics": "Loading... / Lyrics not found./ Instrumental.",
|
||||||
|
"term.copyright": "Copyright",
|
||||||
|
"term.rightsReserved": "All Rights Reserved.",
|
||||||
|
"term.sponsor": "Sponsor this project",
|
||||||
|
"term.ciderTeam": "Cider Team",
|
||||||
|
"term.developer": "Developer",
|
||||||
|
"term.socialTeam": "Social Team",
|
||||||
|
"term.contributors": "Contributors",
|
||||||
|
"term.equalizer": "Equalizer",
|
||||||
|
"term.reset": "Reset",
|
||||||
|
"term.tracks": "tracks", // Assume x amount of tracks. e.g. 50 tracks
|
||||||
|
"term.videos": "Videos",
|
||||||
|
|
||||||
|
|
||||||
|
// Home
|
||||||
|
"home.title": "Home",
|
||||||
|
"home.recentlyPlayed": "Recently Played",
|
||||||
|
"home.recentlyAdded": "Recently Added",
|
||||||
|
"home.artistsFeed": "Your Artists Feed",
|
||||||
|
"home.artistsFeed.noArtist": "Follow some artists first and their latest releases will be here",
|
||||||
|
"home.madeForYou": "Made For You",
|
||||||
|
"home.friendsListeningTo": "Friends Listening To",
|
||||||
|
"home.followedArtists": "Followed Artists",
|
||||||
|
// Errors
|
||||||
|
"error.appleMusicSubRequired": "Apple Music requires a subscription.",
|
||||||
|
"error.connectionError": "There was a problem connecting to Apple Music.",
|
||||||
|
"error.noResults": "No Results.",
|
||||||
|
"error.noResults.description": "Try a new search.",
|
||||||
|
|
||||||
|
//Podcasts
|
||||||
|
"podcast.followOnCider": "Follow On Cider",
|
||||||
|
"podcast.followedOnCider": "Following On Cider",
|
||||||
|
"podcast.subscribeOnItunes": "Subscribe On iTunes",
|
||||||
|
"podcast.subscribedOnItunes": "Subscribed On iTunes",
|
||||||
|
"podcast.itunesStore": "iTunes Store",
|
||||||
|
"podcast.episodes": "Episodes",
|
||||||
|
"podcast.playEpisode": "Play Episode",
|
||||||
|
"podcast.website": "Podcast Website",
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
"action.addToLibrary": "Add to Library",
|
||||||
|
"action.addToLibrary.success": "Added to Library",
|
||||||
|
"action.addToLibrary.error": "Error Adding to Library",
|
||||||
|
"action.removeFromLibrary": "Remove from Library",
|
||||||
|
"action.removeFromLibrary.success": "Removed from Library",
|
||||||
|
"action.addToQueue": "Add to Queue",
|
||||||
|
"action.addToQueue.success": "Added to Queue",
|
||||||
|
"action.addToQueue.error": "Error Adding to Queue",
|
||||||
|
"action.removeFromQueue": "Remove from Queue",
|
||||||
|
"action.removeFromQueue.success": "Removed from Queue",
|
||||||
|
"action.removeFromQueue.error": "Error Removing from Queue",
|
||||||
|
"action.addToPlaylist": "Add to Playlist",
|
||||||
|
"action.removeFromPlaylist": "Remove from Playlist",
|
||||||
|
"action.addToFavorites": "Add to Favorites",
|
||||||
|
"action.follow": "Follow",
|
||||||
|
"action.follow.success": "Followed",
|
||||||
|
"action.follow.error": "Error Following",
|
||||||
|
"action.unfollow": "Unfollow",
|
||||||
|
"action.unfollow.success": "Unfollowed",
|
||||||
|
"action.unfollow.error": "Error Unfollowing",
|
||||||
|
"action.playNext": "Play Next",
|
||||||
|
"action.playLater": "Play Later",
|
||||||
|
"action.startRadio": "Start Radio",
|
||||||
|
"action.goToArtist": "Go to Artist",
|
||||||
|
"action.goToAlbum": "Go to Album",
|
||||||
|
"action.moveToTop": "Move to top",
|
||||||
|
"action.share": "Share",
|
||||||
|
"action.rename": "Rename",
|
||||||
|
"action.love": "Love",
|
||||||
|
"action.unlove": "Unlove",
|
||||||
|
"action.dislike": "Dislike",
|
||||||
|
"action.undoDislike": "Undo dislike",
|
||||||
|
"action.showWebRemoteQR": "Show Web Remote QR",
|
||||||
|
"action.playTracksNext": "Play ${app.selectedMediaItems.length} tracks next",
|
||||||
|
"action.playTracksLater": "Play ${app.selectedMediaItems.length} tracks later",
|
||||||
|
"action.removeTracks": "Remove ${self.selectedItems.length} tracks from queue",
|
||||||
|
|
||||||
|
// Settings - General
|
||||||
|
"settings.header.general": "General",
|
||||||
|
"settings.header.general.description": "Adjust the general settings for Cider.",
|
||||||
|
"settings.option.general.language": "Language",
|
||||||
|
|
||||||
|
// Language optgroups
|
||||||
|
"settings.option.general.language.main": "Languages",
|
||||||
|
"settings.option.general.language.fun": "Fun Languages",
|
||||||
|
"settings.option.general.language.unsorted": "Unsorted",
|
||||||
|
|
||||||
|
// Settings - Audio
|
||||||
|
"settings.header.audio": "Audio",
|
||||||
|
"settings.header.audio.description": "Adjust the audio settings for Cider.",
|
||||||
|
"settings.option.audio.quality": "Audio Quality", // Dropdown
|
||||||
|
"settings.header.audio.quality.high": "High",
|
||||||
|
"settings.header.audio.quality.low": "Low",
|
||||||
|
"settings.header.audio.quality.auto": "Auto",
|
||||||
|
"settings.option.audio.seamlessTransition": "Seamless Audio Transition", // Toggle
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality": "Enable Advanced Functionality", // Toggle
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.description": "Enabling AudioContext functionality will allow for extended audio features like Audio Normalization , Equalizers and Visualizers, however on some systems this may cause stuttering in audio tracks.",
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.audioNormalization": "Audio Normalization", // Toggle
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.audioNormalization.description": "Normalizes peak volume for individual tracks to create a more uniform listening experience.",
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization": "Audio Spatialization", // Toggle
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization.description": "Spatialize audio and make audio more 3-dimensional (note: This is not Dolby Atmos)",
|
||||||
|
// Settings - Visual
|
||||||
|
"settings.header.visual": "Visual",
|
||||||
|
"settings.header.visual.description": "Adjust the visual settings for Cider.",
|
||||||
|
"settings.option.visual.windowBackgroundStyle": "Window Background Style", // Toggle
|
||||||
|
"settings.header.visual.windowBackgroundStyle.none": "None",
|
||||||
|
"settings.header.visual.windowBackgroundStyle.artwork": "Artwork",
|
||||||
|
"settings.header.visual.windowBackgroundStyle.image": "Image",
|
||||||
|
"settings.option.visual.animatedArtwork": "Animated Artwork", // Dropdown
|
||||||
|
"settings.header.visual.animatedArtwork.always": "Always",
|
||||||
|
"settings.header.visual.animatedArtwork.limited": "Limited to pages and special entries",
|
||||||
|
"settings.header.visual.animatedArtwork.disable": "Disable everywhere",
|
||||||
|
"settings.option.visual.animatedArtworkQuality": "Animated Artwork Quality", // Dropdown
|
||||||
|
"settings.header.visual.animatedArtworkQuality.low": "Low",
|
||||||
|
"settings.header.visual.animatedArtworkQuality.medium": "Medium",
|
||||||
|
"settings.header.visual.animatedArtworkQuality.high": "High",
|
||||||
|
"settings.header.visual.animatedArtworkQuality.veryHigh": "Very High",
|
||||||
|
"settings.header.visual.animatedArtworkQuality.extreme": "Extreme",
|
||||||
|
"settings.option.visual.animatedWindowBackground": "Animated Window Background", // Toggle
|
||||||
|
"settings.option.visual.hardwareAcceleration": "Hardware Acceleration", // Dropdown
|
||||||
|
"settings.option.visual.hardwareAcceleration.description": "Requires relaunch",
|
||||||
|
"settings.header.visual.hardwareAcceleration.default": "Default",
|
||||||
|
"settings.header.visual.hardwareAcceleration.webGPU": "WebGPU",
|
||||||
|
// Refer to term.disabled for the disabled option
|
||||||
|
"settings.option.visual.showPersonalInfo": "Show Personal Info", // Toggle
|
||||||
|
|
||||||
|
// Settings - Lyrics
|
||||||
|
"settings.header.lyrics": "Lyrics",
|
||||||
|
"settings.header.lyrics.description": "Adjust the lyrics settings for Cider.",
|
||||||
|
"settings.option.lyrics.enableMusixmatch": "Enable Musixmatch Lyrics", // Toggle
|
||||||
|
"settings.option.lyrics.enableMusixmatchKaraoke": "Enable Karaoke Mode (Musixmatch only)", // Toggle
|
||||||
|
"settings.option.lyrics.musixmatchPreferredLanguage": "Musixmatch Translation Preferred Language", // Dropdown
|
||||||
|
"settings.option.lyrics.enableYoutubeLyrics": "Enable Youtube Lyrics for Music Videos", // Toggle
|
||||||
|
|
||||||
|
// Settings - Connectivity
|
||||||
|
"settings.header.connectivity": "Connectivity",
|
||||||
|
"settings.header.connectivity.description": "Adjust the connectivity settings for Cider.",
|
||||||
|
"settings.option.connectivity.discordRPC": "Discord Rich Presence", // Dropdown
|
||||||
|
// Refer to term.disabled for the disabled option
|
||||||
|
"settings.header.connectivity.discordRPC.cider": "Display as 'Cider'",
|
||||||
|
"settings.header.connectivity.discordRPC.appleMusic": "Display as 'Apple Music'",
|
||||||
|
"settings.option.connectivity.discordRPC.clearOnPause": "Clear Discord Rich Presence on Pause", // Toggle
|
||||||
|
"settings.option.connectivity.lastfmScrobble": "LastFM Scrobbling", // Option to Connect
|
||||||
|
"settings.option.connectivity.lastfmScrobble.delay": "LastFM Scrobble Delay (%)",
|
||||||
|
"settings.option.connectivity.lastfmScrobble.nowPlaying": "Enable LastFM Now Playing",
|
||||||
|
"settings.option.connectivity.lastfmScrobble.removeFeatured": "Remove featuring artists from song title (LastFM)",
|
||||||
|
"settings.option.connectivity.lastfmScrobble.filterLoop": "Filter looped track (LastFM)",
|
||||||
|
// Refer to term.connect for the connect button
|
||||||
|
|
||||||
|
// Settings - Experimental
|
||||||
|
"settings.header.experimental": "Experimental",
|
||||||
|
"settings.header.experimental.description": "Adjust the experimental settings for Cider.",
|
||||||
|
"settings.option.experimental.compactUI": "Compact UI", // Toggle
|
||||||
|
"settings.option.experimental.closeButtonBehaviour": "Close Button Behavior",
|
||||||
|
"settings.option.experimental.closeButtonBehaviour.quit": "Quit Cider",
|
||||||
|
"settings.option.experimental.closeButtonBehaviour.minimizeTaskbar": "Minimize to taskbar",
|
||||||
|
"settings.option.experimental.closeButtonBehaviour.minimizeTray": "Minimize to system tray",
|
||||||
|
// Refer to term.disabled & term.enabled
|
||||||
|
|
||||||
|
// Spatialization Menu
|
||||||
|
"spatial.spatialProperties" : "Spatial Properties",
|
||||||
|
"spatial.width" : "Width",
|
||||||
|
"spatial.height" : "Height",
|
||||||
|
"spatial.depth" : "Depth",
|
||||||
|
"spatial.gain" : "Gain",
|
||||||
|
"spatial.roomMaterials" : "Room Materials",
|
||||||
|
"spatial.roomDimensions" : "Room Dimensions",
|
||||||
|
"spatial.roomPositions" : "Room Positions",
|
||||||
|
"spatial.setDimensions" : "Set Dimensions",
|
||||||
|
"spatial.setPositions" : "Set Positions",
|
||||||
|
"spatial.up" : "Up",
|
||||||
|
"spatial.front" : "Front",
|
||||||
|
"spatial.left" : "Left",
|
||||||
|
"spatial.right" : "Right",
|
||||||
|
"spatial.back" : "Back",
|
||||||
|
"spatial.down" : "Down",
|
||||||
|
"spatial.listener" : "Listener",
|
||||||
|
"spatial.audioSource" : "Audio Source",
|
||||||
|
|
||||||
|
// Settings - Unfinished
|
||||||
|
"settings.header.unfinished": "Unfinished",
|
||||||
|
|
||||||
|
// Web Remote
|
||||||
|
"remote.web.title": "Cider Remote",
|
||||||
|
"remote.web.description": "Scan the QR code to pair your phone up with this Cider instance",
|
||||||
|
|
||||||
|
// About
|
||||||
|
"about.thanks": "Major thanks to the Cider Collective Team and all of our contributors."
|
||||||
|
}
|
197
src/i18n/hu_HU.jsonc
Normal file
|
@ -0,0 +1,197 @@
|
||||||
|
{ // Base File
|
||||||
|
// App info
|
||||||
|
"app.name": "Cider",
|
||||||
|
// Dialogs
|
||||||
|
"dialog.cancel": "Mégsem",
|
||||||
|
"dialog.ok": "OK",
|
||||||
|
|
||||||
|
// Notification
|
||||||
|
"notification.updatingLibrarySongs": "Zenekönyvtár frissítése...",
|
||||||
|
"notification.updatingLibraryAlbums": "Albumok frissítése...",
|
||||||
|
"notification.updatingLibraryArtists": "Előadók frissítése...",
|
||||||
|
|
||||||
|
// Terms
|
||||||
|
"term.appleMusic": "Apple Music",
|
||||||
|
"term.applePodcasts": "Apple Podcastok",
|
||||||
|
"term.itunes": "iTunes",
|
||||||
|
"term.github": "GitHub",
|
||||||
|
"term.discord": "Discord",
|
||||||
|
"term.learnMore": "Tudj meg többet",
|
||||||
|
"term.accountSettings": "Fiókbeállítások",
|
||||||
|
"term.logout": "Kijelentkezés",
|
||||||
|
"term.login": "Bejelentkezés",
|
||||||
|
"term.about": "About",
|
||||||
|
"term.privateSession": "Privát hallgatás",
|
||||||
|
"term.queue": "Várólista",
|
||||||
|
"term.search": "Keresés",
|
||||||
|
"term.library": "Könyvtár",
|
||||||
|
"term.listenNow": "Hallgatás most",
|
||||||
|
"term.browse": "Böngészés",
|
||||||
|
"term.radio": "Rádió",
|
||||||
|
"term.recentlyAdded": "Nemrég hozzáadott",
|
||||||
|
"term.songs": "Dalok",
|
||||||
|
"term.albums": "Albumok",
|
||||||
|
"term.artists": "Előadók",
|
||||||
|
"term.podcasts": "Podcastok",
|
||||||
|
"term.playlists": "Lejátszási listák",
|
||||||
|
"term.playlist": "Lejátszási lista",
|
||||||
|
"term.play": "Lejátszás",
|
||||||
|
"term.pause": "Megállítás",
|
||||||
|
"term.previous": "Előző",
|
||||||
|
"term.next": "Következő",
|
||||||
|
"term.shuffle": "Keverés",
|
||||||
|
"term.repeat": "Ismétlés",
|
||||||
|
"term.volume": "Hangerő",
|
||||||
|
"term.mute": "Némítás",
|
||||||
|
"term.unmute": "Némítás feloldása",
|
||||||
|
"term.share": "Megosztás",
|
||||||
|
"term.settings": "Beállítások",
|
||||||
|
"term.seeAll": "Összes",
|
||||||
|
"term.sortBy": "Rendezés",
|
||||||
|
"term.sortBy.album": "Album",
|
||||||
|
"term.sortBy.artist": "Előadó",
|
||||||
|
"term.sortBy.name": "Név",
|
||||||
|
"term.sortBy.genre": "Műfaj",
|
||||||
|
"term.sortBy.releaseDate": "Kiadás dátuma",
|
||||||
|
"term.sortBy.duration": "Időtartam",
|
||||||
|
"term.sortOrder": "A-Z",
|
||||||
|
"term.sortOrder.ascending": "Növekvő",
|
||||||
|
"term.sortOrder.descending": "Csökkenő",
|
||||||
|
"term.viewAs": "Megjelenítés",
|
||||||
|
"term.viewAs.coverArt": "Borító",
|
||||||
|
"term.viewAs.list": "Lista",
|
||||||
|
"term.size": "Méret",
|
||||||
|
"term.size.normal": "Normál",
|
||||||
|
"term.size.compact": "Kompakt",
|
||||||
|
"term.enable": "Be",
|
||||||
|
"term.disable": "Ki",
|
||||||
|
"term.enabled": "Be",
|
||||||
|
"term.disabled": "Ki",
|
||||||
|
"term.connect": "Csatlakoztatás",
|
||||||
|
"term.confirm": "Jóváhagyás ?",
|
||||||
|
"term.more": "Több",
|
||||||
|
"term.less": "Kevesebb",
|
||||||
|
"term.showMore": "Mutass többet",
|
||||||
|
"term.showLess": "Mutass kevesebbet",
|
||||||
|
"term.topSongs" : "A legjobb dalok",
|
||||||
|
"term.latestReleases": "Új megjelenések",
|
||||||
|
"term.time.added": "Hozzáadva",
|
||||||
|
"term.time.released": "Kiadva",
|
||||||
|
"term.time.updated": "Frissítve",
|
||||||
|
"term.fullscreenView": "Teljes képernyős mód",
|
||||||
|
"term.defaultView": "Alapértelmezett nézet",
|
||||||
|
|
||||||
|
|
||||||
|
// Home
|
||||||
|
"home.title": "Kezdőlap",
|
||||||
|
"home.recentlyPlayed": "Nemrég játszott",
|
||||||
|
"home.recentlyAdded": "Nemrég hozzáadott",
|
||||||
|
"home.artistsFeed": "Az előadóid feedje",
|
||||||
|
"home.madeForYou": "Személyre szabva",
|
||||||
|
"home.friendsListeningTo": "A barátok épp ezt hallgatják",
|
||||||
|
"home.followedArtists": "Követett előadók",
|
||||||
|
// Errors
|
||||||
|
"error.appleMusicSubRequired": "Apple Music előfizetés szükséges.",
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
"action.addToLibrary": "Hozzáadás a Könyvtárhoz",
|
||||||
|
"action.addToLibrary.success": "Hozzáadva a Könyvtárhoz",
|
||||||
|
"action.addToLibrary.error": "Hiba történt! Sikertelen hozzáadás.",
|
||||||
|
"action.removeFromLibrary": "Törlés a Könytárból",
|
||||||
|
"action.removeFromLibrary.success": "Törölve a Könyvtárból",
|
||||||
|
"action.addToQueue": "Hozzáadás a várólistához",
|
||||||
|
"action.addToQueue.success": "Hozzáadva a várólistához",
|
||||||
|
"action.addToQueue.error": "Sikertelen hozzáadás a várólistához",
|
||||||
|
"action.removeFromQueue": "Törlés a várólistáról",
|
||||||
|
"action.removeFromQueue.success": "Törölve a várólistáról",
|
||||||
|
"action.removeFromQueue.error": "Sikertelen törlés a várólistáról",
|
||||||
|
"action.addToPlaylist": "Lejátszási listához adás",
|
||||||
|
"action.removeFromPlaylist": "Törlés a lejátszási listáról",
|
||||||
|
"action.addToFavorites": "Hozzáadás a kedvencekhez",
|
||||||
|
"action.follow": "Követés",
|
||||||
|
"action.follow.success": "Követve",
|
||||||
|
"action.follow.error": "Sikertelen követés",
|
||||||
|
"action.unfollow": "Követés visszavonása",
|
||||||
|
"action.unfollow.success": "Követés visszavonva",
|
||||||
|
"action.unfollow.error": "Sikertelen visszavonás",
|
||||||
|
"action.playNext": "Lejátszás következőként",
|
||||||
|
"action.playLater": "Lejátszás utolsóként",
|
||||||
|
"action.startRadio": "Állomás létrehozása",
|
||||||
|
"action.goToArtist": "Előadó megjelenítése",
|
||||||
|
"action.goToAlbum": "Album megjelenítése",
|
||||||
|
"action.moveToTop": "Mozgatás legfelülre",
|
||||||
|
"action.share": "Megosztás",
|
||||||
|
"action.rename": "Átnevezés",
|
||||||
|
"action.love": "Szeretem",
|
||||||
|
"action.unlove": "Mégsem szeretem",
|
||||||
|
"action.dislike": "Kevesebb ilyen javasolása",
|
||||||
|
"action.undoDislike": "A Kevesebb ilyen javasolása visszavonása",
|
||||||
|
"action.showWebRemoteQR": "Távirányító QR kód megjelenítése",
|
||||||
|
// Settings - Audio
|
||||||
|
"settings.header.audio": "Hang",
|
||||||
|
"settings.header.audio.description": "A Cider hangbeállításainak módosítása.",
|
||||||
|
"settings.option.audio.quality": "Hangminőség", // Dropdown
|
||||||
|
"settings.header.audio.quality.high": "Magas",
|
||||||
|
"settings.header.audio.quality.low": "Alacsony",
|
||||||
|
"settings.header.audio.quality.auto": "Auto",
|
||||||
|
"settings.option.audio.seamlessTransition": "Szünetmentes lejátszás", // Toggle
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality": "Haladó funkcionalitás engedélyezése", // Toggle
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.description": "Az AudioContext funkció engedélyezése lehetővé teszi a fejlettebb hangfunkciókat, például a Normalizásást, az Equalizereket és a Visualizer funkciókat, azonban egyes számítógépeken ez akadozást okozhat a hangsávokban.",
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.audioNormalization": "Normalizálás", // Toggle
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.audioNormalization.description": "Normalizálja az egyes zeneszámok hangosabb részeit, hogy egységesebb hallgatási élményt hozzon létre.",
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization": "Térbeli hang", // Toggle
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization.description": "Térbeli hang és a hang háromdimenziósabbá tétele (Ez nem összekeverendő a Dolby Atmos-szal!)",
|
||||||
|
// Settings - Visual
|
||||||
|
"settings.header.visual": "Vizuális",
|
||||||
|
"settings.header.visual.description": "A Cider vizuális beállításainak módosítása.",
|
||||||
|
"settings.option.visual.windowBackgroundStyle": "Ablak háttér stílusa", // Toggle
|
||||||
|
"settings.header.visual.windowBackgroundStyle.none": "None",
|
||||||
|
"settings.header.visual.windowBackgroundStyle.artwork": "Borító",
|
||||||
|
"settings.option.visual.animatedArtwork": "Animált borító", // Dropdown
|
||||||
|
"settings.header.visual.animatedArtwork.always": "Mindig",
|
||||||
|
"settings.header.visual.animatedArtwork.limited": "Oldalakra és speciális bejegyzésekre korlátozva.",
|
||||||
|
"settings.header.visual.animatedArtwork.disable": "Kikapcsolás mindenhol",
|
||||||
|
"settings.option.visual.animatedArtworkQuality": "Animált borító minősége", // Dropdown
|
||||||
|
"settings.header.visual.animatedArtworkQuality.low": "Alacsony",
|
||||||
|
"settings.header.visual.animatedArtworkQuality.medium": "Közepes",
|
||||||
|
"settings.header.visual.animatedArtworkQuality.high": "Magas",
|
||||||
|
"settings.header.visual.animatedArtworkQuality.extreme": "Extrém",
|
||||||
|
"settings.option.visual.animatedWindowBackground": "Animált ablakháttér", // Toggle
|
||||||
|
"settings.option.visual.hardwareAcceleration": "Hardveres gyorsítás", // Dropdown
|
||||||
|
"settings.header.visual.hardwareAcceleration.default": "Alap",
|
||||||
|
"settings.header.visual.hardwareAcceleration.webGPU": "WebGPU",
|
||||||
|
// Refer to term.disabled for the disabled option
|
||||||
|
"settings.option.visual.showPersonalInfo": "Személyes adatok mutatása", // Toggle
|
||||||
|
// Settings - General (Reserved)
|
||||||
|
"settings.header.general": "Általános",
|
||||||
|
"settings.header.general.description": "A Cider általános beállításainak módosítása.",
|
||||||
|
|
||||||
|
// Settings - Lyrics
|
||||||
|
"settings.header.lyrics": "Dalszöveg",
|
||||||
|
"settings.header.lyrics.description": "A Cider dalszöveg beállításainak módosítása.",
|
||||||
|
"settings.option.lyrics.enableMusixmatch": "MusixMatch dalszövegek engedélyezése", // Toggle
|
||||||
|
"settings.option.lyrics.enableMusixmatchKaraoke": "Karaoke mód bekapcsolása (Csak MusixMatch)", // Toggle
|
||||||
|
"settings.option.lyrics.musixmatchPreferredLanguage": "MusixMatch fordítás nyelve", // Dropdown
|
||||||
|
"settings.option.lyrics.enableYoutubeLyrics": "YouTube dalszövegek engedélyezése a zenei videóknál", // Toggle
|
||||||
|
|
||||||
|
// Settings - Connectivity
|
||||||
|
"settings.header.connectivity": "Csatlakozások",
|
||||||
|
"settings.header.connectivity.description": "A Cider csatlakozás beállításainak módosítása.",
|
||||||
|
"settings.option.connectivity.discordRPC": "Discord Rich Presence", // Dropdown
|
||||||
|
// Refer to term.disabled for the disabled option
|
||||||
|
"settings.header.connectivity.discordRPC.cider": "Megjelenítés 'Cider'-ként",
|
||||||
|
"settings.header.connectivity.discordRPC.appleMusic": "Megjelenítés 'Apple Music'-ként",
|
||||||
|
"settings.option.connectivity.discordRPC.clearOnPause": "Discord Rich Presence törlése megállításnál", // Toggle
|
||||||
|
"settings.option.connectivity.lastfmScrobble": "LastFM Scrobbling", // Option to Connect
|
||||||
|
// Refer to term.connect for the connect button
|
||||||
|
|
||||||
|
// Settings - Experimental
|
||||||
|
"settings.header.experimental": "Kísérleti",
|
||||||
|
"settings.header.experimental.description": "A Cider kísérleti beállításainak módosítása.",
|
||||||
|
"settings.option.experimental.compactUI": "Kompakt UI", // Toggle
|
||||||
|
// Refer to term.disabled & term.enabled
|
||||||
|
|
||||||
|
// Web Remote
|
||||||
|
"remote.web.title": "Cider Remote",
|
||||||
|
"remote.web.description": "Olvasd be ezt a QR-kódot a telefonoddal, hogy tudd vezérelni a lejátszót."
|
||||||
|
}
|
283
src/i18n/ja_JP.jsonc
Normal file
|
@ -0,0 +1,283 @@
|
||||||
|
{
|
||||||
|
// App info
|
||||||
|
"app.name": "Cider",
|
||||||
|
|
||||||
|
"date.format": "${y}年${m}月${d}日",
|
||||||
|
|
||||||
|
// i18n Info
|
||||||
|
"i18n.languageName": "日本語", // name of language in native language
|
||||||
|
"i18n.languageNameEnglish": "Japanese", // name of language in English
|
||||||
|
"i18n.category": "main", // main = real language, fun = fun community languages
|
||||||
|
"i18n.authors": "@maikirakiwi", // Authors, if you contribute to this file feel free to add your name seperated with a space
|
||||||
|
|
||||||
|
// Dialogs
|
||||||
|
"dialog.cancel": "キャンセル",
|
||||||
|
"dialog.ok": "OK",
|
||||||
|
|
||||||
|
// Notification
|
||||||
|
"notification.updatingLibrarySongs": "ライブラリの更新中...",
|
||||||
|
"notification.updatingLibraryAlbums": "ライブラリの更新中...",
|
||||||
|
"notification.updatingLibraryArtists": "ライブラリの更新中...",
|
||||||
|
|
||||||
|
// Terms
|
||||||
|
"term.appleMusic": "Apple Music", // Follows brand term
|
||||||
|
"term.applePodcasts": "Apple Podcasts", // Follows brand term
|
||||||
|
"term.itunes": "iTunes", // Follows brand term
|
||||||
|
"term.github": "GitHub", // Follows brand term
|
||||||
|
"term.discord": "Discord", // Follows brand term
|
||||||
|
"term.learnMore": "詳しい情報",
|
||||||
|
"term.accountSettings": "アカウント設定",
|
||||||
|
"term.logout": "サインアウト",
|
||||||
|
"term.login": "サインイン",
|
||||||
|
"term.about": "Ciderについて",
|
||||||
|
"term.privateSession": "プライベートセッション",
|
||||||
|
"term.queue": "次はこちら",
|
||||||
|
"term.search": "検索",
|
||||||
|
"term.library": "ライブラリ",
|
||||||
|
"term.listenNow": "今すぐ聴く",
|
||||||
|
"term.browse": "見つける",
|
||||||
|
"term.radio": "ラジオ",
|
||||||
|
"term.recentlyAdded": "最近追加した項目",
|
||||||
|
"term.songs": "曲",
|
||||||
|
"term.albums": "アルバム",
|
||||||
|
"term.artists": "アーティスト",
|
||||||
|
"term.podcasts": "Podcast",
|
||||||
|
"term.playlists": "プレイリスト",
|
||||||
|
"term.playlist": "プレイリスト",
|
||||||
|
"term.play": "再生",
|
||||||
|
"term.pause": "停止",
|
||||||
|
"term.previous": "戻る",
|
||||||
|
"term.next": "次へ",
|
||||||
|
"term.shuffle": "シャッフル",
|
||||||
|
"term.repeat": "リピート",
|
||||||
|
"term.volume": "音量",
|
||||||
|
"term.mute": "ミュート",
|
||||||
|
"term.unmute": "ミュート解除",
|
||||||
|
"term.share": "共有",
|
||||||
|
"term.settings": "設定",
|
||||||
|
"term.seeAll": "すべて見る",
|
||||||
|
"term.sortBy": "並べ替え",
|
||||||
|
"term.sortBy.album": "アルバム",
|
||||||
|
"term.sortBy.artist": "アーティスト",
|
||||||
|
"term.sortBy.name": "曲名",
|
||||||
|
"term.sortBy.genre": "ジャンル",
|
||||||
|
"term.sortBy.releaseDate": "配信開始日",
|
||||||
|
"term.sortBy.duration": "時間",
|
||||||
|
"term.sortOrder": "並べ替え",
|
||||||
|
"term.sortOrder.ascending": "昇順",
|
||||||
|
"term.sortOrder.descending": "降順",
|
||||||
|
"term.viewAs": "表示",
|
||||||
|
"term.viewAs.coverArt": "カバーアート",
|
||||||
|
"term.viewAs.list": "リスト",
|
||||||
|
"term.size": "サイズ",
|
||||||
|
"term.size.normal": "普通",
|
||||||
|
"term.size.compact": "コンパクト",
|
||||||
|
"term.enable": "ON",
|
||||||
|
"term.disable": "OFF",
|
||||||
|
"term.enabled": "ON",
|
||||||
|
"term.disabled": "OFF",
|
||||||
|
"term.connect": "接続",
|
||||||
|
"term.connecting": "接続中",
|
||||||
|
"term.disconnect": "切断",
|
||||||
|
"term.authed": "認証済み",
|
||||||
|
"term.confirm": "よろしいでしょうか?",
|
||||||
|
"term.more": "もっと",
|
||||||
|
"term.less": "減らす",
|
||||||
|
"term.showMore": "もっと見る",
|
||||||
|
"term.showLess": "表示数を少なくする",
|
||||||
|
"term.topSongs" : "トップソング",
|
||||||
|
"term.latestReleases": "ニューリリース",
|
||||||
|
"term.time.added": "追加日",
|
||||||
|
"term.time.released": "配信開始日",
|
||||||
|
"term.time.updated": "最終更新日",
|
||||||
|
"term.time.hours": "時間",
|
||||||
|
"term.time.hour": "時間",
|
||||||
|
"term.time.minutes": "分",
|
||||||
|
"term.time.minute": "分",
|
||||||
|
"term.time.seconds": "秒",
|
||||||
|
"term.time.second": "秒",
|
||||||
|
"term.fullscreenView": "全画面表示",
|
||||||
|
"term.defaultView": "ウィンドウ表示",
|
||||||
|
"term.spacializedAudioSetting": "オーディオ空間化設定",
|
||||||
|
"term.clearAll": "消去",
|
||||||
|
"term.recentStations": "最近の再生",
|
||||||
|
"term.language": "言語",
|
||||||
|
"term.noLyrics": "ローディング。。 / 歌詞が見つからない / 器楽曲.",
|
||||||
|
"term.copyright": "著作権",
|
||||||
|
"term.rightsReserved": "All Rights Reserved.", // Translation does not exist in Japanese
|
||||||
|
"term.sponsor": "スポンサーになりましょう",
|
||||||
|
"term.ciderTeam": "Cider チーム",
|
||||||
|
"term.developer": "開発者",
|
||||||
|
"term.socialTeam": "ソーシャル チーム",
|
||||||
|
"term.contributors": "貢献者",
|
||||||
|
"term.equalizer": "イコライザー",
|
||||||
|
"term.reset": "リセット",
|
||||||
|
"term.tracks": "曲", // Assume x amount of tracks. e.g. 50 tracks
|
||||||
|
|
||||||
|
// Home
|
||||||
|
"home.title": "ホーム",
|
||||||
|
"home.recentlyPlayed": "最近の再生",
|
||||||
|
"home.recentlyAdded": "最近追加した項目",
|
||||||
|
"home.artistsFeed": "アーティストのフィード",
|
||||||
|
"home.artistsFeed.noArtist": "自分の好きなアーティストをフォローしましょう・",
|
||||||
|
"home.madeForYou": "あなたにおすすめ",
|
||||||
|
"home.friendsListeningTo": "友達が聴いている",
|
||||||
|
"home.followedArtists": "フォローしているアーティスト",
|
||||||
|
// Errors
|
||||||
|
"error.appleMusicSubRequired": "Apple Musicのサブスクリプションが必要です。",
|
||||||
|
"error.connectionError": "Apple Musicに接続できません。",
|
||||||
|
"error.noResults": "見つかりませんでした",
|
||||||
|
"error.noResults.description": "もう一度お試しください。",
|
||||||
|
|
||||||
|
//Podcasts
|
||||||
|
"podcast.followOnCider": "Ciderでフォロー",
|
||||||
|
"podcast.followedOnCider": "フォロー中",
|
||||||
|
"podcast.subscribeOnItunes": "iTunesで購読",
|
||||||
|
"podcast.subscribedOnItunes": "購読中",
|
||||||
|
"podcast.itunesStore": "iTunes Store",
|
||||||
|
"podcast.episodes": "番組",
|
||||||
|
"podcast.playEpisode": "再生",
|
||||||
|
"podcast.website": "Podcast ウェブ",
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
"action.addToLibrary": "ライブラリに追加",
|
||||||
|
"action.addToLibrary.success": "ライブラリに追加されました",
|
||||||
|
"action.addToLibrary.error": "ライブラリへの追加に失敗しました",
|
||||||
|
"action.removeFromLibrary": "ライブラリから削除",
|
||||||
|
"action.removeFromLibrary.success": "ライブラリから削除されました",
|
||||||
|
"action.addToQueue": "「次はこちら」に項目を追加",
|
||||||
|
"action.addToQueue.success": "「次はこちら」に項目を追加されました",
|
||||||
|
"action.addToQueue.error": "操作を完了できませんでした",
|
||||||
|
"action.addToPlaylist": "プレイリストに追加",
|
||||||
|
"action.removeFromPlaylist": "プレイリストから削除",
|
||||||
|
"action.addToFavorites": "ラブに追加",
|
||||||
|
"action.removeFromQueue": "「次はこちら」から項目を削除",
|
||||||
|
"action.removeFromQueue.success": "「次はこちら」から項目を削除されました",
|
||||||
|
"action.removeFromQueue.error": "操作を完了できませんでした",
|
||||||
|
"action.follow": "フォロー",
|
||||||
|
"action.follow.success": "フォロー中",
|
||||||
|
"action.follow.error": "操作を完了できませんでした",
|
||||||
|
"action.unfollow": "フォロー解除",
|
||||||
|
"action.unfollow.success": "フォローをやめました",
|
||||||
|
"action.unfollow.error": "操作を完了できませんでした",
|
||||||
|
"action.playNext": "次に再生",
|
||||||
|
"action.playLater": "最後に再生",
|
||||||
|
"action.startRadio": "ステーションを作成",
|
||||||
|
"action.goToArtist": "アーティストへ移動",
|
||||||
|
"action.goToAlbum": "アルバムへ移動",
|
||||||
|
"action.moveToTop": "上に戻る",
|
||||||
|
"action.share": "曲を共有",
|
||||||
|
"action.rename": "名前の変更",
|
||||||
|
"action.love": "ラブ",
|
||||||
|
"action.unlove": "ラブを解除",
|
||||||
|
"action.dislike": "これに似たものをすすめない",
|
||||||
|
"action.undoDislike": "「これと似た曲のおすすめを減らす」を取り消す",
|
||||||
|
"action.showWebRemoteQR": "WEBリモコンQRコードを表示",
|
||||||
|
"action.playTracksNext": "${app.selectedMediaItems.length}曲が次に再生",
|
||||||
|
"action.playTracksLater": "${app.selectedMediaItems.length}曲が最後に再生",
|
||||||
|
"action.removeTracks": "${self.selectedItems.length}曲が「次はこちら」から削除",
|
||||||
|
|
||||||
|
// Settings - Audio
|
||||||
|
"settings.header.audio": "オーディオ",
|
||||||
|
"settings.header.audio.description": "Ciderのオーディオ設定",
|
||||||
|
"settings.option.audio.quality": "音質", // Dropdown
|
||||||
|
"settings.header.audio.quality.high": "高品質",
|
||||||
|
"settings.header.audio.quality.low": "高効率",
|
||||||
|
"settings.header.audio.quality.auto": "自動",
|
||||||
|
"settings.option.audio.seamlessTransition": "曲間なしで再生", // Toggle
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality": "先進的な機能", // Toggle
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.description": "AudioContext 機能を有効にすると、オーディオノーマライズ、空間オーディオ、イコライザーなどの機能を使用できますが、音が途切れるかもしれません。", // Toggle
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.audioNormalization": "オーディオノーマライズ", // Toggle
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.audioNormalization.description": "さまざまな曲の音量を均一にし、より整った音を楽しめるようにする機能です。",
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization": "オーディオ空間化", // Toggle
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization.description": "オーディオを空間に分散させる機能です。 (ドルビーアトモスではありません)",
|
||||||
|
// Settings - Visual
|
||||||
|
"settings.header.visual": "ビジュアル",
|
||||||
|
"settings.header.visual.description": "Ciderのビジュアル設定",
|
||||||
|
"settings.option.visual.windowBackgroundStyle": "アプリウインドウの背景スタイル", // Toggle
|
||||||
|
"settings.header.visual.windowBackgroundStyle.none": "なし",
|
||||||
|
"settings.header.visual.windowBackgroundStyle.artwork": "アートワーク",
|
||||||
|
"settings.option.visual.animatedArtwork": "アニメーションアートワーク", // Dropdown
|
||||||
|
"settings.header.visual.animatedArtwork.always": "常に表示",
|
||||||
|
"settings.header.visual.animatedArtwork.limited": "アーティストページのみ表示",
|
||||||
|
"settings.header.visual.animatedArtwork.disable": "オフ",
|
||||||
|
"settings.option.visual.animatedArtworkQuality": "アニメーションアートワークの品質", // Dropdown
|
||||||
|
"settings.header.visual.animatedArtworkQuality.low": "低",
|
||||||
|
"settings.header.visual.animatedArtworkQuality.medium": "中",
|
||||||
|
"settings.header.visual.animatedArtworkQuality.high": "高",
|
||||||
|
"settings.header.visual.animatedArtworkQuality.veryHigh": "超高",
|
||||||
|
"settings.header.visual.animatedArtworkQuality.extreme": "最高",
|
||||||
|
"settings.option.visual.animatedWindowBackground": "アプリウィンドウの背景をアニメーション化", // Toggle
|
||||||
|
"settings.option.visual.hardwareAcceleration": "ハードウェア アクセラレーション", // Dropdown
|
||||||
|
"settings.option.visual.hardwareAcceleration.description": "アプリを再起動する必要があります",
|
||||||
|
"settings.header.visual.hardwareAcceleration.default": "既定",
|
||||||
|
"settings.header.visual.hardwareAcceleration.webGPU": "WebGPU",
|
||||||
|
// Refer to term.disabled for the disabled option
|
||||||
|
"settings.option.visual.showPersonalInfo": "プロフィールを表示", // Toggle
|
||||||
|
// Settings - General (Reserved)
|
||||||
|
"settings.header.general": "一般",
|
||||||
|
"settings.header.general.description": "Ciderの一般設定",
|
||||||
|
|
||||||
|
// Settings - Lyrics
|
||||||
|
"settings.header.lyrics": "歌詞",
|
||||||
|
"settings.header.lyrics.description": "歌詞の表示設定",
|
||||||
|
"settings.option.lyrics.enableMusixmatch": "Musixmatchで歌詞を表示する", // Toggle
|
||||||
|
"settings.option.lyrics.enableMusixmatchKaraoke": "カラオケモードを有効にする (Musixmatchのみ)", // Toggle
|
||||||
|
"settings.option.lyrics.musixmatchPreferredLanguage": "歌詞の優先言語", // Dropdown
|
||||||
|
"settings.option.lyrics.enableYoutubeLyrics": "YouTubeの歌詞をミュージックビデオに使用する", // Toggle
|
||||||
|
|
||||||
|
// Settings - Connectivity
|
||||||
|
"settings.header.connectivity": "アプリと連携",
|
||||||
|
"settings.header.connectivity.description": "Ciderの連携設定",
|
||||||
|
"settings.option.connectivity.discordRPC": "Discord Rich Presence", // Dropdown
|
||||||
|
// Refer to term.disabled for the disabled option
|
||||||
|
"settings.header.connectivity.discordRPC.cider": "'Cider' を表示する",
|
||||||
|
"settings.header.connectivity.discordRPC.appleMusic": "'Apple Music' を表示する",
|
||||||
|
"settings.option.connectivity.discordRPC.clearOnPause": "一時停止時にDiscord Rich Presenceをクリアする", // Toggle
|
||||||
|
"settings.option.connectivity.lastfmScrobble": "LastFM Scrobbling", // Option to Connect
|
||||||
|
"settings.option.connectivity.lastfmScrobble.delay": "LastFM Scrobble Delay (%)",
|
||||||
|
"settings.option.connectivity.lastfmScrobble.nowPlaying": "Enable LastFM Now Playing",
|
||||||
|
"settings.option.connectivity.lastfmScrobble.removeFeatured": "Remove featuring artists from song title (LastFM)",
|
||||||
|
// Refer to term.connect for the connect button
|
||||||
|
|
||||||
|
// Settings - Experimental
|
||||||
|
"settings.header.experimental": "試験的な機能",
|
||||||
|
"settings.header.experimental.description": "開発中の実験的な機能は不完全で不安定である可能性があります",
|
||||||
|
"settings.option.experimental.compactUI": "コンパクトインターフェース", // Toggle
|
||||||
|
"settings.option.experimental.closeButtonBehaviour": "「閉じる」ボタンの動作", // Dropdown
|
||||||
|
"settings.option.experimental.closeButtonBehaviour.quit": "アプリを終了する",
|
||||||
|
"settings.option.experimental.closeButtonBehaviour.minimizeTaskbar": "タスクバーに最小化する",
|
||||||
|
"settings.option.experimental.closeButtonBehaviour.minimizeTray": "トレイに最小化する",
|
||||||
|
// Refer to term.disabled & term.enabled
|
||||||
|
|
||||||
|
// Spatialization Menu
|
||||||
|
"spatial.spatialProperties" : "空間化のプロパティ",
|
||||||
|
"spatial.width" : "幅",
|
||||||
|
"spatial.height" : "高さ",
|
||||||
|
"spatial.depth" : "奥行",
|
||||||
|
"spatial.gain" : "ゲイン",
|
||||||
|
"spatial.roomMaterials" : "部屋のマテリアル",
|
||||||
|
"spatial.roomDimensions" : "部屋の大きさ",
|
||||||
|
"spatial.roomPositions" : "部屋の位置",
|
||||||
|
"spatial.setDimensions" : "大きさを設定",
|
||||||
|
"spatial.setPositions" : "位置を設定",
|
||||||
|
"spatial.up" : "上",
|
||||||
|
"spatial.front" : "前",
|
||||||
|
"spatial.left" : "左",
|
||||||
|
"spatial.right" : "右",
|
||||||
|
"spatial.back" : "後",
|
||||||
|
"spatial.down" : "下",
|
||||||
|
"spatial.listener" : "リスナー",
|
||||||
|
"spatial.audioSource" : "音源",
|
||||||
|
|
||||||
|
// Settings - Unfinished
|
||||||
|
"settings.header.unfinished": "未完成",
|
||||||
|
|
||||||
|
// Web Remote
|
||||||
|
"remote.web.title": "Cider リモート",
|
||||||
|
"remote.web.description": "QRコードを使用して、Ciderとスマートフォンをペアリングする",
|
||||||
|
|
||||||
|
//About
|
||||||
|
"about.thanks": "Cider Collective とご協力いただいた貢献者様に感謝申し上げます。"
|
||||||
|
|
||||||
|
}
|
238
src/i18n/pt_BR.jsonc
Normal file
|
@ -0,0 +1,238 @@
|
||||||
|
{ // Base File
|
||||||
|
// App info
|
||||||
|
"app.name": "Cider",
|
||||||
|
|
||||||
|
"date.format": "${d} ${m}, ${y}",
|
||||||
|
|
||||||
|
// Dialogs
|
||||||
|
"dialog.cancel": "Cancelar",
|
||||||
|
"dialog.ok": "OK",
|
||||||
|
|
||||||
|
// Notification
|
||||||
|
"notification.updatingLibrarySongs": "Atualizando músicas na biblioteca...",
|
||||||
|
"notification.updatingLibraryAlbums": "Atualizando albuns na biblioteca...",
|
||||||
|
"notification.updatingLibraryArtists": "Atualizando artistas na biblioteca...",
|
||||||
|
"notification.connectionError": "Houve um problema a se conectar no Apple Music",
|
||||||
|
|
||||||
|
// Terms
|
||||||
|
"term.appleMusic": "Apple Music",
|
||||||
|
"term.applePodcasts": "Apple Podcasts",
|
||||||
|
"term.itunes": "iTunes",
|
||||||
|
"term.github": "GitHub",
|
||||||
|
"term.discord": "Discord",
|
||||||
|
"term.learnMore": "Saiba Mais",
|
||||||
|
"term.accountSettings": "Definições da Conta",
|
||||||
|
"term.logout": "Sair",
|
||||||
|
"term.login": "Entrar",
|
||||||
|
"term.about": "Sobre",
|
||||||
|
"term.privateSession": "Sessão Privada",
|
||||||
|
"term.queue": "Fila",
|
||||||
|
"term.search": "Procurar",
|
||||||
|
"term.library": "Biblioteca",
|
||||||
|
"term.listenNow": "Ouça Agora",
|
||||||
|
"term.browse": "Explorar",
|
||||||
|
"term.radio": "Radio",
|
||||||
|
"term.recentlyAdded": "Adicionado Recentemente",
|
||||||
|
"term.songs": "Músicas",
|
||||||
|
"term.albums": "Albuns",
|
||||||
|
"term.artists": "Artistas",
|
||||||
|
"term.podcasts": "Podcasts",
|
||||||
|
"term.playlists": "Listas de Reprodução",
|
||||||
|
"term.playlist": "Lista de Reprodução",
|
||||||
|
"term.play": "Tocar",
|
||||||
|
"term.pause": "Pausar",
|
||||||
|
"term.previous": "Anterior",
|
||||||
|
"term.next": "Próximo",
|
||||||
|
"term.shuffle": "Aleatório",
|
||||||
|
"term.repeat": "Repetir",
|
||||||
|
"term.volume": "Volume",
|
||||||
|
"term.mute": "Mudo",
|
||||||
|
"term.unmute": "Tirar o Mudo",
|
||||||
|
"term.share": "Partilhar",
|
||||||
|
"term.settings": "Definições",
|
||||||
|
"term.seeAll": "Ver Tudo",
|
||||||
|
"term.sortBy": "Organizar Por",
|
||||||
|
"term.sortBy.album": "Album",
|
||||||
|
"term.sortBy.artist": "Artista",
|
||||||
|
"term.sortBy.name": "Nome",
|
||||||
|
"term.sortBy.genre": "Genero",
|
||||||
|
"term.sortBy.releaseDate": "Data de Lançamento",
|
||||||
|
"term.sortBy.duration": "Duração",
|
||||||
|
"term.sortOrder": "A-Z",
|
||||||
|
"term.sortOrder.ascending": "Ascendente",
|
||||||
|
"term.sortOrder.descending": "Descendente",
|
||||||
|
"term.viewAs": "Ver Como",
|
||||||
|
"term.viewAs.coverArt": "Capa",
|
||||||
|
"term.viewAs.list": "Lista",
|
||||||
|
"term.size": "Tamanho",
|
||||||
|
"term.size.normal": "Normal",
|
||||||
|
"term.size.compact": "Compacto",
|
||||||
|
"term.enable": "Ativar",
|
||||||
|
"term.disable": "Desativar",
|
||||||
|
"term.enabled": "Ativado",
|
||||||
|
"term.disabled": "Desativado",
|
||||||
|
"term.connect": "Conectar",
|
||||||
|
"term.disconnect": "Desconectar",
|
||||||
|
"term.connecting": "Conectando",
|
||||||
|
"term.confirm": "Confirmar ?",
|
||||||
|
"term.more": "Mais",
|
||||||
|
"term.less": "Menos",
|
||||||
|
"term.showMore": "Mostrar Mais",
|
||||||
|
"term.showLess": "Mostrar Menos",
|
||||||
|
"term.topSongs" : "Top de Músicas",
|
||||||
|
"term.latestReleases": "Ultimos Lançamentos",
|
||||||
|
"term.time.added": "Adicionado",
|
||||||
|
"term.time.released": "Lançado",
|
||||||
|
"term.time.updated": "Atualizado",
|
||||||
|
"term.fullscreenView": "Visualização em Tela Cheia",
|
||||||
|
"term.defaultView": "Visualização Normal",
|
||||||
|
"term.spacializedAudioSetting": "Definições de Audio Espacial",
|
||||||
|
"term.clearAll": "Limpar Tudo",
|
||||||
|
"term.language": "Idioma",
|
||||||
|
"term.recentStations": "Estações Recentes",
|
||||||
|
|
||||||
|
// Home
|
||||||
|
"home.title": "Inicio",
|
||||||
|
"home.recentlyPlayed": "Tocado Recentemente",
|
||||||
|
"home.recentlyAdded": "Adicionado Recentemente",
|
||||||
|
"home.artistsFeed": "Novidades dos seus Artistas",
|
||||||
|
"home.madeForYou": "Feito para Você",
|
||||||
|
"home.friendsListeningTo": "Amigos Ouvindo",
|
||||||
|
"home.followedArtists": "Artistas Seguidos",
|
||||||
|
// Errors
|
||||||
|
"error.appleMusicSubRequired": "Necessário uma assinatura Apple Music.",
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
"action.addToLibrary": "Adicionar à Biblioteca",
|
||||||
|
"action.addToLibrary.success": "Adicionado à Biblioteca",
|
||||||
|
"action.addToLibrary.error": "Erro ao Adicionar na Biblioteca",
|
||||||
|
"action.removeFromLibrary": "Remover da Biblioteca",
|
||||||
|
"action.removeFromLibrary.success": "Removido da Biblioteca",
|
||||||
|
"action.addToQueue": "Adicionar à Fila",
|
||||||
|
"action.addToQueue.success": "Adicionado à Fila",
|
||||||
|
"action.addToQueue.error": "Erro ao Adicionar à Fila",
|
||||||
|
"action.removeFromQueue": "Remover da Fila",
|
||||||
|
"action.removeFromQueue.success": "Removido da Fila",
|
||||||
|
"action.removeFromQueue.error": "Erro ao Remover da Fila",
|
||||||
|
"action.addToPlaylist": "Adicionar à Lista de Reprodução",
|
||||||
|
"action.removeFromPlaylist": "Remover da Lista de Reprodução",
|
||||||
|
"action.addToFavorites": "Adicionar aos Favoritos",
|
||||||
|
"action.follow": "Seguir",
|
||||||
|
"action.follow.success": "A Seguir",
|
||||||
|
"action.follow.error": "Erro ao Seguir",
|
||||||
|
"action.unfollow": "Deixar de Seguir",
|
||||||
|
"action.unfollow.success": "Deixou de Seguir",
|
||||||
|
"action.unfollow.error": "Erro ao Deixar de Seguir",
|
||||||
|
"action.playNext": "Tocar Proximo",
|
||||||
|
"action.playLater": "Tocar por Ultimo",
|
||||||
|
"action.startRadio": "Começar Radio",
|
||||||
|
"action.goToArtist": "Ir para o Artista",
|
||||||
|
"action.goToAlbum": "Ir para o Album",
|
||||||
|
"action.moveToTop": "Mover para cima",
|
||||||
|
"action.share": "Partilhar",
|
||||||
|
"action.rename": "Mudar o Nome",
|
||||||
|
"action.love": "Gostar",
|
||||||
|
"action.unlove": "Deixar de Gostar",
|
||||||
|
"action.dislike": "Não Gostar",
|
||||||
|
"action.undoDislike": "Deixar de Não Gostar",
|
||||||
|
"action.showWebRemoteQR": "Mostrar o QR para Página Remota",
|
||||||
|
// Settings - Audio
|
||||||
|
"settings.header.audio": "Audio",
|
||||||
|
"settings.header.audio.description": "Ajustar as definições de audio no Cider.",
|
||||||
|
"settings.option.audio.quality": "Qualidade do Audio", // Dropdown
|
||||||
|
"settings.header.audio.quality.high": "Alta",
|
||||||
|
"settings.header.audio.quality.low": "Baixa",
|
||||||
|
"settings.header.audio.quality.auto": "Automatico",
|
||||||
|
"settings.option.audio.seamlessTransition": "Transição de Áudio Perfeita", // Toggle
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality": "Ativar Funcionabilidades Avançadas", // Toggle
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.description": "Habilitar a funcionalidade AudioContext permitirá recursos de áudio estendidos, como Normalização de Áudio , Equalizadores e Visualizadores. No entanto, em alguns sistemas, isso pode causar travamentos nas faixas de áudio.",
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.audioNormalization": "Normalização de Audio", // Toggle
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.audioNormalization.description": "Normaliza o volume alto para faixas individuais para criar uma experiência de audição mais uniforme.",
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization": "Audio Espacial", // Toggle
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization.description": "Espacialize o áudio e torne o áudio mais tridimensional (nota: isto não é Dolby Atmos)",
|
||||||
|
// Settings - Visual
|
||||||
|
"settings.header.visual": "Visual",
|
||||||
|
"settings.header.visual.description": "Ajustar as Definições de Visual do Cider.",
|
||||||
|
"settings.option.visual.windowBackgroundStyle": "Estilo do Fundo da Janela", // Toggle
|
||||||
|
"settings.header.visual.windowBackgroundStyle.none": "Nenhum",
|
||||||
|
"settings.header.visual.windowBackgroundStyle.artwork": "Capa",
|
||||||
|
"settings.option.visual.animatedArtwork": "Capa Animada", // Dropdown
|
||||||
|
"settings.header.visual.animatedArtwork.always": "Sempre",
|
||||||
|
"settings.header.visual.animatedArtwork.limited": "Limitado a páginas e entradas especiais",
|
||||||
|
"settings.header.visual.animatedArtwork.disable": "Desativar em Tudo",
|
||||||
|
"settings.option.visual.animatedArtworkQuality": "Qualidade da Capa Animada", // Dropdown
|
||||||
|
"settings.header.visual.animatedArtworkQuality.low": "Baixa",
|
||||||
|
"settings.header.visual.animatedArtworkQuality.medium": "Media",
|
||||||
|
"settings.header.visual.animatedArtworkQuality.high": "Alta",
|
||||||
|
"settings.header.visual.animatedArtworkQuality.veryHigh": "Muito Alta",
|
||||||
|
"settings.header.visual.animatedArtworkQuality.extreme": "Extrema",
|
||||||
|
"settings.option.visual.animatedWindowBackground": "Fundo de Janela Animado", // Toggle
|
||||||
|
"settings.option.visual.hardwareAcceleration": "Acelaração no Hardware", // Dropdown
|
||||||
|
"settings.option.visual.hardwareAcceleration.description": "Necessário reiniciar a aplicação",
|
||||||
|
"settings.header.visual.hardwareAcceleration.default": "Normal",
|
||||||
|
"settings.header.visual.hardwareAcceleration.webGPU": "WebGPU",
|
||||||
|
|
||||||
|
|
||||||
|
// Refer to term.disabled for the disabled option
|
||||||
|
"settings.option.visual.showPersonalInfo": "Mostrar Informaçoes Pessoais", // Toggle
|
||||||
|
// Settings - General (Reserved)
|
||||||
|
"settings.header.general": "Principal",
|
||||||
|
"settings.header.general.description": "Ajustar as definiçoes principais no Cider.",
|
||||||
|
|
||||||
|
// Settings - Lyrics
|
||||||
|
"settings.header.lyrics": "Letras",
|
||||||
|
"settings.header.lyrics.description": "Ajustar as definições das letras no Cider.",
|
||||||
|
"settings.option.lyrics.enableMusixmatch": "Ativar Letras do Musixmatch", // Toggle
|
||||||
|
"settings.option.lyrics.enableMusixmatchKaraoke": "Ativar Modo Karaoke (só para Musixmatch)", // Toggle
|
||||||
|
"settings.option.lyrics.musixmatchPreferredLanguage": "Idioma Preferido para Tradução Musixmatch", // Dropdown
|
||||||
|
"settings.option.lyrics.enableYoutubeLyrics": "Ativar letras do Youtube para videos musicais", // Toggle
|
||||||
|
|
||||||
|
// Settings - Connectivity
|
||||||
|
"settings.header.connectivity": "Conectividade",
|
||||||
|
"settings.header.connectivity.description": "Ajustar as definições de conectividade no Cider.",
|
||||||
|
"settings.option.connectivity.discordRPC": "Discord Rich Presence", // Dropdown
|
||||||
|
// Refer to term.disabled for the disabled option
|
||||||
|
"settings.header.connectivity.discordRPC.cider": "Mostrar como 'Cider'",
|
||||||
|
"settings.header.connectivity.discordRPC.appleMusic": "Mostrar como 'Apple Music'",
|
||||||
|
"settings.option.connectivity.discordRPC.clearOnPause": "Apagar Discord Rich Presence quando estiver pausado", // Toggle
|
||||||
|
"settings.option.connectivity.lastfmScrobble": "LastFM Scrobbling", // Option to Connect
|
||||||
|
"settings.option.connectivity.lastfmScrobble.delay": "Atraso dos Scrobbles do LastFM (%)",
|
||||||
|
"settings.option.connectivity.lastfmScrobble.nowPlaying": "Ativar LastFM Now Playing",
|
||||||
|
"settings.option.connectivity.lastfmScrobble.removeFeatured": "Remover artistas de colaboração do nome da música (LastFM)",
|
||||||
|
// Refer to term.connect for the connect button
|
||||||
|
|
||||||
|
// Settings - Experimental
|
||||||
|
"settings.header.experimental": "Experimental",
|
||||||
|
"settings.header.experimental.description": "Ajustar as definições experimental no Cider.",
|
||||||
|
"settings.option.experimental.compactUI": "UI Compacto", // Toggle
|
||||||
|
// Refer to term.disabled & term.enabled
|
||||||
|
|
||||||
|
// Spatialization Menu
|
||||||
|
"spatial.spatialProperties" : "Propriedades do Espacial",
|
||||||
|
"spatial.width" : "Largura",
|
||||||
|
"spatial.height" : "Altura",
|
||||||
|
"spatial.depth" : "Profundidade",
|
||||||
|
"spatial.roomMaterials" : "Materiais da Sala",
|
||||||
|
"spatial.roomDimensions" : "Dimensões da Sala",
|
||||||
|
"spatial.roomPositions" : "Posições da Sala",
|
||||||
|
"spatial.setDimensions" : "Definir Dimensões",
|
||||||
|
"spatial.setPositions" : "Definir Posições",
|
||||||
|
"spatial.up" : "Acima",
|
||||||
|
"spatial.front" : "Frente",
|
||||||
|
"spatial.left" : "Esquerda",
|
||||||
|
"spatial.right" : "Direita",
|
||||||
|
"spatial.back" : "Atrás",
|
||||||
|
"spatial.down" : "Abaixo",
|
||||||
|
"spatial.listener" : "Ouvinte",
|
||||||
|
"spatial.audioSource" : "Fonte de Audio",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Settings - Unfinished
|
||||||
|
"settings.header.unfinished": "Inacabado",
|
||||||
|
|
||||||
|
// Web Remote
|
||||||
|
"remote.web.title": "Cider Remoto",
|
||||||
|
"remote.web.description": "Digitalize o código QR para emparelhar seu telefone com esta instância Cider"
|
||||||
|
}
|
294
src/i18n/tr_TR.jsonc
Normal file
|
@ -0,0 +1,294 @@
|
||||||
|
{
|
||||||
|
// i18n Info
|
||||||
|
"i18n.languageName": "Türkçe", // name of language in native language
|
||||||
|
"i18n.languageNameEnglish": "Turkish", // name of language in English
|
||||||
|
"i18n.category": "main", // main = real language, fun = fun community languages
|
||||||
|
"i18n.authors": "@gms10ur", // Authors, if you contribute to this file feel free to add your name seperated with a space
|
||||||
|
|
||||||
|
// App info
|
||||||
|
"app.name": "Cider",
|
||||||
|
|
||||||
|
"date.format": "${d} ${m}, ${y}",
|
||||||
|
|
||||||
|
// Dialogs
|
||||||
|
"dialog.cancel": "İptal",
|
||||||
|
"dialog.ok": "Tamam",
|
||||||
|
|
||||||
|
// Notification
|
||||||
|
"notification.updatingLibrarySongs": "Arşiv'deki şarkılar alınıyor...",
|
||||||
|
"notification.updatingLibraryAlbums": "Arşiv'deki albümler alınıyor...",
|
||||||
|
"notification.updatingLibraryArtists": "Arşiv'deki sanatçılar alınıyor...",
|
||||||
|
|
||||||
|
// Terms
|
||||||
|
"term.appleInc": "Apple",
|
||||||
|
"term.appleMusic": "Apple Müzik",
|
||||||
|
"term.applePodcasts": "Apple Podcastler",
|
||||||
|
"term.itunes": "iTunes",
|
||||||
|
"term.github": "GitHub",
|
||||||
|
"term.discord": "Discord",
|
||||||
|
"term.learnMore": "Daha fazla bilgi edin",
|
||||||
|
"term.accountSettings": "Hesap Ayarları",
|
||||||
|
"term.logout": "Çıkış Yap",
|
||||||
|
"term.login": "Giriş yap",
|
||||||
|
"term.about": "Hakkında",
|
||||||
|
"term.privateSession": "Özel Oturum",
|
||||||
|
"term.queue": "Çalma Sırası",
|
||||||
|
"term.search": "Arama",
|
||||||
|
"term.library": "Arşiv",
|
||||||
|
"term.recentlyAdded": "Son Eklenenler",
|
||||||
|
"term.songs": "Şarkılar",
|
||||||
|
"term.albums": "Albümler",
|
||||||
|
"term.artists": "Sanatçılar",
|
||||||
|
"term.podcasts": "Podcastler",
|
||||||
|
"term.playlists": "Listeler",
|
||||||
|
"term.playlist": "Liste",
|
||||||
|
"term.play": "Oynat",
|
||||||
|
"term.pause": "Duraklat",
|
||||||
|
"term.previous": "Önceki",
|
||||||
|
"term.next": "Sonraki",
|
||||||
|
"term.shuffle": "Karıştır",
|
||||||
|
"term.repeat": "Tekrarla",
|
||||||
|
"term.volume": "Ses",
|
||||||
|
"term.mute": "Sustur",
|
||||||
|
"term.unmute": "Sesi Aç",
|
||||||
|
"term.share": "Paylaş",
|
||||||
|
"term.settings": "Ayarlar",
|
||||||
|
"term.seeAll": "Tümünü Gör",
|
||||||
|
"term.listenNow": "Şimdi Dinle",
|
||||||
|
"term.browse": "Göz At",
|
||||||
|
"term.radio": "Radyo",
|
||||||
|
"term.sortBy": "Sırala",
|
||||||
|
"term.sortBy.album": "Albüm",
|
||||||
|
"term.sortBy.artist": "Sanatçı",
|
||||||
|
"term.sortBy.name": "Şarkı İsmi",
|
||||||
|
"term.sortBy.genre": "Tür",
|
||||||
|
"term.sortBy.releaseDate": "Yayınlanma Tarihi",
|
||||||
|
"term.sortBy.duration": "Süre",
|
||||||
|
"term.sortOrder": "A-Z",
|
||||||
|
"term.sortOrder.ascending": "Çoğalan",
|
||||||
|
"term.sortOrder.descending": "Azalan",
|
||||||
|
"term.viewAs": "Şöyle Göster",
|
||||||
|
"term.viewAs.coverArt": "Albüm Kapağı",
|
||||||
|
"term.viewAs.list": "Liste",
|
||||||
|
"term.size": "Boyut",
|
||||||
|
"term.size.normal": "Normal",
|
||||||
|
"term.size.compact": "Daha Sıkı",
|
||||||
|
"term.enable": "Aç",
|
||||||
|
"term.disable": "Kapa",
|
||||||
|
"term.enabled": "Açık",
|
||||||
|
"term.disabled": "Kapalı",
|
||||||
|
"term.connect": "Bağlan",
|
||||||
|
"term.connecting": "Bağlanıyor",
|
||||||
|
"term.authed": "Bağlantı sağlandı",
|
||||||
|
"term.disconnect": "Bağlantıyı Kes",
|
||||||
|
"term.confirm": "Onayla?",
|
||||||
|
"term.more": "Daha Fazla",
|
||||||
|
"term.less": "Daha Az",
|
||||||
|
"term.showMore": "Daha Fazla Göster",
|
||||||
|
"term.showLess": "Daha Az Göster",
|
||||||
|
"term.topSongs" : "Popüler Parçalar",
|
||||||
|
"term.latestReleases": "Yeni Çıkan",
|
||||||
|
"term.time.added": "Şu tarihte eklendi: ",
|
||||||
|
"term.time.released": "Şu tarihte yayınalndı: ",
|
||||||
|
"term.time.updated": "Şu tarihte güncellendi: ",
|
||||||
|
"term.fullscreenView": "Tam Ekrana Geç",
|
||||||
|
"term.defaultView": "Normal Görünüme Dön",
|
||||||
|
"term.spacializedAudioSetting": "Uzamsal Ses Ayarları",
|
||||||
|
"term.clearAll": "Tümünü Temizle",
|
||||||
|
"term.recentStations": "Son İstasyonlar",
|
||||||
|
"term.language": "Dil",
|
||||||
|
"term.funLanguages": "Mizahi Diller",
|
||||||
|
"term.noLyrics": "Yükleniyor... / Şarkı Sözü Bulunamadı./ Enstrumantal.",
|
||||||
|
"term.copyright": "Copyright",
|
||||||
|
"term.rightsReserved": "Tüm Haklası Saklıdır.",
|
||||||
|
"term.sponsor": "Bu Projeye Destek Olun",
|
||||||
|
"term.ciderTeam": "Cider Ekibi",
|
||||||
|
"term.developer": "Geliştirici",
|
||||||
|
"term.socialTeam": "Sosyal Ekip",
|
||||||
|
"term.contributors": "Katkıda Bulunanlar",
|
||||||
|
"term.equalizer": "Ekolayzer",
|
||||||
|
"term.reset": "Sıfırla",
|
||||||
|
"term.tracks": "adet şarkı", // Assume x amount of tracks. e.g. 50 tracks
|
||||||
|
"term.time.hours": "saat",
|
||||||
|
"term.time.hour": "saat",
|
||||||
|
"term.time.minutes": "dakika",
|
||||||
|
"term.time.minute": "dakika",
|
||||||
|
"term.time.seconds": "saniye",
|
||||||
|
"term.time.second": "saniye",
|
||||||
|
|
||||||
|
// Home
|
||||||
|
"home.title": "Ana Sayfa",
|
||||||
|
"home.recentlyPlayed": "Son Oynatılanlar",
|
||||||
|
"home.recentlyAdded": "Son Eklenenler",
|
||||||
|
"home.artistsFeed": "Son Çıkanlar",
|
||||||
|
"home.artistsFeed.noArtist": "Birkaç sanatçı takip ettiğinizde, sanatçılarınızın son çıkan yayınları burada gözükür.",
|
||||||
|
"home.madeForYou": "Sadece Size Özel",
|
||||||
|
"home.friendsListeningTo": "Arkadaşlarınızın Dinledikleri",
|
||||||
|
"home.followedArtists": "Takip Edilen Sanatçılar",
|
||||||
|
|
||||||
|
// Errors
|
||||||
|
"error.appleMusicSubRequired": "Apple Müzik, aktif bir abonelik gerektirir.",
|
||||||
|
"error.connectionError": "Apple Müzik ile bağlantı kurulamadı.",
|
||||||
|
"error.noResults": "Hiç sonuç yok",
|
||||||
|
"error.noResults.description": "Tekrar deneyin.",
|
||||||
|
|
||||||
|
//Podcasts
|
||||||
|
"podcast.followOnCider": "Cider'de Takip Et",
|
||||||
|
"podcast.followedOnCider": "Cider'de Takip Ediliyor",
|
||||||
|
"podcast.subscribeOnItunes": "itunes'de Abone Ol",
|
||||||
|
"podcast.subscribedOnItunes": "iTunes'de Abone Olundu",
|
||||||
|
"podcast.itunesStore": "iTunes Mağazası",
|
||||||
|
"podcast.episodes": "Bölümler",
|
||||||
|
"podcast.playEpisode": "Bölümü Oynat",
|
||||||
|
"podcast.website": "Web Sayfası",
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
"action.addToLibrary": "Arşiv'e Ekle",
|
||||||
|
"action.addToLibrary.success": "Arşiv'e Eklendi",
|
||||||
|
"action.addToLibrary.error": "Arşiv'e Eklenemedi",
|
||||||
|
"action.removeFromLibrary": "Arşiv'den Sil",
|
||||||
|
"action.removeFromLibrary.success": "Arşiv'den Silindi",
|
||||||
|
"action.addToPlaylist": "Liste'ye Ekle",
|
||||||
|
"action.removeFromPlaylist": "Liste'den Sil",
|
||||||
|
"action.addToFavorites": "Favorilere Ekle",
|
||||||
|
"action.moveToTop": "En Başa Taşı",
|
||||||
|
"action.rename": "Yeniden Adlandır",
|
||||||
|
"action.addToQueue": "Sıraya Ekle",
|
||||||
|
"action.addToQueue.success": "Sıraya Eklendi",
|
||||||
|
"action.addToQueue.error": "Sıraya Eklenemedi",
|
||||||
|
"action.removeFromQueue": "Sıradan Kaldır",
|
||||||
|
"action.removeFromQueue.success": "Sıradan Kaldırıldı",
|
||||||
|
"action.removeFromQueue.error": "Sıradan Kaldırılamadı",
|
||||||
|
"action.follow": "Takip Et",
|
||||||
|
"action.follow.success": "Takip Ediliyor",
|
||||||
|
"action.follow.error": "Takip Edilemedi",
|
||||||
|
"action.unfollow": "Takibi Bırak",
|
||||||
|
"action.unfollow.success": "Takipten Çıkıldı",
|
||||||
|
"action.unfollow.error": "Takipten Çıkılamadı",
|
||||||
|
"action.playNext": "Sıradaki Yap",
|
||||||
|
"action.playLater": "En Son Çal",
|
||||||
|
"action.startRadio": "İstasyon Yarat",
|
||||||
|
"action.goToArtist": "Sanatçıya Git",
|
||||||
|
"action.goToAlbum": "Albüme Git",
|
||||||
|
"action.share": "Paylaş",
|
||||||
|
"action.love": "Beğen",
|
||||||
|
"action.unlove": "Beğeniyi Kaldır",
|
||||||
|
"action.dislike": "Bunun Gibileri Daha Az Öner",
|
||||||
|
"action.undoDislike": "Bunun Gibileri Daha Az Önermeyi Geri Al",
|
||||||
|
"action.showWebRemoteQR": "Uzaktan Kumanda Bağla",
|
||||||
|
"action.playTracksNext": "Sonrasında ${app.selectedMediaItems.length} şarkıları çal",
|
||||||
|
"action.playTracksLater": "En Son ${app.selectedMediaItems.length} şarkıları çal",
|
||||||
|
"action.removeTracks": "Sıradan ${self.selectedItems.length} şarkıları kaldır",
|
||||||
|
|
||||||
|
// Settings - General (Reserved)
|
||||||
|
"settings.header.general": "Genel Ayarlar",
|
||||||
|
"settings.header.general.description": "Genel ayarları buradan düzenleyin.",
|
||||||
|
"settings.option.general.language": "Dil Seçeneği",
|
||||||
|
|
||||||
|
// Language optgroups
|
||||||
|
"settings.option.general.language.main": "Gerçek Diller",
|
||||||
|
"settings.option.general.language.fun": "Mizahi Diller",
|
||||||
|
"settings.option.general.language.unsorted": "Henüz Tamamlanmamış Diller",
|
||||||
|
|
||||||
|
// Settings - Audio
|
||||||
|
"settings.header.audio": "Çalma / Oynatma",
|
||||||
|
"settings.header.audio.description": "Cider'in sizin için en iyi dinleme deneyimini sağlayabilmesi için ses ayarlarınızı yapın.",
|
||||||
|
"settings.option.audio.quality": "Ses Kalitesi", // Dropdown
|
||||||
|
"settings.header.audio.quality.high": "Yüksek Kalite",
|
||||||
|
"settings.header.audio.quality.low": "Yüksek Verimlilik",
|
||||||
|
"settings.header.audio.quality.auto": "Otomatik",
|
||||||
|
"settings.option.audio.seamlessTransition": "Kesintisiz Ses Geçişi", // Toggle
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality": "Gelişmiş Ses Deneyimi", // Toggle
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.description": "Gelişmiş ses deneyiminin etkinleştirilmesi, Ses Normalleştirme, Ekolayzer ve Görselleştirici gibi genişletilmiş ses özelliklerine izin verir, ancak bu durum bazı sistemlerde seste bozulmalara neden olabilir.",
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.audioNormalization": "Ses Normalleştirme", // Toggle
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.audioNormalization.description": "Ses normalleştirme alçak ve yüksek sesli şarkıları dengeler ve daha düzgün bir dinleme deneyimi sağlar.",
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization": "Uzamsal Ses", // Toggle
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization.description": "Sesi uzamsallaştırın ve sesi daha 3 boyutlu hale getirin (not: Bu Dolby Atmos değildir)",
|
||||||
|
|
||||||
|
// Settings - Visual
|
||||||
|
"settings.header.visual": "Görünüm",
|
||||||
|
"settings.header.visual.description": "Cider'in nasıl gözükmesini istediğinizi ayarlayın",
|
||||||
|
"settings.option.visual.windowBackgroundStyle": "Uygulama Arka Plan Stili", // Toggle
|
||||||
|
"settings.header.visual.windowBackgroundStyle.none": "Karanlık",
|
||||||
|
"settings.header.visual.windowBackgroundStyle.artwork": "Albüm Kapağı",
|
||||||
|
"settings.option.visual.animatedArtwork": "Hareketli Albüm Kapakları", // Dropdown
|
||||||
|
"settings.header.visual.animatedArtwork.always": "Her Zaman Açık",
|
||||||
|
"settings.header.visual.animatedArtwork.limited": "Bazı Sayfalara ve Özel Bölgelerle Sınırlı",
|
||||||
|
"settings.header.visual.animatedArtwork.disable": "Her Zaman Kapalı",
|
||||||
|
"settings.option.visual.animatedArtworkQuality": "Hareketli Albüm Kapağı Kalitesi", // Dropdown
|
||||||
|
"settings.header.visual.animatedArtworkQuality.low": "Düşük",
|
||||||
|
"settings.header.visual.animatedArtworkQuality.medium": "Orta",
|
||||||
|
"settings.header.visual.animatedArtworkQuality.high": "Yüksek",
|
||||||
|
"settings.header.visual.animatedArtworkQuality.veryHigh": "Daha Yüksek",
|
||||||
|
"settings.header.visual.animatedArtworkQuality.extreme": "Ekstrem",
|
||||||
|
"settings.option.visual.animatedWindowBackground": "Hareketli Uygulama Arka Planı", // Toggle
|
||||||
|
"settings.option.visual.hardwareAcceleration": "Donanım Hızlandırması", // Dropdown
|
||||||
|
"settings.option.visual.hardwareAcceleration.description": "Etki etmesi için uygulamayı yeniden başlatmak gerekir.",
|
||||||
|
"settings.header.visual.hardwareAcceleration.default": "Varsayılan",
|
||||||
|
"settings.header.visual.hardwareAcceleration.webGPU": "Gelişmiş",
|
||||||
|
// Refer to term.disabled for the disabled option
|
||||||
|
"settings.option.visual.showPersonalInfo": "Kullanıcı Adımı Göster", // Toggle
|
||||||
|
|
||||||
|
// Settings - Lyrics
|
||||||
|
"settings.header.lyrics": "Şarkı Sözleri",
|
||||||
|
"settings.header.lyrics.description": "Cider'in şarkı sözlerini nasıl görüntülemesini istediğini buradan ayarlayın.",
|
||||||
|
"settings.option.lyrics.enableMusixmatch": "Musixmatch Kullan", // Toggle
|
||||||
|
"settings.option.lyrics.enableMusixmatchKaraoke": "Karaoke Modunu Etkinleştir (Sadece Musixmatch)", // Toggle
|
||||||
|
"settings.option.lyrics.musixmatchPreferredLanguage": "Musixmatch için Otomatik Çeviri Dili", // Dropdown
|
||||||
|
"settings.option.lyrics.enableYoutubeLyrics": "Müzik Videoları için Şarkı Sözünü YouTube'dan Al", // Toggle
|
||||||
|
|
||||||
|
// Settings - Connectivity
|
||||||
|
"settings.header.connectivity": "Diğer Servisler",
|
||||||
|
"settings.header.connectivity.description": "Cider'i diğer servislere bağlayarak deneyiminizi zenginleştirin.",
|
||||||
|
"settings.option.connectivity.discordRPC": "Ne Dinlediğimi Discord'da Göster", // Dropdown
|
||||||
|
// Refer to term.disabled for the disabled option
|
||||||
|
"settings.header.connectivity.discordRPC.cider": "'Cider' Olarak",
|
||||||
|
"settings.header.connectivity.discordRPC.appleMusic": "'Apple Music' Olarak",
|
||||||
|
"settings.option.connectivity.discordRPC.clearOnPause": "Duraklatıldığında Discord'da Gösterme", // Toggle
|
||||||
|
"settings.option.connectivity.lastfmScrobble": "LastFM Bağlantısı", // Option to Connect
|
||||||
|
"settings.option.connectivity.lastfmScrobble.delay": "LastFM Scrobblalma Yüzdesi (%)",
|
||||||
|
"settings.option.connectivity.lastfmScrobble.nowPlaying": "Şimdi çalan şarkıyı LastFM'de göster",
|
||||||
|
"settings.option.connectivity.lastfmScrobble.removeFeatured": "Albüm sanatçısını Scrobbledan kaldır(LastFM)",
|
||||||
|
"settings.option.connectivity.lastfmScrobble.filterLoop": "Tekrar edilen şarkıyı filtrele (LastFM)",
|
||||||
|
// Refer to term.connect for the connect button
|
||||||
|
|
||||||
|
// Settings - Experimental
|
||||||
|
"settings.header.experimental": "Deneysel",
|
||||||
|
"settings.header.experimental.description": "Cider'deki deneysel özelliklere erişim sağlayın. (Not: Bazı özellikler düzgün çalışmayabilir.)",
|
||||||
|
"settings.option.experimental.compactUI": "Kompakt Arayüz", // Toggle
|
||||||
|
"settings.option.experimental.closeButtonBehaviour": "Kapat düğmesi davranışı",
|
||||||
|
"settings.option.experimental.closeButtonBehaviour.quit": "Cider'den çık",
|
||||||
|
"settings.option.experimental.closeButtonBehaviour.minimizeTaskbar": "Simge durumuna küçült",
|
||||||
|
"settings.option.experimental.closeButtonBehaviour.minimizeTray": "Görev çubuğuna küçült",
|
||||||
|
// Refer to term.disabled & term.enabled
|
||||||
|
|
||||||
|
// Spatialization Menu
|
||||||
|
"spatial.spatialProperties" : "Uzamsal Özellikler",
|
||||||
|
"spatial.width" : "Genişlik",
|
||||||
|
"spatial.height" : "Yükseklik",
|
||||||
|
"spatial.depth" : "Derinlik",
|
||||||
|
"spatial.roomMaterials" : "Oda Materyalleri",
|
||||||
|
"spatial.roomDimensions" : "Oda Ölçüleri",
|
||||||
|
"spatial.roomPositions" : "Oda Pozisyonu",
|
||||||
|
"spatial.setDimensions" : "Ölçüleri Ayarla",
|
||||||
|
"spatial.setPositions" : "Pozisyonu Ayarla",
|
||||||
|
"spatial.up" : "Üst",
|
||||||
|
"spatial.front" : "Ön",
|
||||||
|
"spatial.left" : "Sol",
|
||||||
|
"spatial.right" : "Sağ",
|
||||||
|
"spatial.back" : "Arka",
|
||||||
|
"spatial.down" : "Aşağı",
|
||||||
|
"spatial.listener" : "Dinleyici",
|
||||||
|
"spatial.audioSource" : "Ses Kaynağı",
|
||||||
|
|
||||||
|
// Settings - Unfinished
|
||||||
|
"settings.header.unfinished": "Geliştirme Aşamasında",
|
||||||
|
|
||||||
|
// Web Remote
|
||||||
|
"remote.web.title": "Cider'e Bağlan",
|
||||||
|
"remote.web.description": "Telefonunuzu Bu Cider Oturumuyla Eşleştirmek için QR Kodunu Tarayın",
|
||||||
|
|
||||||
|
//About
|
||||||
|
"about.thanks": "Cider Collective Ekibine ve tüm katkıda bulunanlara çok teşekkür ederiz."
|
||||||
|
|
||||||
|
}
|
284
src/i18n/zh_CN.jsonc
Normal file
|
@ -0,0 +1,284 @@
|
||||||
|
{
|
||||||
|
// App info
|
||||||
|
"app.name": "Cider",
|
||||||
|
|
||||||
|
"date.format": "${y}年${m}月${d}日",
|
||||||
|
|
||||||
|
// i18n Info
|
||||||
|
"i18n.languageName": "简体中文(中国)", // name of language in native language
|
||||||
|
"i18n.languageNameEnglish": "Simp. Chinese (China)", // name of language in English
|
||||||
|
"i18n.category": "main", // main = real language, fun = fun community languages
|
||||||
|
"i18n.authors": "@maikirakiwi", // Authors, if you contribute to this file feel free to add your name seperated with a space
|
||||||
|
|
||||||
|
// Dialogs
|
||||||
|
"dialog.cancel": "取消",
|
||||||
|
"dialog.ok": "确定",
|
||||||
|
|
||||||
|
// Notification
|
||||||
|
"notification.updatingLibrarySongs": "正在更新资料库的歌曲信息...",
|
||||||
|
"notification.updatingLibraryAlbums": "正在更新资料库的专辑信息...",
|
||||||
|
"notification.updatingLibraryArtists": "正在更新资料库的艺人信息...",
|
||||||
|
|
||||||
|
// Terms
|
||||||
|
"term.appleMusic": "Apple Music", // Follows brand term
|
||||||
|
"term.applePodcasts": "Apple Podcasts", // Follows brand term
|
||||||
|
"term.itunes": "iTunes", // Follows brand term
|
||||||
|
"term.github": "GitHub", // Follows brand term
|
||||||
|
"term.discord": "Discord", // Follows brand term
|
||||||
|
"term.learnMore": "更多信息",
|
||||||
|
"term.accountSettings": "账户设置",
|
||||||
|
"term.logout": "登出",
|
||||||
|
"term.login": "登录",
|
||||||
|
"term.about": "关于",
|
||||||
|
"term.privateSession": "私人聆听",
|
||||||
|
"term.queue": "队列",
|
||||||
|
"term.search": "搜索",
|
||||||
|
"term.library": "资料库",
|
||||||
|
"term.listenNow": "现在就听",
|
||||||
|
"term.browse": "浏览",
|
||||||
|
"term.radio": "广播",
|
||||||
|
"term.recentlyAdded": "最近添加",
|
||||||
|
"term.songs": "歌曲",
|
||||||
|
"term.albums": "专辑",
|
||||||
|
"term.artists": "艺人",
|
||||||
|
"term.podcasts": "播客",
|
||||||
|
"term.playlists": "播放列表",
|
||||||
|
"term.playlist": "播放列表",
|
||||||
|
"term.play": "播放",
|
||||||
|
"term.pause": "暂停",
|
||||||
|
"term.previous": "上一首",
|
||||||
|
"term.next": "下一首",
|
||||||
|
"term.shuffle": "随机播放",
|
||||||
|
"term.repeat": "重复播放",
|
||||||
|
"term.volume": "音量",
|
||||||
|
"term.mute": "静音",
|
||||||
|
"term.unmute": "解除静音",
|
||||||
|
"term.share": "分享",
|
||||||
|
"term.settings": "设置",
|
||||||
|
"term.seeAll": "查看全部",
|
||||||
|
"term.sortBy": "排序",
|
||||||
|
"term.sortBy.album": "专辑",
|
||||||
|
"term.sortBy.artist": "艺人",
|
||||||
|
"term.sortBy.name": "歌名",
|
||||||
|
"term.sortBy.genre": "类型",
|
||||||
|
"term.sortBy.releaseDate": "发行日期",
|
||||||
|
"term.sortBy.duration": "时长",
|
||||||
|
"term.sortOrder": "字母排序",
|
||||||
|
"term.sortOrder.ascending": "升序",
|
||||||
|
"term.sortOrder.descending": "倒序",
|
||||||
|
"term.viewAs": "显示模式",
|
||||||
|
"term.viewAs.coverArt": "专辑封面",
|
||||||
|
"term.viewAs.list": "列表",
|
||||||
|
"term.size": "大小",
|
||||||
|
"term.size.normal": "正常",
|
||||||
|
"term.size.compact": "紧凑",
|
||||||
|
"term.enable": "启用",
|
||||||
|
"term.disable": "禁用",
|
||||||
|
"term.enabled": "已启用",
|
||||||
|
"term.disabled": "已禁用",
|
||||||
|
"term.connect": "连接",
|
||||||
|
"term.connecting": "连接中",
|
||||||
|
"term.disconnect": "断开",
|
||||||
|
"term.authed": "已认证",
|
||||||
|
"term.confirm": "确认?",
|
||||||
|
"term.more": "更多",
|
||||||
|
"term.less": "较少",
|
||||||
|
"term.showMore": "显示更多",
|
||||||
|
"term.showLess": "显示更少",
|
||||||
|
"term.topSongs" : "热门歌曲",
|
||||||
|
"term.latestReleases": "最新发行",
|
||||||
|
"term.time.added": "添加于",
|
||||||
|
"term.time.released": "发行于",
|
||||||
|
"term.time.updated": "更新于",
|
||||||
|
"term.time.hours": "小时",
|
||||||
|
"term.time.hour": "小时",
|
||||||
|
"term.time.minutes": "分钟",
|
||||||
|
"term.time.minute": "分钟",
|
||||||
|
"term.time.seconds": "秒",
|
||||||
|
"term.time.second": "秒",
|
||||||
|
"term.fullscreenView": "全屏",
|
||||||
|
"term.defaultView": "默认",
|
||||||
|
"term.spacializedAudioSetting": "音频空间化设置",
|
||||||
|
"term.clearAll": "清空",
|
||||||
|
"term.recentStations": "最近播放的频道",
|
||||||
|
"term.language": "语言",
|
||||||
|
"term.noLyrics": "加载中。。/ 搜索无结果 / 纯音乐",
|
||||||
|
"term.copyright": "版权所有",
|
||||||
|
"term.rightsReserved": "保留所有权利。",
|
||||||
|
"term.sponsor": "赞助",
|
||||||
|
"term.ciderTeam": "Cider 团队",
|
||||||
|
"term.developer": "开发者",
|
||||||
|
"term.socialTeam": "媒体团队",
|
||||||
|
"term.contributors": "贡献者",
|
||||||
|
"term.equalizer": "均衡器",
|
||||||
|
"term.reset": "重置",
|
||||||
|
"term.tracks": "首歌曲", // Assume x amount of tracks. e.g. 50 tracks
|
||||||
|
|
||||||
|
// Home
|
||||||
|
"home.title": "主页",
|
||||||
|
"home.recentlyPlayed": "最近播放",
|
||||||
|
"home.recentlyAdded": "最近添加",
|
||||||
|
"home.artistsFeed": "艺人推荐",
|
||||||
|
"home.artistsFeed.noArtist": "追踪您喜爱的艺人后便可查看他们的最新发行。",
|
||||||
|
"home.madeForYou": "专属推荐",
|
||||||
|
"home.friendsListeningTo": "朋友正在听",
|
||||||
|
"home.followedArtists": "关注的艺人",
|
||||||
|
|
||||||
|
// Errors
|
||||||
|
"error.appleMusicSubRequired": "需要订阅 Apple Music 以使用 Cider",
|
||||||
|
"error.connectionError": "无法连接到 Apple Music。",
|
||||||
|
"error.noResults": "没有结果",
|
||||||
|
"error.noResults.description": "尝试更改搜索条件。",
|
||||||
|
|
||||||
|
//Podcasts
|
||||||
|
"podcast.followOnCider": "在 Cider 中追踪",
|
||||||
|
"podcast.followedOnCider": "已追踪",
|
||||||
|
"podcast.subscribeOnItunes": "在 iTunes 上订阅",
|
||||||
|
"podcast.subscribedOnItunes": "已订阅",
|
||||||
|
"podcast.itunesStore": "iTunes Store", // Follow brand term
|
||||||
|
"podcast.episodes": "单集",
|
||||||
|
"podcast.playEpisode": "播放单集",
|
||||||
|
"podcast.website": "Podcast 网站",
|
||||||
|
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
"action.addToLibrary": "加入资料库",
|
||||||
|
"action.addToLibrary.success": "成功加入资料库",
|
||||||
|
"action.addToLibrary.error": "加入资料库的过程发生了错误",
|
||||||
|
"action.removeFromLibrary": "从资料库中移除",
|
||||||
|
"action.removeFromLibrary.success": "已从资料库中移除",
|
||||||
|
"action.addToQueue": "加入队列",
|
||||||
|
"action.addToQueue.success": "成功加入队列",
|
||||||
|
"action.addToQueue.error": "加入队列的过程发生了错误",
|
||||||
|
"action.removeFromQueue": "从队列中移除",
|
||||||
|
"action.removeFromQueue.success": "已从队列中移除",
|
||||||
|
"action.removeFromQueue.error": "从队列中移除的过程发生了错误",
|
||||||
|
"action.addToPlaylist": "加入播放列表",
|
||||||
|
"action.removeFromPlaylist": "从播放列表中移除",
|
||||||
|
"action.addToFavorites": "加至收藏",
|
||||||
|
"action.follow": "关注",
|
||||||
|
"action.follow.success": "已关注",
|
||||||
|
"action.follow.error": "尝试关注的过程发生了错误",
|
||||||
|
"action.unfollow": "取消关注",
|
||||||
|
"action.unfollow.success": "已取消关注",
|
||||||
|
"action.unfollow.error": "尝试取消关注的过程发生了错误",
|
||||||
|
"action.playNext": "下一首播放",
|
||||||
|
"action.playLater": "最后播放",
|
||||||
|
"action.startRadio": "开始电台",
|
||||||
|
"action.goToArtist": "前往艺人",
|
||||||
|
"action.goToAlbum": "前往专辑",
|
||||||
|
"action.moveToTop": "移到顶部",
|
||||||
|
"action.share": "分享歌曲",
|
||||||
|
"action.rename": "重命名",
|
||||||
|
"action.love": "喜欢",
|
||||||
|
"action.unlove": "踩",
|
||||||
|
"action.dislike": "减少此类建议",
|
||||||
|
"action.undoDislike": "增加此类建议",
|
||||||
|
"action.showWebRemoteQR": "显示远程控制的二维码",
|
||||||
|
"action.playTracksNext": "插播 ${app.selectedMediaItems.length} 首歌曲",
|
||||||
|
"action.playTracksLater": "最后播放 ${app.selectedMediaItems.length} 首歌曲",
|
||||||
|
"action.removeTracks": "从队列中移除 ${self.selectedItems.length} 首歌曲",
|
||||||
|
|
||||||
|
// Settings - Audio
|
||||||
|
"settings.header.audio": "音频",
|
||||||
|
"settings.header.audio.description": "调整 Cider 的音频设置",
|
||||||
|
"settings.option.audio.quality": "音质", // Dropdown
|
||||||
|
"settings.header.audio.quality.high": "高音质",
|
||||||
|
"settings.header.audio.quality.low": "高效率",
|
||||||
|
"settings.header.audio.quality.auto": "自动",
|
||||||
|
"settings.option.audio.seamlessTransition": "无缝播放", // Toggle
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality": "进阶功能", // Toggle
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.description": "启用 AudioContext 将解锁例如音量标准化和音频空间化的功能,但可能会在小部分设备上出现音频上的卡顿。",
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.audioNormalization": "音量标准化", // Toggle
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.audioNormalization.description": "使所感知到的音频响度统一",
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization": "音频空间化", // Toggle
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization.description": "使所感知到的音频更有立体感 (注: 这不是杜比全景声)",
|
||||||
|
// Settings - Visual
|
||||||
|
"settings.header.visual": "外观",
|
||||||
|
"settings.header.visual.description": "调整 Cider 的外观",
|
||||||
|
"settings.option.visual.windowBackgroundStyle": "窗口背景样式", // Toggle
|
||||||
|
"settings.header.visual.windowBackgroundStyle.none": "无",
|
||||||
|
"settings.header.visual.windowBackgroundStyle.artwork": "专辑封面",
|
||||||
|
"settings.option.visual.animatedArtwork": "动态专辑封面", // Dropdown
|
||||||
|
"settings.header.visual.animatedArtwork.always": "总是显示",
|
||||||
|
"settings.header.visual.animatedArtwork.limited": "只在艺人页面和专辑封面显示",
|
||||||
|
"settings.header.visual.animatedArtwork.disable": "关闭",
|
||||||
|
"settings.option.visual.animatedArtworkQuality": "动态专辑封面画质", // Dropdown
|
||||||
|
"settings.header.visual.animatedArtworkQuality.low": "低",
|
||||||
|
"settings.header.visual.animatedArtworkQuality.medium": "中",
|
||||||
|
"settings.header.visual.animatedArtworkQuality.high": "高",
|
||||||
|
"settings.header.visual.animatedArtworkQuality.veryHigh": "非常高",
|
||||||
|
"settings.header.visual.animatedArtworkQuality.extreme": "极高",
|
||||||
|
"settings.option.visual.animatedWindowBackground": "动态窗口背景", // Toggle
|
||||||
|
"settings.option.visual.hardwareAcceleration": "硬件加速", // Dropdown
|
||||||
|
"settings.option.visual.hardwareAcceleration.description": "需要重启 Cider 才会生效",
|
||||||
|
"settings.header.visual.hardwareAcceleration.default": "默认",
|
||||||
|
"settings.header.visual.hardwareAcceleration.webGPU": "WebGPU",
|
||||||
|
// Refer to term.disabled for the disabled option
|
||||||
|
"settings.option.visual.showPersonalInfo": "显示个人资料", // Toggle
|
||||||
|
// Settings - General (Reserved)
|
||||||
|
"settings.header.general": "通用",
|
||||||
|
"settings.header.general.description": "调整 Cider 的通用设置",
|
||||||
|
|
||||||
|
// Settings - Lyrics
|
||||||
|
"settings.header.lyrics": "歌词",
|
||||||
|
"settings.header.lyrics.description": "调整 Cider 的歌词设置",
|
||||||
|
"settings.option.lyrics.enableMusixmatch": "启用 Musixmatch 歌词", // Toggle
|
||||||
|
"settings.option.lyrics.enableMusixmatchKaraoke": "启用卡拉 OK 模式(仅 Musixmatch)", // Toggle
|
||||||
|
"settings.option.lyrics.musixmatchPreferredLanguage": "Musixmatch 歌词语言偏好", // Dropdown
|
||||||
|
"settings.option.lyrics.enableYoutubeLyrics": "播放 MV 时使用 YouTube 歌词", // Toggle
|
||||||
|
|
||||||
|
// Settings - Connectivity
|
||||||
|
"settings.header.connectivity": "外部连接",
|
||||||
|
"settings.header.connectivity.description": "调整Cider与外部应用的交互设置",
|
||||||
|
"settings.option.connectivity.discordRPC": "Discord 动态", // Dropdown
|
||||||
|
// Refer to term.disabled for the disabled option
|
||||||
|
"settings.header.connectivity.discordRPC.cider": "显示正在玩 'Cider'",
|
||||||
|
"settings.header.connectivity.discordRPC.appleMusic": "显示正在玩 'Apple Music'",
|
||||||
|
"settings.option.connectivity.discordRPC.clearOnPause": "暂停时清除Discord 动态", // Toggle
|
||||||
|
"settings.option.connectivity.lastfmScrobble": "LastFM Scrobbling 记录", // Option to Connect
|
||||||
|
"settings.option.connectivity.lastfmScrobble.delay": "LastFM Scrobble 延迟 (%)",
|
||||||
|
"settings.option.connectivity.lastfmScrobble.nowPlaying": "启用 LastFM 正在播放",
|
||||||
|
"settings.option.connectivity.lastfmScrobble.removeFeatured": "从歌名里去除艺人推荐 (LastFM)",
|
||||||
|
// Refer to term.connect for the connect button
|
||||||
|
|
||||||
|
// Settings - Experimental
|
||||||
|
"settings.header.experimental": "实验性功能",
|
||||||
|
"settings.header.experimental.description": "调整Cider的实验性功能",
|
||||||
|
"settings.option.experimental.compactUI": "紧凑型 UI", // Toggle
|
||||||
|
"settings.option.experimental.closeButtonBehaviour": "点击关闭按钮时",
|
||||||
|
"settings.option.experimental.closeButtonBehaviour.quit": "退出 Cider",
|
||||||
|
"settings.option.experimental.closeButtonBehaviour.minimizeTaskbar": "最小化到任务栏",
|
||||||
|
"settings.option.experimental.closeButtonBehaviour.minimizeTray": "最小化到系统托盘",
|
||||||
|
// Refer to term.disabled & term.enabled
|
||||||
|
|
||||||
|
// Spatialization Menu
|
||||||
|
"spatial.spatialProperties" : "空间属性",
|
||||||
|
"spatial.width" : "宽度",
|
||||||
|
"spatial.height" : "高度",
|
||||||
|
"spatial.depth" : "深度",
|
||||||
|
"spatial.gain": "增益",
|
||||||
|
"spatial.roomMaterials" : "空间材质",
|
||||||
|
"spatial.roomDimensions" : "空间尺寸",
|
||||||
|
"spatial.roomPositions" : "空间位置",
|
||||||
|
"spatial.setDimensions" : "设置尺寸",
|
||||||
|
"spatial.setPositions" : "设置位置",
|
||||||
|
"spatial.up" : "上",
|
||||||
|
"spatial.front" : "前",
|
||||||
|
"spatial.left" : "左",
|
||||||
|
"spatial.right" : "右",
|
||||||
|
"spatial.back" : "后",
|
||||||
|
"spatial.down" : "下",
|
||||||
|
"spatial.listener" : "您",
|
||||||
|
"spatial.audioSource" : "音源",
|
||||||
|
|
||||||
|
// Settings - Unfinished
|
||||||
|
"settings.header.unfinished": "未完成",
|
||||||
|
|
||||||
|
// Web Remote
|
||||||
|
"remote.web.title": "Cider 远程控制",
|
||||||
|
"remote.web.description": "扫描以下的二维码以控制 Cider",
|
||||||
|
|
||||||
|
//About
|
||||||
|
"about.thanks": "郑重感谢 Cider Collective 以及为这个项目提供支持的贡献者。"
|
||||||
|
}
|
274
src/i18n/zh_HK.jsonc
Normal file
|
@ -0,0 +1,274 @@
|
||||||
|
{
|
||||||
|
// i18n Info
|
||||||
|
"i18n.languageName": "繁體中文(香港)", // name of language in native language
|
||||||
|
"i18n.languageNameEnglish": "Trad. Chinese (Hong Kong)", // name of language in English
|
||||||
|
"i18n.category": "main", // main = real language, fun = fun community languages
|
||||||
|
"i18n.authors": "@kyw504100 @maikirakiwi", // Authors, if you contribute to this file feel free to add your name seperated with a space
|
||||||
|
|
||||||
|
// App info
|
||||||
|
"app.name": "Cider",
|
||||||
|
|
||||||
|
"date.format": "${y}年${m}月${d}日",
|
||||||
|
|
||||||
|
// Dialogs
|
||||||
|
"dialog.cancel": "取消",
|
||||||
|
"dialog.ok": "確認",
|
||||||
|
|
||||||
|
// Notification
|
||||||
|
"notification.updatingLibrarySongs": "正在更新資料庫的歌曲...",
|
||||||
|
"notification.updatingLibraryAlbums": "正在更新資料庫的專輯...",
|
||||||
|
"notification.updatingLibraryArtists": "正在更新資料庫的藝人...",
|
||||||
|
// Terms
|
||||||
|
"term.appleInc": "Apple Inc.",
|
||||||
|
"term.appleMusic": "Apple Music", // Follows brand term
|
||||||
|
"term.applePodcasts": "Apple Podcasts", // Follows brand term
|
||||||
|
"term.itunes": "iTunes", // Follows brand term
|
||||||
|
"term.github": "GitHub", // Follows brand term
|
||||||
|
"term.discord": "Discord", // Follows brand term
|
||||||
|
"term.learnMore": "了解更多",
|
||||||
|
"term.accountSettings": "帳號設定",
|
||||||
|
"term.logout": "登出",
|
||||||
|
"term.login": "登入",
|
||||||
|
"term.about": "關於",
|
||||||
|
"term.privateSession": "私人模式",
|
||||||
|
"term.queue": "待播清單",
|
||||||
|
"term.search": "搜尋",
|
||||||
|
"term.library": "資料庫",
|
||||||
|
"term.listenNow": "立即聆聽",
|
||||||
|
"term.browse": "瀏覽",
|
||||||
|
"term.radio": "廣播",
|
||||||
|
"term.recentlyAdded": "最近加入",
|
||||||
|
"term.songs": "歌曲",
|
||||||
|
"term.albums": "專輯",
|
||||||
|
"term.artists": "藝人",
|
||||||
|
"term.podcasts": "Podcasts",
|
||||||
|
"term.playlists": "播放列表",
|
||||||
|
"term.playlist": "播放列表",
|
||||||
|
"term.play": "播放",
|
||||||
|
"term.pause": "暫停",
|
||||||
|
"term.previous": "上一首",
|
||||||
|
"term.next": "下一首",
|
||||||
|
"term.shuffle": "隨機播放",
|
||||||
|
"term.repeat": "重複播放",
|
||||||
|
"term.volume": "音量",
|
||||||
|
"term.mute": "靜音",
|
||||||
|
"term.unmute": "取消靜音",
|
||||||
|
"term.share": "分享",
|
||||||
|
"term.settings": "設定",
|
||||||
|
"term.seeAll": "顯示全部",
|
||||||
|
"term.sortBy": "排序",
|
||||||
|
"term.sortBy.album": "專輯",
|
||||||
|
"term.sortBy.artist": "藝人",
|
||||||
|
"term.sortBy.name": "歌名",
|
||||||
|
"term.sortBy.genre": "音樂風格",
|
||||||
|
"term.sortBy.releaseDate": "發行日期",
|
||||||
|
"term.sortBy.duration": "時長",
|
||||||
|
"term.sortOrder": "字母排序",
|
||||||
|
"term.sortOrder.ascending": "順序",
|
||||||
|
"term.sortOrder.descending": "倒序",
|
||||||
|
"term.viewAs": "顯示模式",
|
||||||
|
"term.viewAs.coverArt": "專輯封面",
|
||||||
|
"term.viewAs.list": "列表",
|
||||||
|
"term.size": "大小",
|
||||||
|
"term.size.normal": "正常",
|
||||||
|
"term.size.compact": "緊凑",
|
||||||
|
"term.enable": "啟用",
|
||||||
|
"term.disable": "停用",
|
||||||
|
"term.enabled": "已啟用",
|
||||||
|
"term.disabled": "已停用",
|
||||||
|
"term.connect": "連結",
|
||||||
|
"term.connecting": "連結中",
|
||||||
|
"term.disconnect": "取消連結",
|
||||||
|
"term.authed": "已授權",
|
||||||
|
"term.confirm": "確認?",
|
||||||
|
"term.more": "更多",
|
||||||
|
"term.less": "較少",
|
||||||
|
"term.showMore": "顯示更多",
|
||||||
|
"term.showLess": "顯示較少",
|
||||||
|
"term.topSongs" : "熱門歌曲",
|
||||||
|
"term.latestReleases": "最新發行",
|
||||||
|
"term.time.added": "加入於",
|
||||||
|
"term.time.released": "發行於",
|
||||||
|
"term.time.updated": "更新於",
|
||||||
|
"term.fullscreenView": "全螢幕檢視",
|
||||||
|
"term.defaultView": "一般檢視",
|
||||||
|
"term.spacializedAudioSetting": "空間音訊設定",
|
||||||
|
"term.clearAll": "清空",
|
||||||
|
"term.recentStations": "最近播放的頻道",
|
||||||
|
"term.language": "語言",
|
||||||
|
"term.noLyrics": "加載中... / 找不到歌詞。/ 純音樂。",
|
||||||
|
"term.copyright": "Copyright",
|
||||||
|
"term.rightsReserved": "保留一切權利。",
|
||||||
|
"term.sponsor": "贊助這個項目",
|
||||||
|
"term.ciderTeam": "Cider 團隊",
|
||||||
|
"term.developer": "開發者",
|
||||||
|
"term.socialTeam": "社交團隊",
|
||||||
|
"term.contributors": "貢獻者",
|
||||||
|
"term.equalizer": "等化器",
|
||||||
|
"term.reset": "重設",
|
||||||
|
"term.tracks": "首歌曲", // Assume x amount of tracks. e.g. 50 tracks
|
||||||
|
|
||||||
|
// Home
|
||||||
|
"home.title": "主頁",
|
||||||
|
"home.recentlyPlayed": "最近播放",
|
||||||
|
"home.recentlyAdded": "最近加入",
|
||||||
|
"home.artistsFeed": "藝人動態",
|
||||||
|
"home.artistsFeed.noArtist": "追蹤一些藝人來獲得他們的最新歌曲資訊。",
|
||||||
|
"home.madeForYou": "為您推薦",
|
||||||
|
"home.friendsListeningTo": "朋友正在聆聽",
|
||||||
|
"home.followedArtists": "追蹤的藝人",
|
||||||
|
// Errors
|
||||||
|
"error.appleMusicSubRequired": "需要訂閱Apple Music以使用Cider",
|
||||||
|
"error.connectionError": "無法連接到 Apple Music。",
|
||||||
|
"error.noResults": "沒有結果",
|
||||||
|
"error.noResults.description": "請嘗試新的搜尋內容。",
|
||||||
|
|
||||||
|
//Podcasts
|
||||||
|
"podcast.followOnCider": "在Cider上追蹤",
|
||||||
|
"podcast.followedOnCider": "已在Cider上追蹤",
|
||||||
|
"podcast.subscribeOnItunes": "在iTunes上訂閱",
|
||||||
|
"podcast.subscribedOnItunes": "已在iTunes上訂閱",
|
||||||
|
"podcast.itunesStore": "iTunes Store",
|
||||||
|
"podcast.episodes": "單集",
|
||||||
|
"podcast.playEpisode": "播放單集",
|
||||||
|
"podcast.website": "Podcast 網頁",
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
"action.addToLibrary": "加入資料庫",
|
||||||
|
"action.addToLibrary.success": "成功加入資料庫",
|
||||||
|
"action.addToLibrary.error": "加入資料庫的過程發生錯誤",
|
||||||
|
"action.removeFromLibrary": "從資料庫刪除",
|
||||||
|
"action.removeFromLibrary.success": "已從資料庫刪除",
|
||||||
|
"action.addToQueue": "加入待播清單",
|
||||||
|
"action.addToQueue.success": "成功加入待播清單",
|
||||||
|
"action.addToQueue.error": "加入待播清單的過程發生錯誤",
|
||||||
|
"action.removeFromQueue": "從待播清單刪除",
|
||||||
|
"action.removeFromQueue.success": "已從待播清單刪除",
|
||||||
|
"action.removeFromQueue.error": "從待播清單刪除的過程中發生錯誤",
|
||||||
|
"action.addToPlaylist": "加至播放列表",
|
||||||
|
"action.removeFromPlaylist": "從播放列表刪除",
|
||||||
|
"action.addToFavorites": "加至收藏",
|
||||||
|
"action.follow": "追蹤",
|
||||||
|
"action.follow.success": "追蹤中",
|
||||||
|
"action.follow.error": "追蹤的過程發生錯誤",
|
||||||
|
"action.unfollow": "取消追蹤",
|
||||||
|
"action.unfollow.success": "已取消追蹤",
|
||||||
|
"action.unfollow.error": "取消追蹤的過程發生錯誤",
|
||||||
|
"action.playNext": "插播",
|
||||||
|
"action.playLater": "稍後播放",
|
||||||
|
"action.startRadio": "建立電台",
|
||||||
|
"action.goToArtist": "前往藝人",
|
||||||
|
"action.goToAlbum": "前往專輯",
|
||||||
|
"action.moveToTop": "移動到頂部",
|
||||||
|
"action.share": "分享歌曲",
|
||||||
|
"action.rename": "重新命名",
|
||||||
|
"action.love": "喜愛",
|
||||||
|
"action.unlove": "取消喜愛",
|
||||||
|
"action.dislike": "減少此類建議",
|
||||||
|
"action.undoDislike": "還原減小此類建議",
|
||||||
|
"action.showWebRemoteQR": "顯示遙距控制二維碼",
|
||||||
|
|
||||||
|
// Settings - Audio
|
||||||
|
"settings.header.audio": "音訊",
|
||||||
|
"settings.header.audio.description": "調整Cider的音訊設定",
|
||||||
|
"settings.option.audio.quality": "音訊音質", // Dropdown
|
||||||
|
"settings.header.audio.quality.high": "高素質",
|
||||||
|
"settings.header.audio.quality.low": "高效率",
|
||||||
|
"settings.header.audio.quality.auto": "自動",
|
||||||
|
"settings.option.audio.seamlessTransition": "無縫播放", // Toggle
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality": "進階功能", // Toggle
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.description": "啟用 AudioContext 將解鎖類似音量平衡和等化器的進階功能。但是會在一些電腦造成音樂卡頓。",
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.audioNormalization": "音量平衡", // Toggle
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.audioNormalization.description": "將平衡輕柔和響亮的歌曲,建立更統一的聆聽體驗。",
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization": "空間音訊", // Toggle
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization.description": "將音訊進行空間化處理來製造一個更立體的聆聽體驗(注:此功能不是官方的杜比全景聲)",
|
||||||
|
// Settings - Visual
|
||||||
|
"settings.header.visual": "外觀",
|
||||||
|
"settings.header.visual.description": "調整Cider的外觀",
|
||||||
|
"settings.option.visual.windowBackgroundStyle": "窗口背景樣式", // Toggle
|
||||||
|
"settings.header.visual.windowBackgroundStyle.none": "空白",
|
||||||
|
"settings.header.visual.windowBackgroundStyle.artwork": "專輯封面",
|
||||||
|
"settings.option.visual.animatedArtwork": "動態專輯封面", // Dropdown
|
||||||
|
"settings.header.visual.animatedArtwork.always": "總是顯示",
|
||||||
|
"settings.header.visual.animatedArtwork.limited": "只在藝人頁面和專輯封面顯示",
|
||||||
|
"settings.header.visual.animatedArtwork.disable": "關閉",
|
||||||
|
"settings.option.visual.animatedArtworkQuality": "動態專輯封面品質", // Dropdown
|
||||||
|
"settings.header.visual.animatedArtworkQuality.low": "低",
|
||||||
|
"settings.header.visual.animatedArtworkQuality.medium": "中",
|
||||||
|
"settings.header.visual.animatedArtworkQuality.high": "高",
|
||||||
|
"settings.header.visual.animatedArtworkQuality.veryHigh": "非常高",
|
||||||
|
"settings.header.visual.animatedArtworkQuality.extreme": "極高",
|
||||||
|
"settings.option.visual.animatedWindowBackground": "動態窗口背景", // Toggle
|
||||||
|
"settings.option.visual.hardwareAcceleration": "硬體加速", // Dropdown
|
||||||
|
"settings.option.visual.hardwareAcceleration.description": "需要重啓 Cider 才能生效",
|
||||||
|
"settings.header.visual.hardwareAcceleration.default": "默認",
|
||||||
|
"settings.header.visual.hardwareAcceleration.webGPU": "WebGPU",
|
||||||
|
// Refer to term.disabled for the disabled option
|
||||||
|
"settings.option.visual.showPersonalInfo": "顯示個人檔案", // Toggle
|
||||||
|
// Settings - General (Reserved)
|
||||||
|
"settings.header.general": "一般",
|
||||||
|
"settings.header.general.description": "調整Cider的一般設定",
|
||||||
|
|
||||||
|
// Settings - Lyrics
|
||||||
|
"settings.header.lyrics": "歌詞",
|
||||||
|
"settings.header.lyrics.description": "調整Cider的歌詞設定",
|
||||||
|
"settings.option.lyrics.enableMusixmatch": "啟用 Musixmatch 歌詞", // Toggle
|
||||||
|
"settings.option.lyrics.enableMusixmatchKaraoke": "啟用卡拉OK模式(僅限Musixmatch)", // Toggle
|
||||||
|
"settings.option.lyrics.musixmatchPreferredLanguage": "Musixmatch 歌詞語言偏好", // Dropdown
|
||||||
|
"settings.option.lyrics.enableYoutubeLyrics": "播放 MV 時使用 YouTube 歌詞", // Toggle
|
||||||
|
|
||||||
|
// Settings - Connectivity
|
||||||
|
"settings.header.connectivity": "外部連結",
|
||||||
|
"settings.header.connectivity.description": "調整Cider與外部的連結",
|
||||||
|
"settings.option.connectivity.discordRPC": "Discord 狀態", // Dropdown
|
||||||
|
// Refer to term.disabled for the disabled option
|
||||||
|
"settings.header.connectivity.discordRPC.cider": "顯示為'Cider'",
|
||||||
|
"settings.header.connectivity.discordRPC.appleMusic": "顯示為'Apple Music'",
|
||||||
|
"settings.option.connectivity.discordRPC.clearOnPause": "暫停時清除 Discord 狀態", // Toggle
|
||||||
|
"settings.option.connectivity.lastfmScrobble": "LastFM Scrobbling 記錄", // Option to Connect
|
||||||
|
"settings.option.connectivity.lastfmScrobble.delay": "LastFM Scrobble 延遲 (%)",
|
||||||
|
"settings.option.connectivity.lastfmScrobble.nowPlaying": "啟用 LastFM 正在播放",
|
||||||
|
"settings.option.connectivity.lastfmScrobble.removeFeatured": "從歌名中移除藝人推薦 (LastFM)",
|
||||||
|
"settings.option.connectivity.lastfmScrobble.filterLoop": "Filter looped track (LastFM)",
|
||||||
|
// Refer to term.connect for the connect button
|
||||||
|
|
||||||
|
// Settings - Experimental
|
||||||
|
"settings.header.experimental": "實驗性功能",
|
||||||
|
"settings.header.experimental.description": "調整Cider的實驗性功能",
|
||||||
|
"settings.option.experimental.compactUI": "緊凑型 UI", // Toggle
|
||||||
|
"settings.option.experimental.closeButtonBehaviour": "關閉按鈕行為",
|
||||||
|
"settings.option.experimental.closeButtonBehaviour.quit": "結束 Cider",
|
||||||
|
"settings.option.experimental.closeButtonBehaviour.minimizeTaskbar": "縮小至工作列",
|
||||||
|
"settings.option.experimental.closeButtonBehaviour.minimizeTray": "縮小至系統托盤",
|
||||||
|
// Refer to term.disabled & term.enabled
|
||||||
|
|
||||||
|
// Spatialization Menu
|
||||||
|
"spatial.spatialProperties" : "空間音訊屬性",
|
||||||
|
"spatial.width" : "闊度",
|
||||||
|
"spatial.height" : "高度",
|
||||||
|
"spatial.depth" : "深度",
|
||||||
|
"spatial.gain" : "增益",
|
||||||
|
"spatial.roomMaterials" : "空間材質",
|
||||||
|
"spatial.roomDimensions" : "空間大小",
|
||||||
|
"spatial.roomPositions" : "空間位置",
|
||||||
|
"spatial.setDimensions" : "大小設定",
|
||||||
|
"spatial.setPositions" : "位置設定",
|
||||||
|
"spatial.up" : "上方",
|
||||||
|
"spatial.front" : "前方",
|
||||||
|
"spatial.left" : "左方",
|
||||||
|
"spatial.right" : "右方",
|
||||||
|
"spatial.back" : "後方",
|
||||||
|
"spatial.down" : "下方",
|
||||||
|
"spatial.listener" : "觀眾",
|
||||||
|
"spatial.audioSource" : "音源",
|
||||||
|
|
||||||
|
// Settings - Unfinished
|
||||||
|
"settings.header.unfinished": "未完成",
|
||||||
|
|
||||||
|
// Web Remote
|
||||||
|
"remote.web.title": "遙距控制 Cider",
|
||||||
|
"remote.web.description": "掃描以下的二維碼以控制 Cider",
|
||||||
|
|
||||||
|
//About
|
||||||
|
"about.thanks": "感謝 Cider Collective 以及所有貢獻者所作出的貢獻。"
|
||||||
|
}
|
283
src/i18n/zh_TW.jsonc
Normal file
|
@ -0,0 +1,283 @@
|
||||||
|
{
|
||||||
|
// App info
|
||||||
|
"app.name": "Cider",
|
||||||
|
|
||||||
|
"date.format": "${y}年${m}月${d}日",
|
||||||
|
|
||||||
|
// i18n Info
|
||||||
|
"i18n.languageName": "繁體中文(台灣)", // name of language in native language
|
||||||
|
"i18n.languageNameEnglish": "Trad. Chinese (Taiwan)", // name of language in English
|
||||||
|
"i18n.category": "main", // main = real language, fun = fun community languages
|
||||||
|
"i18n.authors": "@maikirakiwi", // Authors, if you contribute to this file feel free to add your name seperated with a space
|
||||||
|
|
||||||
|
|
||||||
|
// Dialogs
|
||||||
|
"dialog.cancel": "取消",
|
||||||
|
"dialog.ok": "OK",
|
||||||
|
|
||||||
|
// Notification
|
||||||
|
"notification.updatingLibrarySongs": "正在更新資料庫的歌曲...",
|
||||||
|
"notification.updatingLibraryAlbums": "正在更新資料庫的專輯...",
|
||||||
|
"notification.updatingLibraryArtists": "正在更新資料庫的藝人...",
|
||||||
|
// Terms
|
||||||
|
"term.appleMusic": "Apple Music", // Follows brand term
|
||||||
|
"term.applePodcasts": "Apple Podcasts", // Follows brand term
|
||||||
|
"term.itunes": "iTunes", // Follows brand term
|
||||||
|
"term.github": "GitHub", // Follows brand term
|
||||||
|
"term.discord": "Discord", // Follows brand term
|
||||||
|
"term.learnMore": "更多内容",
|
||||||
|
"term.accountSettings": "賬戶設定",
|
||||||
|
"term.logout": "登出",
|
||||||
|
"term.login": "登入",
|
||||||
|
"term.about": "關於",
|
||||||
|
"term.privateSession": "私人時段",
|
||||||
|
"term.queue": "待播清單",
|
||||||
|
"term.search": "搜尋",
|
||||||
|
"term.library": "資料庫",
|
||||||
|
"term.listenNow": "立即聆聽",
|
||||||
|
"term.browse": "瀏覽",
|
||||||
|
"term.radio": "廣播",
|
||||||
|
"term.recentlyAdded": "最近加入",
|
||||||
|
"term.songs": "歌曲",
|
||||||
|
"term.albums": "專輯",
|
||||||
|
"term.artists": "藝人",
|
||||||
|
"term.podcasts": "Podcasts",
|
||||||
|
"term.playlists": "播放列表",
|
||||||
|
"term.playlist": "播放列表",
|
||||||
|
"term.play": "播放",
|
||||||
|
"term.pause": "暫停",
|
||||||
|
"term.previous": "上一首",
|
||||||
|
"term.next": "下一首",
|
||||||
|
"term.shuffle": "隨機播放",
|
||||||
|
"term.repeat": "重複播放",
|
||||||
|
"term.volume": "音量",
|
||||||
|
"term.mute": "靜音",
|
||||||
|
"term.unmute": "取消靜音",
|
||||||
|
"term.share": "分享",
|
||||||
|
"term.settings": "設定",
|
||||||
|
"term.seeAll": "顯示全部",
|
||||||
|
"term.sortBy": "排序",
|
||||||
|
"term.sortBy.album": "專輯",
|
||||||
|
"term.sortBy.artist": "藝人",
|
||||||
|
"term.sortBy.name": "歌名",
|
||||||
|
"term.sortBy.genre": "音樂風格",
|
||||||
|
"term.sortBy.releaseDate": "發行日期",
|
||||||
|
"term.sortBy.duration": "時長",
|
||||||
|
"term.sortOrder": "字母排序",
|
||||||
|
"term.sortOrder.ascending": "升序",
|
||||||
|
"term.sortOrder.descending": "降序",
|
||||||
|
"term.viewAs": "顯示模式",
|
||||||
|
"term.viewAs.coverArt": "專輯封面",
|
||||||
|
"term.viewAs.list": "列表",
|
||||||
|
"term.size": "大小",
|
||||||
|
"term.size.normal": "正常",
|
||||||
|
"term.size.compact": "緊凑",
|
||||||
|
"term.enable": "啟用",
|
||||||
|
"term.disable": "停用",
|
||||||
|
"term.enabled": "已啟用",
|
||||||
|
"term.disabled": "已停用",
|
||||||
|
"term.connect": "連接",
|
||||||
|
"term.connecting": "連接中",
|
||||||
|
"term.disconnect": "斷開",
|
||||||
|
"term.authed": "已授權",
|
||||||
|
"term.confirm": "確定?",
|
||||||
|
"term.more": "更多",
|
||||||
|
"term.less": "更少",
|
||||||
|
"term.showMore": "顯示更多",
|
||||||
|
"term.showLess": "顯示更少",
|
||||||
|
"term.topSongs" : "熱門歌曲",
|
||||||
|
"term.latestReleases": "最新發行",
|
||||||
|
"term.time.added": "加入于",
|
||||||
|
"term.time.released": "發行于",
|
||||||
|
"term.time.updated": "更改于",
|
||||||
|
"term.time.hours": "小時",
|
||||||
|
"term.time.hour": "小時",
|
||||||
|
"term.time.minutes": "分鐘",
|
||||||
|
"term.time.minute": "分鐘",
|
||||||
|
"term.time.seconds": "秒",
|
||||||
|
"term.time.second": "秒",
|
||||||
|
"term.fullscreenView": "全螢幕顯示",
|
||||||
|
"term.defaultView": "預設顯示",
|
||||||
|
"term.spacializedAudioSetting": "音頻空間化設置",
|
||||||
|
"term.clearAll": "清空",
|
||||||
|
"term.recentStations": "最近收聽的廣播",
|
||||||
|
"term.language": "語言",
|
||||||
|
"term.noLyrics": "正在載入。。/ 無歌詞結果 / 純音樂",
|
||||||
|
"term.copyright": "版權聲明",
|
||||||
|
"term.rightsReserved": "保留所有權利。",
|
||||||
|
"term.sponsor": "贊助",
|
||||||
|
"term.ciderTeam": "Cider 團隊",
|
||||||
|
"term.developer": "開發者",
|
||||||
|
"term.socialTeam": "公關團隊",
|
||||||
|
"term.contributors": "貢獻者",
|
||||||
|
"term.equalizer": "等化器",
|
||||||
|
"term.reset": "重置",
|
||||||
|
"term.tracks": "首歌曲", // Assume x amount of tracks. e.g. 50 tracks
|
||||||
|
|
||||||
|
|
||||||
|
// Home
|
||||||
|
"home.title": "主頁",
|
||||||
|
"home.recentlyPlayed": "最近播放",
|
||||||
|
"home.recentlyAdded": "最近加入",
|
||||||
|
"home.artistsFeed": "藝人追蹤",
|
||||||
|
"home.artistsFeed.noArtist": "追蹤一些藝人來獲得他們的最新歌曲。",
|
||||||
|
"home.madeForYou": "為您推薦",
|
||||||
|
"home.friendsListeningTo": "朋友正在聆聽",
|
||||||
|
"home.followedArtists": "追蹤的藝人",
|
||||||
|
// Errors
|
||||||
|
"error.appleMusicSubRequired": "需要訂閱Apple Music以使用Cider",
|
||||||
|
"error.connectionError": "無法連接到 Apple Music。",
|
||||||
|
"error.noResults": "沒有結果",
|
||||||
|
"error.noResults.description": "嘗試新的搜尋項目。",
|
||||||
|
|
||||||
|
//Podcasts
|
||||||
|
"podcast.followOnCider": "在 Cider 上追蹤",
|
||||||
|
"podcast.followedOnCider": "已追蹤",
|
||||||
|
"podcast.subscribeOnItunes": "在 iTunes 上訂閱",
|
||||||
|
"podcast.subscribedOnItunes": "已訂閱",
|
||||||
|
"podcast.itunesStore": "iTunes Store", // Follows brand term
|
||||||
|
"podcast.episodes": "單集",
|
||||||
|
"podcast.playEpisode": "播放單集",
|
||||||
|
"podcast.website": "Podcast 網站",
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
"action.addToLibrary": "加入資料庫",
|
||||||
|
"action.addToLibrary.success": "成功加入資料庫",
|
||||||
|
"action.addToLibrary.error": "加入資料庫的過程發生錯誤",
|
||||||
|
"action.removeFromLibrary": "從資料庫刪除",
|
||||||
|
"action.removeFromLibrary.success": "已從資料庫刪除",
|
||||||
|
"action.addToQueue": "加入待播清單",
|
||||||
|
"action.addToQueue.success": "成功加入待播清單",
|
||||||
|
"action.addToQueue.error": "加入待播清單的過程發生錯誤",
|
||||||
|
"action.removeFromQueue": "從待播清單刪除",
|
||||||
|
"action.removeFromQueue.success": "已從待播清單刪除",
|
||||||
|
"action.removeFromQueue.error": "從待播清單刪除的過程發生錯誤",
|
||||||
|
"action.addToPlaylist": "加入播放列表",
|
||||||
|
"action.removeFromPlaylist": "從播放列表刪除",
|
||||||
|
"action.addToFavorites": "加入我的最愛",
|
||||||
|
"action.follow": "追蹤",
|
||||||
|
"action.follow.success": "追蹤中",
|
||||||
|
"action.follow.error": "追蹤的過程發生錯誤",
|
||||||
|
"action.unfollow": "取消追蹤",
|
||||||
|
"action.unfollow.success": "已取消追蹤",
|
||||||
|
"action.unfollow.error": "取消追蹤的過程發生錯誤",
|
||||||
|
"action.playNext": "插播",
|
||||||
|
"action.playLater": "最後播放",
|
||||||
|
"action.startRadio": "建立電台",
|
||||||
|
"action.goToArtist": "前往藝人",
|
||||||
|
"action.goToAlbum": "前往專輯",
|
||||||
|
"action.moveToTop": "移至頂端",
|
||||||
|
"action.share": "分享歌曲",
|
||||||
|
"action.rename": "重新命名",
|
||||||
|
"action.love": "喜愛",
|
||||||
|
"action.unlove": "取消喜愛",
|
||||||
|
"action.dislike": "減少此類建議",
|
||||||
|
"action.undoDislike": "還原減小此類建議",
|
||||||
|
"action.showWebRemoteQR": "顯示遠程遙控行動條碼",
|
||||||
|
"action.playTracksNext": "插播 ${app.selectedMediaItems.length} 首歌曲",
|
||||||
|
"action.playTracksLater": "最後播放 ${app.selectedMediaItems.length} 首歌曲",
|
||||||
|
"action.removeTracks": "從待播清單刪除 ${self.selectedItems.length} 首歌曲",
|
||||||
|
|
||||||
|
// Settings - Audio
|
||||||
|
"settings.header.audio": "音訊",
|
||||||
|
"settings.header.audio.description": "調整Cider的音訊設定",
|
||||||
|
"settings.option.audio.quality": "音訊音質", // Dropdown
|
||||||
|
"settings.header.audio.quality.high": "高品質",
|
||||||
|
"settings.header.audio.quality.low": "高效率",
|
||||||
|
"settings.header.audio.quality.auto": "自動",
|
||||||
|
"settings.option.audio.seamlessTransition": "無間斷播放", // Toggle
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality": "進階機能", // Toggle
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.description": "啟用 AudioContext 將解鎖類似音訊標準化和等化器的進階機能。但是會在一些電腦造成音樂卡頓。",
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.audioNormalization": "音訊標準化", // Toggle
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.audioNormalization.description": "將平衡輕柔和響亮的歌曲,建立更統一的聆聽體驗。",
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization": "音訊空間化", // Toggle
|
||||||
|
"settings.option.audio.enableAdvancedFunctionality.audioSpatialization.description": "將音訊進行空間化處理來製造一個更立體的聆聽體驗(注:此功能不是官方的杜比全景聲)",
|
||||||
|
// Settings - Visual
|
||||||
|
"settings.header.visual": "外觀",
|
||||||
|
"settings.header.visual.description": "調整Cider的外觀",
|
||||||
|
"settings.option.visual.windowBackgroundStyle": "窗口背景樣式", // Toggle
|
||||||
|
"settings.header.visual.windowBackgroundStyle.none": "空白",
|
||||||
|
"settings.header.visual.windowBackgroundStyle.artwork": "專輯封面",
|
||||||
|
"settings.option.visual.animatedArtwork": "動態專輯封面", // Dropdown
|
||||||
|
"settings.header.visual.animatedArtwork.always": "總是顯示",
|
||||||
|
"settings.header.visual.animatedArtwork.limited": "只在藝人頁面和專輯封面顯示",
|
||||||
|
"settings.header.visual.animatedArtwork.disable": "關閉",
|
||||||
|
"settings.option.visual.animatedArtworkQuality": "動態專輯封面品質", // Dropdown
|
||||||
|
"settings.header.visual.animatedArtworkQuality.low": "低",
|
||||||
|
"settings.header.visual.animatedArtworkQuality.medium": "中",
|
||||||
|
"settings.header.visual.animatedArtworkQuality.high": "高",
|
||||||
|
"settings.header.visual.animatedArtworkQuality.veryHigh": "非常高",
|
||||||
|
"settings.header.visual.animatedArtworkQuality.extreme": "極高",
|
||||||
|
"settings.option.visual.animatedWindowBackground": "動態窗口背景", // Toggle
|
||||||
|
"settings.option.visual.hardwareAcceleration": "硬體加速", // Dropdown
|
||||||
|
"settings.option.visual.hardwareAcceleration.description": "需要重新啟動 Cider 才會生效",
|
||||||
|
"settings.header.visual.hardwareAcceleration.default": "默認",
|
||||||
|
"settings.header.visual.hardwareAcceleration.webGPU": "WebGPU",
|
||||||
|
// Refer to term.disabled for the disabled option
|
||||||
|
"settings.option.visual.showPersonalInfo": "顯示個人檔案", // Toggle
|
||||||
|
// Settings - General (Reserved)
|
||||||
|
"settings.header.general": "一般",
|
||||||
|
"settings.header.general.description": "調整Cider的一般設定",
|
||||||
|
|
||||||
|
// Settings - Lyrics
|
||||||
|
"settings.header.lyrics": "歌詞",
|
||||||
|
"settings.header.lyrics.description": "調整 Cider 的歌詞設定",
|
||||||
|
"settings.option.lyrics.enableMusixmatch": "啟用 Musixmatch 歌詞", // Toggle
|
||||||
|
"settings.option.lyrics.enableMusixmatchKaraoke": "啟用K歌模式(僅限Musixmatch)", // Toggle
|
||||||
|
"settings.option.lyrics.musixmatchPreferredLanguage": "Musixmatch 歌詞語言偏好", // Dropdown
|
||||||
|
"settings.option.lyrics.enableYoutubeLyrics": "播放 MV 時使用 YouTube 歌詞", // Toggle
|
||||||
|
|
||||||
|
// Settings - Connectivity
|
||||||
|
"settings.header.connectivity": "外部連接",
|
||||||
|
"settings.header.connectivity.description": "調整Cider與外部的連接",
|
||||||
|
"settings.option.connectivity.discordRPC": "Discord 動態", // Dropdown
|
||||||
|
// Refer to term.disabled for the disabled option
|
||||||
|
"settings.header.connectivity.discordRPC.cider": "顯示正在玩 'Cider'",
|
||||||
|
"settings.header.connectivity.discordRPC.appleMusic": "顯示正在玩 'Apple Music'",
|
||||||
|
"settings.option.connectivity.discordRPC.clearOnPause": "暫停時清除 Discord 動態", // Toggle
|
||||||
|
"settings.option.connectivity.lastfmScrobble": "LastFM Scrobbling 記錄", // Option to Connect
|
||||||
|
"settings.option.connectivity.lastfmScrobble.delay": "LastFM Scrobble 延遲 (%)",
|
||||||
|
"settings.option.connectivity.lastfmScrobble.nowPlaying": "啟用 LastFM 目前聆聽", // Toggle
|
||||||
|
"settings.option.connectivity.lastfmScrobble.removeFeatured": "從歌名中移除藝人推薦 (LastFM)",
|
||||||
|
// Refer to term.connect for the connect button
|
||||||
|
|
||||||
|
// Settings - Experimental
|
||||||
|
"settings.header.experimental": "實驗性功能",
|
||||||
|
"settings.header.experimental.description": "調整 Cider 的實驗性功能",
|
||||||
|
"settings.option.experimental.compactUI": "緊凑型 UI", // Toggle
|
||||||
|
"settings.option.experimental.closeButtonBehaviour": "關閉按鈕行為", // Dropdown
|
||||||
|
"settings.option.experimental.closeButtonBehaviour.quit": "退出 Cider",
|
||||||
|
"settings.option.experimental.closeButtonBehaviour.minimizeTaskbar": "最小化到工作列",
|
||||||
|
"settings.option.experimental.closeButtonBehaviour.minimizeTray": "最小化到系統匣",
|
||||||
|
// Refer to term.disabled & term.enabled
|
||||||
|
|
||||||
|
// Spatialization Menu
|
||||||
|
"spatial.spatialProperties" : "空間化屬性",
|
||||||
|
"spatial.width" : "寬度",
|
||||||
|
"spatial.height" : "高度",
|
||||||
|
"spatial.depth" : "深度",
|
||||||
|
"spatial.gain" : "增益",
|
||||||
|
"spatial.roomMaterials" : "空間材質",
|
||||||
|
"spatial.roomDimensions" : "空間尺寸",
|
||||||
|
"spatial.roomPositions" : "空間位置",
|
||||||
|
"spatial.setDimensions" : "設定尺寸",
|
||||||
|
"spatial.setPositions" : "設定位置",
|
||||||
|
"spatial.up" : "上",
|
||||||
|
"spatial.front" : "前",
|
||||||
|
"spatial.left" : "左",
|
||||||
|
"spatial.right" : "右",
|
||||||
|
"spatial.back" : "後",
|
||||||
|
"spatial.down" : "下",
|
||||||
|
"spatial.listener" : "聽衆",
|
||||||
|
"spatial.audioSource" : "音訊來源",
|
||||||
|
|
||||||
|
// Settings - Unfinished
|
||||||
|
"settings.header.unfinished": "未完成",
|
||||||
|
|
||||||
|
// Web Remote
|
||||||
|
"remote.web.title": "Cider 遠控",
|
||||||
|
"remote.web.description": "掃描以下的行動條碼以控制 Cider",
|
||||||
|
|
||||||
|
//About
|
||||||
|
"about.thanks": "著重感謝 Cider Collective 的成員以及所有為項目付出的貢獻者。"
|
||||||
|
}
|
197
src/main/base/app.ts
Normal file
|
@ -0,0 +1,197 @@
|
||||||
|
import * as electron from 'electron';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
export class AppEvents {
|
||||||
|
private static protocols: any = [
|
||||||
|
"ame",
|
||||||
|
"cider",
|
||||||
|
"itms",
|
||||||
|
"itmss",
|
||||||
|
"musics",
|
||||||
|
"music"
|
||||||
|
]
|
||||||
|
private static plugin: any = null;
|
||||||
|
private static store: any = null;
|
||||||
|
private static win: any = null;
|
||||||
|
|
||||||
|
constructor(store: any) {
|
||||||
|
AppEvents.store = store
|
||||||
|
AppEvents.start(store);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles all actions that occur for the app on start (Mainly commandline arguments)
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
private static start(store: any): void {
|
||||||
|
console.info('[AppEvents] App started');
|
||||||
|
|
||||||
|
/**********************************************************************************************************************
|
||||||
|
* Startup arguments handling
|
||||||
|
**********************************************************************************************************************/
|
||||||
|
if (electron.app.commandLine.hasSwitch('version') || electron.app.commandLine.hasSwitch('v')) {
|
||||||
|
console.log(electron.app.getVersion())
|
||||||
|
electron.app.exit()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verbose Check
|
||||||
|
if (electron.app.commandLine.hasSwitch('verbose')) {
|
||||||
|
console.log("[Cider] User has launched the application with --verbose");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log File Location
|
||||||
|
if (electron.app.commandLine.hasSwitch('log') || electron.app.commandLine.hasSwitch('l')) {
|
||||||
|
console.log(path.join(electron.app.getPath('userData'), 'logs'))
|
||||||
|
electron.app.exit()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expose GC
|
||||||
|
electron.app.commandLine.appendSwitch('js-flags','--expose_gc')
|
||||||
|
|
||||||
|
/***********************************************************************************************************************
|
||||||
|
* Commandline arguments
|
||||||
|
**********************************************************************************************************************/
|
||||||
|
switch (store.visual.hw_acceleration) {
|
||||||
|
default:
|
||||||
|
case "default":
|
||||||
|
electron.app.commandLine.appendSwitch('enable-accelerated-mjpeg-decode')
|
||||||
|
electron.app.commandLine.appendSwitch('enable-accelerated-video')
|
||||||
|
electron.app.commandLine.appendSwitch('disable-gpu-driver-bug-workarounds')
|
||||||
|
electron.app.commandLine.appendSwitch('ignore-gpu-blacklist')
|
||||||
|
electron.app.commandLine.appendSwitch('enable-native-gpu-memory-buffers')
|
||||||
|
electron.app.commandLine.appendSwitch('enable-accelerated-video-decode');
|
||||||
|
electron.app.commandLine.appendSwitch('enable-gpu-rasterization');
|
||||||
|
electron.app.commandLine.appendSwitch('enable-native-gpu-memory-buffers');
|
||||||
|
electron.app.commandLine.appendSwitch('enable-oop-rasterization');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "webgpu":
|
||||||
|
console.info("WebGPU is enabled.");
|
||||||
|
electron.app.commandLine.appendSwitch('enable-unsafe-webgpu')
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "disabled":
|
||||||
|
console.info("Hardware acceleration is disabled.");
|
||||||
|
electron.app.commandLine.appendSwitch('disable-gpu')
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.platform === "linux") {
|
||||||
|
electron.app.commandLine.appendSwitch('disable-features', 'MediaSessionService');
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************************************************************************************************************
|
||||||
|
* Protocols
|
||||||
|
**********************************************************************************************************************/
|
||||||
|
if (process.defaultApp) {
|
||||||
|
if (process.argv.length >= 2) {
|
||||||
|
this.protocols.forEach((protocol: string) => {
|
||||||
|
electron.app.setAsDefaultProtocolClient(protocol, process.execPath, [path.resolve(process.argv[1])])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.protocols.forEach((protocol: string) => {
|
||||||
|
electron.app.setAsDefaultProtocolClient(protocol)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public quit() {
|
||||||
|
console.log('App stopped');
|
||||||
|
}
|
||||||
|
|
||||||
|
public ready(plug: any) {
|
||||||
|
AppEvents.plugin = plug
|
||||||
|
console.log('[AppEvents] App ready');
|
||||||
|
}
|
||||||
|
|
||||||
|
public bwCreated(win: Electron.BrowserWindow) {
|
||||||
|
AppEvents.win = win
|
||||||
|
|
||||||
|
electron.app.on('open-url', (event, url) => {
|
||||||
|
event.preventDefault()
|
||||||
|
if (AppEvents.protocols.some((protocol: string) => url.includes(protocol))) {
|
||||||
|
AppEvents.LinkHandler(url)
|
||||||
|
console.log(url)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
AppEvents.InstanceHandler()
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************************************************************************************************************
|
||||||
|
* Private methods
|
||||||
|
**********************************************************************************************************************/
|
||||||
|
|
||||||
|
private static LinkHandler(arg: string) {
|
||||||
|
if (!arg) return;
|
||||||
|
|
||||||
|
// LastFM Auth URL
|
||||||
|
if (arg.includes('auth')) {
|
||||||
|
let authURI = arg.split('/auth/')[1]
|
||||||
|
if (authURI.startsWith('lastfm')) { // If we wanted more auth options
|
||||||
|
const authKey = authURI.split('lastfm?token=')[1];
|
||||||
|
AppEvents.store.set('lastfm.enabled', true);
|
||||||
|
AppEvents.store.set('lastfm.auth_token', authKey);
|
||||||
|
AppEvents.win.webContents.send('LastfmAuthenticated', authKey);
|
||||||
|
AppEvents.plugin.callPlugin('lastfm', 'authenticate', authKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Play
|
||||||
|
else if (arg.includes('/play/')) { //Steer away from protocol:// specific conditionals
|
||||||
|
const playParam = arg.split('/play/')[1]
|
||||||
|
|
||||||
|
const mediaType = {
|
||||||
|
"s/": "song",
|
||||||
|
"a/": "album",
|
||||||
|
"p/": "playlist"
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(mediaType)) {
|
||||||
|
if (playParam.includes(key)) {
|
||||||
|
const id = playParam.split(key)[1]
|
||||||
|
AppEvents.win.webContents.send('play', value, id)
|
||||||
|
console.debug(`[LinkHandler] Attempting to load ${value} by id: ${id}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (arg.includes('music.apple.com')) { // URL (used with itms/itmss/music/musics uris)
|
||||||
|
console.log(arg)
|
||||||
|
let url = arg.split('//')[1]
|
||||||
|
console.warn(`[LinkHandler] Attempting to load url: ${url}`);
|
||||||
|
AppEvents.win.webContents.send('play', 'url', url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static InstanceHandler() {
|
||||||
|
|
||||||
|
// Detects of an existing instance is running (So if the lock has been achieved, no existing instance has been found)
|
||||||
|
const gotTheLock = electron.app.requestSingleInstanceLock()
|
||||||
|
|
||||||
|
if (!gotTheLock) { // Runs on the new instance if another instance has been found
|
||||||
|
console.log('[Cider] Another instance has been found, quitting.')
|
||||||
|
electron.app.quit()
|
||||||
|
} else { // Runs on the first instance if no other instance has been found
|
||||||
|
electron.app.on('second-instance', (_event, startArgs) => {
|
||||||
|
console.log("[InstanceHandler] (second-instance) Instance started with " + startArgs.toString())
|
||||||
|
|
||||||
|
startArgs.forEach(arg => {
|
||||||
|
console.log(arg)
|
||||||
|
if (arg.includes("cider://")) {
|
||||||
|
console.debug('[InstanceHandler] (second-instance) Link detected with ' + arg)
|
||||||
|
AppEvents.LinkHandler(arg)
|
||||||
|
} else if (arg.includes("--force-quit")) {
|
||||||
|
console.warn('[InstanceHandler] (second-instance) Force Quit found. Quitting App.');
|
||||||
|
electron.app.quit()
|
||||||
|
} else if (AppEvents.win) {
|
||||||
|
if (AppEvents.win.isMinimized()) AppEvents.win.restore()
|
||||||
|
AppEvents.win.focus()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
65
src/main/base/plugins.ts
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
import * as electron from 'electron'
|
||||||
|
|
||||||
|
export default class PluginHandler {
|
||||||
|
private basePluginsPath = path.join(__dirname, '../plugins');
|
||||||
|
private userPluginsPath = path.join(electron.app.getPath('userData'), 'plugins');
|
||||||
|
private readonly pluginsList: any = {};
|
||||||
|
private readonly _store: any;
|
||||||
|
|
||||||
|
constructor(config: any) {
|
||||||
|
this._store = config;
|
||||||
|
this.pluginsList = this.getPlugins();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getPlugins(): any {
|
||||||
|
let plugins: any = {};
|
||||||
|
|
||||||
|
|
||||||
|
if (fs.existsSync(this.basePluginsPath)) {
|
||||||
|
fs.readdirSync(this.basePluginsPath).forEach(file => {
|
||||||
|
if (file.endsWith('.ts') || file.endsWith('.js')) {
|
||||||
|
const plugin = require(path.join(this.basePluginsPath, file)).default;
|
||||||
|
if (plugins[file] || plugin.name in plugins) {
|
||||||
|
console.log(`[${plugin.name}] Plugin already loaded / Duplicate Class Name`);
|
||||||
|
} else {
|
||||||
|
plugins[file] = new plugin(electron.app, this._store);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (fs.existsSync(this.userPluginsPath)) {
|
||||||
|
fs.readdirSync(this.userPluginsPath).forEach(file => {
|
||||||
|
if (file.endsWith('.ts') || file.endsWith('.js')) {
|
||||||
|
const plugin = require(path.join(this.userPluginsPath, file)).default;
|
||||||
|
file = file.replace('.ts', '').replace('.js', '');
|
||||||
|
if (plugins[file] || plugin in plugins) {
|
||||||
|
console.log(`[${plugin.name}] Plugin already loaded / Duplicate Class Name`);
|
||||||
|
} else {
|
||||||
|
plugins[file] = new plugin(electron.app, this._store);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
console.log('[PluginHandler] Loaded plugins:', Object.keys(plugins));
|
||||||
|
return plugins;
|
||||||
|
}
|
||||||
|
|
||||||
|
public callPlugins(event: string, ...args: any[]) {
|
||||||
|
for (const plugin in this.pluginsList) {
|
||||||
|
if (this.pluginsList[plugin][event]) {
|
||||||
|
this.pluginsList[plugin][event](...args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public callPlugin(plugin: string, event: string, ...args: any[]) {
|
||||||
|
if (this.pluginsList[plugin][event]) {
|
||||||
|
this.pluginsList[plugin][event](...args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
166
src/main/base/store.ts
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
import * as Store from 'electron-store';
|
||||||
|
import * as electron from "electron";
|
||||||
|
|
||||||
|
export class ConfigStore {
|
||||||
|
private _store: Store;
|
||||||
|
|
||||||
|
private defaults: any = {
|
||||||
|
"general": {
|
||||||
|
"close_behavior": 0, // 0 = close, 1 = minimize, 2 = minimize to tray
|
||||||
|
"startup_behavior": 0, // 0 = nothing, 1 = open on startup
|
||||||
|
"discord_rpc": 1, // 0 = disabled, 1 = enabled as Cider, 2 = enabled as Apple Music
|
||||||
|
"discordClearActivityOnPause": 1, // 0 = disabled, 1 = enabled
|
||||||
|
"language" : "en_US"
|
||||||
|
},
|
||||||
|
"home": {
|
||||||
|
"followedArtists": [],
|
||||||
|
"favoriteItems": []
|
||||||
|
},
|
||||||
|
"libraryPrefs": {
|
||||||
|
"songs": {
|
||||||
|
"sort": "name",
|
||||||
|
"sortOrder": "asc",
|
||||||
|
"size": "normal"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"audio": {
|
||||||
|
"volume": 1,
|
||||||
|
"lastVolume": 1,
|
||||||
|
"muted": false,
|
||||||
|
"quality": "256",
|
||||||
|
"seamless_audio": true,
|
||||||
|
"normalization": false,
|
||||||
|
"spatial": false,
|
||||||
|
"maxVolume": 1,
|
||||||
|
"volumePrecision": 0.1,
|
||||||
|
"volumeRoundMax": 0.9,
|
||||||
|
"volumeRoundMin": 0.1,
|
||||||
|
"spatial_properties": {
|
||||||
|
"presets": [],
|
||||||
|
"gain": 0.8,
|
||||||
|
"listener_position": [0, 0, 0],
|
||||||
|
"audio_position": [0, 0, 0],
|
||||||
|
"room_dimensions": {
|
||||||
|
"width": 32,
|
||||||
|
"height": 12,
|
||||||
|
"depth": 32
|
||||||
|
},
|
||||||
|
"room_materials": {
|
||||||
|
"left": 'metal',
|
||||||
|
"right": 'metal',
|
||||||
|
"front": 'brick-bare',
|
||||||
|
"back": 'brick-bare',
|
||||||
|
"down": 'acoustic-ceiling-tiles',
|
||||||
|
"up": 'acoustic-ceiling-tiles',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"equalizer": {
|
||||||
|
'preset': "default",
|
||||||
|
'frequencies': [32, 63, 125, 250, 500, 1000, 2000, 4000, 8000, 16000],
|
||||||
|
'gain': [0,0,0,0,0,0,0,0,0,0],
|
||||||
|
'Q' : [1,1,1,1,1,1,1,1,1,1],
|
||||||
|
'preamp' : 0,
|
||||||
|
'mix' : 1,
|
||||||
|
'presets': [],
|
||||||
|
'userGenerated': false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"visual": {
|
||||||
|
"theme": "",
|
||||||
|
"scrollbars": 0, // 0 = show on hover, 2 = always hide, 3 = always show
|
||||||
|
"refresh_rate": 0,
|
||||||
|
"window_background_style": "artwork", // "none", "artwork", "color"
|
||||||
|
"animated_artwork": "limited", // 0 = always, 1 = limited, 2 = never
|
||||||
|
"animated_artwork_qualityLevel": 1,
|
||||||
|
"bg_artwork_rotation": false,
|
||||||
|
"hw_acceleration": "default", // default, webgpu, disabled
|
||||||
|
"showuserinfo": true,
|
||||||
|
"miniplayer_top_toggle": true
|
||||||
|
},
|
||||||
|
"lyrics": {
|
||||||
|
"enable_mxm": false,
|
||||||
|
"mxm_karaoke": false,
|
||||||
|
"mxm_language": "en",
|
||||||
|
"enable_yt": false,
|
||||||
|
},
|
||||||
|
"lastfm": {
|
||||||
|
"enabled": false,
|
||||||
|
"scrobble_after": 30,
|
||||||
|
"auth_token": "",
|
||||||
|
"enabledRemoveFeaturingArtists": true,
|
||||||
|
"filterLoop": true,
|
||||||
|
"NowPlaying": "true"
|
||||||
|
},
|
||||||
|
"advanced": {
|
||||||
|
"AudioContext": false,
|
||||||
|
"experiments": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private migrations: any = {}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this._store = new Store({
|
||||||
|
name: 'cider-config',
|
||||||
|
defaults: this.defaults,
|
||||||
|
migrations: this.migrations,
|
||||||
|
});
|
||||||
|
|
||||||
|
this._store.set(this.mergeStore(this.defaults, this._store.store))
|
||||||
|
this.ipcHandler(this._store);
|
||||||
|
}
|
||||||
|
|
||||||
|
get store() {
|
||||||
|
return this._store.store;
|
||||||
|
}
|
||||||
|
|
||||||
|
get(key: string) {
|
||||||
|
return this._store.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
set(key: string, value: any) {
|
||||||
|
this._store.set(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge Configurations
|
||||||
|
* @param target The target configuration
|
||||||
|
* @param source The source configuration
|
||||||
|
*/
|
||||||
|
private mergeStore = (target: { [x: string]: any; }, source: { [x: string]: any; }) => {
|
||||||
|
// Iterate through `source` properties and if an `Object` set property to merge of `target` and `source` properties
|
||||||
|
for (const key of Object.keys(source)) {
|
||||||
|
if (key.includes('migrations')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(source[key] instanceof Array) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (source[key] instanceof Object) Object.assign(source[key], this.mergeStore(target[key], source[key]))
|
||||||
|
}
|
||||||
|
// Join `target` and modified `source`
|
||||||
|
Object.assign(target || {}, source)
|
||||||
|
return target
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IPC Handler
|
||||||
|
*/
|
||||||
|
private ipcHandler(cfg: Store | any): void {
|
||||||
|
electron.ipcMain.handle('getStoreValue', (event, key, defaultValue) => {
|
||||||
|
return (defaultValue ? cfg.get(key, true) : cfg.get(key));
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.handle('setStoreValue', (event, key, value) => {
|
||||||
|
cfg.set(key, value);
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on('getStore', (event) => {
|
||||||
|
event.returnValue = cfg.store
|
||||||
|
})
|
||||||
|
|
||||||
|
electron.ipcMain.on('setStore', (event, store) => {
|
||||||
|
cfg.store = store
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
612
src/main/base/win.ts
Normal file
|
@ -0,0 +1,612 @@
|
||||||
|
// @ts-nocheck
|
||||||
|
import * as path from "path";
|
||||||
|
import * as electron from "electron";
|
||||||
|
import * as windowStateKeeper from "electron-window-state";
|
||||||
|
import * as express from "express";
|
||||||
|
import * as getPort from "get-port";
|
||||||
|
import * as yt from "youtube-search-without-api-key";
|
||||||
|
import * as fs from "fs";
|
||||||
|
import { Stream } from "stream";
|
||||||
|
import * as qrcode from "qrcode-terminal";
|
||||||
|
import * as os from "os";
|
||||||
|
import * as mm from 'music-metadata';
|
||||||
|
import fetch from 'electron-fetch'
|
||||||
|
import {wsapi} from "./wsapi";
|
||||||
|
import * as jsonc from "jsonc";
|
||||||
|
|
||||||
|
export class Win {
|
||||||
|
private win: any | undefined = null;
|
||||||
|
private app: any | undefined = null;
|
||||||
|
private store: any | undefined = null;
|
||||||
|
private devMode: boolean = !electron.app.isPackaged;
|
||||||
|
|
||||||
|
constructor(app: electron.App, store: any) {
|
||||||
|
this.app = app;
|
||||||
|
this.store = store;
|
||||||
|
}
|
||||||
|
|
||||||
|
private paths: any = {
|
||||||
|
srcPath: path.join(__dirname, "../../src"),
|
||||||
|
resourcePath: path.join(__dirname, "../../resources"),
|
||||||
|
ciderCache: path.resolve(electron.app.getPath("userData"), "CiderCache"),
|
||||||
|
themes: path.resolve(electron.app.getPath("userData"), "Themes"),
|
||||||
|
plugins: path.resolve(electron.app.getPath("userData"), "Plugins"),
|
||||||
|
};
|
||||||
|
private audioStream: any = new Stream.PassThrough();
|
||||||
|
private clientPort: number = 0;
|
||||||
|
private remotePort: number = 6942;
|
||||||
|
private EnvironmentVariables: object = {
|
||||||
|
env: {
|
||||||
|
platform: process.platform,
|
||||||
|
dev: electron.app.isPackaged,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
private options: any = {
|
||||||
|
icon: path.join(
|
||||||
|
this.paths.resourcePath,
|
||||||
|
`icons/icon.` + (process.platform === "win32" ? "ico" : "png")
|
||||||
|
),
|
||||||
|
width: 1024,
|
||||||
|
height: 600,
|
||||||
|
x: undefined,
|
||||||
|
y: undefined,
|
||||||
|
minWidth: 900,
|
||||||
|
minHeight: 390,
|
||||||
|
frame: false,
|
||||||
|
title: "Cider",
|
||||||
|
vibrancy: "dark",
|
||||||
|
transparent: process.platform === "darwin",
|
||||||
|
hasShadow: false,
|
||||||
|
show: false,
|
||||||
|
backgroundColor: "#1E1E1E",
|
||||||
|
webPreferences: {
|
||||||
|
nodeIntegration: true,
|
||||||
|
sandbox: true,
|
||||||
|
allowRunningInsecureContent: true,
|
||||||
|
contextIsolation: false,
|
||||||
|
webviewTag: true,
|
||||||
|
plugins: true,
|
||||||
|
nodeIntegrationInWorker: false,
|
||||||
|
webSecurity: false,
|
||||||
|
preload: path.join(this.paths.srcPath, "./preload/cider-preload.js"),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the browser window
|
||||||
|
*/
|
||||||
|
async createWindow(): Promise<Electron.BrowserWindow> {
|
||||||
|
this.clientPort = await getPort({ port: 9000 });
|
||||||
|
this.verifyFiles();
|
||||||
|
|
||||||
|
// Load the previous state with fallback to defaults
|
||||||
|
const windowState = windowStateKeeper({
|
||||||
|
defaultWidth: 1024,
|
||||||
|
defaultHeight: 600,
|
||||||
|
});
|
||||||
|
this.options.width = windowState.width;
|
||||||
|
this.options.height = windowState.height;
|
||||||
|
|
||||||
|
// Start the webserver for the browser window to load
|
||||||
|
|
||||||
|
this.startWebServer();
|
||||||
|
|
||||||
|
this.win = new electron.BrowserWindow(this.options);
|
||||||
|
const ws = new wsapi(this.win)
|
||||||
|
ws.InitWebSockets()
|
||||||
|
// and load the renderer.
|
||||||
|
this.startSession();
|
||||||
|
this.startHandlers();
|
||||||
|
|
||||||
|
// Register listeners on Window to track size and position of the Window.
|
||||||
|
windowState.manage(this.win);
|
||||||
|
|
||||||
|
return this.win;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies the files for the renderer to use (Cache, library info, etc.)
|
||||||
|
*/
|
||||||
|
private verifyFiles(): void {
|
||||||
|
const expectedDirectories = ["CiderCache"];
|
||||||
|
const expectedFiles = [
|
||||||
|
"library-songs.json",
|
||||||
|
"library-artists.json",
|
||||||
|
"library-albums.json",
|
||||||
|
"library-playlists.json",
|
||||||
|
"library-recentlyAdded.json",
|
||||||
|
];
|
||||||
|
for (let i = 0; i < expectedDirectories.length; i++) {
|
||||||
|
if (
|
||||||
|
!fs.existsSync(
|
||||||
|
path.join(electron.app.getPath("userData"), expectedDirectories[i])
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
fs.mkdirSync(
|
||||||
|
path.join(electron.app.getPath("userData"), expectedDirectories[i])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let i = 0; i < expectedFiles.length; i++) {
|
||||||
|
const file = path.join(this.paths.ciderCache, expectedFiles[i]);
|
||||||
|
if (!fs.existsSync(file)) {
|
||||||
|
fs.writeFileSync(file, JSON.stringify([]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the webserver for the renderer process.
|
||||||
|
*/
|
||||||
|
private startWebServer(): void {
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
app.use(express.static(path.join(this.paths.srcPath, "./renderer/")));
|
||||||
|
app.set("views", path.join(this.paths.srcPath, "./renderer/views"));
|
||||||
|
app.set("view engine", "ejs");
|
||||||
|
let firstRequest = true;
|
||||||
|
app.use((req, res, next) => {
|
||||||
|
// @ts-ignore
|
||||||
|
if (
|
||||||
|
req.url.includes("audio.webm") ||
|
||||||
|
(req.headers.host.includes("localhost") &&
|
||||||
|
(this.devMode || req.headers["user-agent"].includes("Electron")))
|
||||||
|
) {
|
||||||
|
next();
|
||||||
|
} else {
|
||||||
|
res.redirect("https://discord.gg/applemusic");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get("/", (req, res) => {
|
||||||
|
res.render("main", this.EnvironmentVariables);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get("/audio.webm", (req, res) => {
|
||||||
|
try {
|
||||||
|
req.socket.setTimeout(Number.MAX_SAFE_INTEGER);
|
||||||
|
// CiderBase.requests.push({req: req, res: res});
|
||||||
|
// var pos = CiderBase.requests.length - 1;
|
||||||
|
// req.on("close", () => {
|
||||||
|
// console.info("CLOSED", CiderBase.requests.length);
|
||||||
|
// requests.splice(pos, 1);
|
||||||
|
// console.info("CLOSED", CiderBase.requests.length);
|
||||||
|
// });
|
||||||
|
this.audioStream.on("data", (data: any) => {
|
||||||
|
try {
|
||||||
|
res.write(data);
|
||||||
|
} catch (ex) {
|
||||||
|
console.log(ex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (ex) {
|
||||||
|
console.log(ex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
//app.use(express.static())
|
||||||
|
|
||||||
|
app.listen(this.clientPort, () => {
|
||||||
|
console.log(`Cider client port: ${this.clientPort}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Remote Client (I had no idea how to add it to our existing express server, so I just made another one) -@quacksire
|
||||||
|
* TODO: Broadcast the remote so that /web-remote/ can connect
|
||||||
|
* https://github.com/ciderapp/Apple-Music-Electron/blob/818ed18940ff600d76eb59d22016723a75885cd5/resources/functions/handler.js#L1173
|
||||||
|
*/
|
||||||
|
const remote = express();
|
||||||
|
remote.use(express.static(path.join(this.paths.srcPath, "./web-remote/")))
|
||||||
|
remote.set("views", path.join(this.paths.srcPath, "./web-remote/views"));
|
||||||
|
remote.set("view engine", "ejs");
|
||||||
|
getPort({port: 6942}).then((port) => {
|
||||||
|
this.remotePort = port;
|
||||||
|
// Start Remote Discovery
|
||||||
|
this.broadcastRemote()
|
||||||
|
remote.listen(this.remotePort, () => {
|
||||||
|
console.log(`Cider remote port: ${this.remotePort}`);
|
||||||
|
if (firstRequest) {
|
||||||
|
console.log("---- Ignore Me ;) ---");
|
||||||
|
qrcode.generate(`http://${os.hostname}:${this.remotePort}`);
|
||||||
|
console.log("---- Ignore Me ;) ---");
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* USING https://www.npmjs.com/package/qrcode-terminal for terminal
|
||||||
|
* WE SHOULD USE https://www.npmjs.com/package/qrcode for the remote (or others) for showing to user via an in-app dialog
|
||||||
|
* -@quacksire
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
firstRequest = false;
|
||||||
|
})
|
||||||
|
remote.get("/", (req, res) => {
|
||||||
|
res.render("index", this.EnvironmentVariables);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the session for the renderer process.
|
||||||
|
*/
|
||||||
|
private startSession(): void {
|
||||||
|
// intercept "https://js-cdn.music.apple.com/hls.js/2.141.0/hls.js/hls.js" and redirect to local file "./apple-hls.js" instead
|
||||||
|
this.win.webContents.session.webRequest.onBeforeRequest(
|
||||||
|
{
|
||||||
|
urls: ["https://*/*.js"],
|
||||||
|
},
|
||||||
|
(
|
||||||
|
details: { url: string | string[] },
|
||||||
|
callback: (arg0: { redirectURL?: string; cancel?: boolean }) => void
|
||||||
|
) => {
|
||||||
|
if (details.url.includes("hls.js")) {
|
||||||
|
callback({
|
||||||
|
redirectURL: `http://localhost:${this.clientPort}/apple-hls.js`,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
callback({
|
||||||
|
cancel: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.win.webContents.session.webRequest.onBeforeSendHeaders(
|
||||||
|
async (
|
||||||
|
details: { url: string; requestHeaders: { [x: string]: string } },
|
||||||
|
callback: (arg0: { requestHeaders: any }) => void
|
||||||
|
) => {
|
||||||
|
if (details.url === "https://buy.itunes.apple.com/account/web/info") {
|
||||||
|
details.requestHeaders["sec-fetch-site"] = "same-site";
|
||||||
|
details.requestHeaders["DNT"] = "1";
|
||||||
|
let itspod = await this.win.webContents.executeJavaScript(
|
||||||
|
`window.localStorage.getItem("music.ampwebplay.itspod")`
|
||||||
|
);
|
||||||
|
if (itspod != null)
|
||||||
|
details.requestHeaders["Cookie"] = `itspod=${itspod}`;
|
||||||
|
}
|
||||||
|
callback({ requestHeaders: details.requestHeaders });
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let location = `http://localhost:${this.clientPort}/`;
|
||||||
|
|
||||||
|
if (electron.app.isPackaged) {
|
||||||
|
this.win.loadURL(location);
|
||||||
|
} else {
|
||||||
|
this.win.loadURL(location, {
|
||||||
|
userAgent: "Cider Development Environment",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the window handlers
|
||||||
|
*/
|
||||||
|
private startHandlers(): void {
|
||||||
|
/**********************************************************************************************************************
|
||||||
|
* ipcMain Events
|
||||||
|
****************************************************************************************************************** */
|
||||||
|
electron.ipcMain.on("cider-platform", (event) => {
|
||||||
|
event.returnValue = process.platform;
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on("get-i18n", (event, key) => {
|
||||||
|
let i18nBase = fs.readFileSync(path.join(__dirname, "../../src/i18n/en_US.jsonc"), "utf8");
|
||||||
|
i18nBase = jsonc.parse(i18nBase)
|
||||||
|
try {
|
||||||
|
let i18n = fs.readFileSync(path.join(__dirname, `../../src/i18n/${key}.jsonc`), "utf8");
|
||||||
|
i18n = jsonc.parse(i18n)
|
||||||
|
Object.assign(i18nBase, i18n)
|
||||||
|
}catch(e) {
|
||||||
|
console.error(e);
|
||||||
|
event.returnValue = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.returnValue = i18nBase;
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on("get-i18n-listing", event => {
|
||||||
|
let i18nFiles = fs.readdirSync(path.join(__dirname, "../../src/i18n")).filter(file => file.endsWith(".jsonc"));
|
||||||
|
// read all the files and parse them
|
||||||
|
let i18nListing = []
|
||||||
|
for (let i = 0; i < i18nFiles.length; i++) {
|
||||||
|
let i18n = fs.readFileSync(path.join(__dirname, `../../src/i18n/${i18nFiles[i]}`), "utf8");
|
||||||
|
i18n = jsonc.parse(i18n)
|
||||||
|
i18nListing.push({
|
||||||
|
"code": i18nFiles[i].replace(".jsonc", ""),
|
||||||
|
"nameNative": i18n["i18n.languageName"] ?? i18nFiles[i].replace(".jsonc", ""),
|
||||||
|
"nameEnglish": i18n["i18n.languageNameEnglish"] ?? i18nFiles[i].replace(".jsonc", ""),
|
||||||
|
"category": i18n["i18n.category"] ?? "",
|
||||||
|
"authors": i18n["i18n.authors"] ?? ""
|
||||||
|
})
|
||||||
|
}
|
||||||
|
event.returnValue = i18nListing;
|
||||||
|
})
|
||||||
|
|
||||||
|
electron.ipcMain.on("get-gpu-mode", (event) => {
|
||||||
|
event.returnValue = process.platform;
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on("is-dev", (event) => {
|
||||||
|
event.returnValue = this.devMode;
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on("close", () => {
|
||||||
|
// listen for close event
|
||||||
|
this.win.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on("put-library-songs", (event, arg) => {
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(this.paths.ciderCache, "library-songs.json"),
|
||||||
|
JSON.stringify(arg)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on("put-library-artists", (event, arg) => {
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(this.paths.ciderCache, "library-artists.json"),
|
||||||
|
JSON.stringify(arg)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on("put-library-albums", (event, arg) => {
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(this.paths.ciderCache, "library-albums.json"),
|
||||||
|
JSON.stringify(arg)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on("put-library-playlists", (event, arg) => {
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(this.paths.ciderCache, "library-playlists.json"),
|
||||||
|
JSON.stringify(arg)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on("put-library-recentlyAdded", (event, arg) => {
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(this.paths.ciderCache, "library-recentlyAdded.json"),
|
||||||
|
JSON.stringify(arg)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on("get-library-songs", (event) => {
|
||||||
|
let librarySongs = fs.readFileSync(
|
||||||
|
path.join(this.paths.ciderCache, "library-songs.json"),
|
||||||
|
"utf8"
|
||||||
|
);
|
||||||
|
event.returnValue = JSON.parse(librarySongs);
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on("get-library-artists", (event) => {
|
||||||
|
let libraryArtists = fs.readFileSync(
|
||||||
|
path.join(this.paths.ciderCache, "library-artists.json"),
|
||||||
|
"utf8"
|
||||||
|
);
|
||||||
|
event.returnValue = JSON.parse(libraryArtists);
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on("get-library-albums", (event) => {
|
||||||
|
let libraryAlbums = fs.readFileSync(
|
||||||
|
path.join(this.paths.ciderCache, "library-albums.json"),
|
||||||
|
"utf8"
|
||||||
|
);
|
||||||
|
event.returnValue = JSON.parse(libraryAlbums);
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on("get-library-playlists", (event) => {
|
||||||
|
let libraryPlaylists = fs.readFileSync(
|
||||||
|
path.join(this.paths.ciderCache, "library-playlists.json"),
|
||||||
|
"utf8"
|
||||||
|
);
|
||||||
|
event.returnValue = JSON.parse(libraryPlaylists);
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on("get-library-recentlyAdded", (event) => {
|
||||||
|
let libraryRecentlyAdded = fs.readFileSync(
|
||||||
|
path.join(this.paths.ciderCache, "library-recentlyAdded.json"),
|
||||||
|
"utf8"
|
||||||
|
);
|
||||||
|
event.returnValue = JSON.parse(libraryRecentlyAdded);
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.handle("getYTLyrics", async (event, track, artist) => {
|
||||||
|
const u = track + " " + artist + " official video";
|
||||||
|
return await yt.search(u);
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.handle("setVibrancy", (event, key, value) => {
|
||||||
|
this.win.setVibrancy(value);
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on("maximize", () => {
|
||||||
|
// listen for maximize event
|
||||||
|
if (this.win.isMaximized()) {
|
||||||
|
this.win.unmaximize();
|
||||||
|
} else {
|
||||||
|
this.win.maximize();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
electron.ipcMain.on("unmaximize", () => {
|
||||||
|
// listen for maximize event
|
||||||
|
this.win.unmaximize();
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on("minimize", () => {
|
||||||
|
// listen for minimize event
|
||||||
|
this.win.minimize();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set scale
|
||||||
|
electron.ipcMain.on("setScreenScale", (event, scale) => {
|
||||||
|
this.win.webContents.setZoomFactor(parseFloat(scale));
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on("windowmin", (event, width, height) => {
|
||||||
|
this.win.setMinimumSize(width,height);
|
||||||
|
})
|
||||||
|
|
||||||
|
electron.ipcMain.on("windowontop", (event, ontop) => {
|
||||||
|
this.win.setAlwaysOnTop(ontop);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set scale
|
||||||
|
electron.ipcMain.on("windowresize", (event, width, height, lock = false) => {
|
||||||
|
this.win.setContentSize(width, height);
|
||||||
|
this.win.setResizable(!lock);
|
||||||
|
});
|
||||||
|
|
||||||
|
//Fullscreen
|
||||||
|
electron.ipcMain.on('setFullScreen', (event, flag) => {
|
||||||
|
this.win.setFullScreen(flag)
|
||||||
|
})
|
||||||
|
//Fullscreen
|
||||||
|
electron.ipcMain.on('detachDT', (event, _) => {
|
||||||
|
this.win.webContents.openDevTools({ mode: 'detach' });
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
electron.ipcMain.on('play', (event, type, id) => {
|
||||||
|
this.win.webContents.executeJavaScript(`
|
||||||
|
MusicKit.getInstance().setQueue({ ${type}: '${id}'}).then(function(queue) {
|
||||||
|
MusicKit.getInstance().play();
|
||||||
|
});
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
function getIp() {
|
||||||
|
let ip = false;
|
||||||
|
let alias = 0;
|
||||||
|
let ifaces = os.networkInterfaces();
|
||||||
|
for (var dev in ifaces) {
|
||||||
|
ifaces[dev].forEach(details => {
|
||||||
|
if (details.family === 'IPv4') {
|
||||||
|
if (!/(loopback|vmware|internal|hamachi|vboxnet|virtualbox)/gi.test(dev + (alias ? ':' + alias : ''))) {
|
||||||
|
if (details.address.substring(0, 8) === '192.168.' ||
|
||||||
|
details.address.substring(0, 7) === '172.16.' ||
|
||||||
|
details.address.substring(0, 3) === '10.'
|
||||||
|
) {
|
||||||
|
ip = details.address;
|
||||||
|
++alias;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
//QR Code
|
||||||
|
electron.ipcMain.handle('showQR', async (event , _) => {
|
||||||
|
let url = `http://${getIp()}:${this.remotePort}`;
|
||||||
|
electron.shell.openExternal(`https://cider.sh/pair-remote?url=${btoa(encodeURI(url))}`);
|
||||||
|
/*
|
||||||
|
* Doing this because we can give them the link and let them send it via Pocket or another in-browser tool -q
|
||||||
|
*/
|
||||||
|
})
|
||||||
|
|
||||||
|
// Get previews for normalization
|
||||||
|
electron.ipcMain.on("getPreviewURL", (_event, url) => {
|
||||||
|
'get url'
|
||||||
|
fetch(url)
|
||||||
|
.then(res => res.buffer())
|
||||||
|
.then(async(buffer) => {
|
||||||
|
try {
|
||||||
|
const metadata = await mm.parseBuffer(buffer, 'audio/x-m4a');
|
||||||
|
let SoundCheckTag = metadata.native.iTunes[1].value
|
||||||
|
console.log('sc',SoundCheckTag)
|
||||||
|
this.win.webContents.send('SoundCheckTag', SoundCheckTag)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error.message);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
/* *********************************************************************************************
|
||||||
|
* Window Events
|
||||||
|
* **********************************************************************************************/
|
||||||
|
if (process.platform === "win32") {
|
||||||
|
let WND_STATE = {
|
||||||
|
MINIMIZED: 0,
|
||||||
|
NORMAL: 1,
|
||||||
|
MAXIMIZED: 2,
|
||||||
|
FULL_SCREEN: 3,
|
||||||
|
};
|
||||||
|
let wndState = WND_STATE.NORMAL;
|
||||||
|
|
||||||
|
this.win.on("resize", (_: any) => {
|
||||||
|
const isMaximized = this.win.isMaximized();
|
||||||
|
const isMinimized = this.win.isMinimized();
|
||||||
|
const isFullScreen = this.win.isFullScreen();
|
||||||
|
const state = wndState;
|
||||||
|
if (isMinimized && state !== WND_STATE.MINIMIZED) {
|
||||||
|
wndState = WND_STATE.MINIMIZED;
|
||||||
|
} else if (isFullScreen && state !== WND_STATE.FULL_SCREEN) {
|
||||||
|
wndState = WND_STATE.FULL_SCREEN;
|
||||||
|
} else if (isMaximized && state !== WND_STATE.MAXIMIZED) {
|
||||||
|
wndState = WND_STATE.MAXIMIZED;
|
||||||
|
this.win.webContents.executeJavaScript(`app.chrome.maximized = true`);
|
||||||
|
} else if (state !== WND_STATE.NORMAL) {
|
||||||
|
wndState = WND_STATE.NORMAL;
|
||||||
|
this.win.webContents.executeJavaScript(
|
||||||
|
`app.chrome.maximized = false`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.win.on("closed", () => {
|
||||||
|
this.win = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set window Handler
|
||||||
|
this.win.webContents.setWindowOpenHandler((x: any) => {
|
||||||
|
if (x.url.includes("apple") || x.url.includes("localhost")) {
|
||||||
|
return { action: "allow" };
|
||||||
|
}
|
||||||
|
electron.shell.openExternal(x.url).catch(console.error);
|
||||||
|
return { action: "deny" };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
private async broadcastRemote() {
|
||||||
|
function getIp() {
|
||||||
|
let ip :any = false;
|
||||||
|
let alias = 0;
|
||||||
|
const ifaces: any = os.networkInterfaces() ;
|
||||||
|
for (var dev in ifaces) {
|
||||||
|
ifaces[dev].forEach( (details: any) => {
|
||||||
|
if (details.family === 'IPv4') {
|
||||||
|
if (!/(loopback|vmware|internal|hamachi|vboxnet|virtualbox)/gi.test(dev + (alias ? ':' + alias : ''))) {
|
||||||
|
if (details.address.substring(0, 8) === '192.168.' ||
|
||||||
|
details.address.substring(0, 7) === '172.16.' ||
|
||||||
|
details.address.substring(0, 3) === '10.'
|
||||||
|
) {
|
||||||
|
ip = details.address;
|
||||||
|
++alias;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) ;
|
||||||
|
}
|
||||||
|
return ip;
|
||||||
|
}
|
||||||
|
const myString = `http://${getIp()}:${this.remotePort}`;
|
||||||
|
let mdns = require('mdns-js');
|
||||||
|
const encoded = new Buffer(myString).toString('base64');
|
||||||
|
var x = mdns.tcp('cider-remote');
|
||||||
|
var txt_record = {
|
||||||
|
"Ver": "131077",
|
||||||
|
'DvSv': '3689',
|
||||||
|
'DbId': 'D41D8CD98F00B205',
|
||||||
|
'DvTy': 'Cider',
|
||||||
|
'OSsi': '0x212F0',
|
||||||
|
'txtvers': '1',
|
||||||
|
"CtlN": "Cider",
|
||||||
|
"iV": "196623"
|
||||||
|
}
|
||||||
|
let server2 = mdns.createAdvertisement(x, `${await getPort({port: 3839})}`, { name: encoded, txt: txt_record });
|
||||||
|
server2.start();
|
||||||
|
console.log('remote broadcasted')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
293
src/main/base/wsapi.ts
Normal file
|
@ -0,0 +1,293 @@
|
||||||
|
import * as ws from "ws";
|
||||||
|
import * as http from "http";
|
||||||
|
import * as https from "https";
|
||||||
|
import * as url from "url";
|
||||||
|
import * as fs from "fs";
|
||||||
|
import * as path from "path";
|
||||||
|
import * as electron from "electron";
|
||||||
|
const WebSocket = ws;
|
||||||
|
const WebSocketServer = ws.Server;
|
||||||
|
|
||||||
|
interface standardResponse {
|
||||||
|
status?: Number,
|
||||||
|
message?: String,
|
||||||
|
data?: any,
|
||||||
|
type?: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class wsapi {
|
||||||
|
static clients: any;
|
||||||
|
port: any = 26369
|
||||||
|
wss: any = null
|
||||||
|
clients: any = []
|
||||||
|
private _win : any;
|
||||||
|
constructor(win : any) {
|
||||||
|
this._win = win;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
createId() {
|
||||||
|
// create random guid
|
||||||
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||||
|
var r = Math.random() * 16 | 0,
|
||||||
|
v = c == 'x' ? r : (r & 0x3 | 0x8);
|
||||||
|
return v.toString(16);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
public async InitWebSockets () {
|
||||||
|
electron.ipcMain.on('wsapi-updatePlaybackState', (event :any, arg :any) => {
|
||||||
|
this.updatePlaybackState(arg);
|
||||||
|
})
|
||||||
|
|
||||||
|
electron.ipcMain.on('wsapi-returnQueue', (event :any, arg :any) => {
|
||||||
|
this.returnQueue(JSON.parse(arg));
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on('wsapi-returnSearch', (event :any, arg :any) => {
|
||||||
|
console.log("SEARCH")
|
||||||
|
this.returnSearch(JSON.parse(arg));
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on('wsapi-returnSearchLibrary', (event :any, arg :any) => {
|
||||||
|
this.returnSearchLibrary(JSON.parse(arg));
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on('wsapi-returnDynamic', (event :any, arg :any, type :any) => {
|
||||||
|
this.returnDynamic(JSON.parse(arg), type);
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on('wsapi-returnMusicKitApi', (event :any, arg :any, method :any) => {
|
||||||
|
this.returnMusicKitApi(JSON.parse(arg), method);
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on('wsapi-returnLyrics', (event :any, arg :any) => {
|
||||||
|
this.returnLyrics(JSON.parse(arg));
|
||||||
|
});
|
||||||
|
this.wss = new WebSocketServer({
|
||||||
|
port: this.port,
|
||||||
|
perMessageDeflate: {
|
||||||
|
zlibDeflateOptions: {
|
||||||
|
// See zlib defaults.
|
||||||
|
chunkSize: 1024,
|
||||||
|
memLevel: 7,
|
||||||
|
level: 3
|
||||||
|
},
|
||||||
|
zlibInflateOptions: {
|
||||||
|
chunkSize: 10 * 1024
|
||||||
|
},
|
||||||
|
// Other options settable:
|
||||||
|
clientNoContextTakeover: true, // Defaults to negotiated value.
|
||||||
|
serverNoContextTakeover: true, // Defaults to negotiated value.
|
||||||
|
serverMaxWindowBits: 10, // Defaults to negotiated value.
|
||||||
|
// Below options specified as default values.
|
||||||
|
concurrencyLimit: 10, // Limits zlib concurrency for perf.
|
||||||
|
threshold: 1024 // Size (in bytes) below which messages
|
||||||
|
// should not be compressed if context takeover is disabled.
|
||||||
|
}
|
||||||
|
})
|
||||||
|
console.log(`WebSocketServer started on port: ${this.port}`);
|
||||||
|
|
||||||
|
const defaultResponse :standardResponse = {status :0, data:{}, message:"OK", type:"generic"};
|
||||||
|
|
||||||
|
|
||||||
|
this.wss.on('connection', (ws : any) => {
|
||||||
|
ws.id = this.createId();
|
||||||
|
console.log(`Client ${ws.id} connected`)
|
||||||
|
this.clients.push(ws);
|
||||||
|
ws.on('message', function incoming(message : any) {
|
||||||
|
|
||||||
|
});
|
||||||
|
// ws on message
|
||||||
|
ws.on('message', (message : any) => {
|
||||||
|
let data = JSON.parse(message);
|
||||||
|
let response :standardResponse = {status :0, data:{}, message:"OK", type:"generic"};
|
||||||
|
if (data.action) {
|
||||||
|
data.action.toLowerCase();
|
||||||
|
}
|
||||||
|
switch (data.action) {
|
||||||
|
default:
|
||||||
|
response.message = "Action not found";
|
||||||
|
break;
|
||||||
|
case "identify":
|
||||||
|
response.message = "Thanks for identifying!"
|
||||||
|
response.data = {
|
||||||
|
id: ws.id
|
||||||
|
}
|
||||||
|
ws.identity = {
|
||||||
|
name: data.name,
|
||||||
|
author: data.author,
|
||||||
|
description: data.description,
|
||||||
|
version: data.version
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "play-next":
|
||||||
|
this._win.webContents.executeJavaScript(`wsapi.playNext(\`${data.type}\`,\`${data.id}\`)`);
|
||||||
|
response.message = "Play Next";
|
||||||
|
break;
|
||||||
|
case "play-later":
|
||||||
|
this._win.webContents.executeJavaScript(`wsapi.playLater(\`${data.type}\`,\`${data.id}\`)`);
|
||||||
|
response.message = "Play Later";
|
||||||
|
break;
|
||||||
|
case "quick-play":
|
||||||
|
this._win.webContents.executeJavaScript(`wsapi.quickPlay(\`${data.term}\`)`);
|
||||||
|
response.message = "Quick Play";
|
||||||
|
break;
|
||||||
|
case "get-lyrics":
|
||||||
|
this._win.webContents.executeJavaScript(`wsapi.getLyrics()`);
|
||||||
|
break;
|
||||||
|
case "shuffle":
|
||||||
|
this._win.webContents.executeJavaScript(`wsapi.toggleShuffle()`);
|
||||||
|
break;
|
||||||
|
case "set-shuffle":
|
||||||
|
if(data.shuffle == true) {
|
||||||
|
this._win.webContents.executeJavaScript(`MusicKit.getInstance().shuffleMode = 1`);
|
||||||
|
}else{
|
||||||
|
this._win.webContents.executeJavaScript(`MusicKit.getInstance().shuffleMode = 0`);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "repeat":
|
||||||
|
this._win.webContents.executeJavaScript(`wsapi.toggleRepeat()`);
|
||||||
|
break;
|
||||||
|
case "seek":
|
||||||
|
this._win.webContents.executeJavaScript(`MusicKit.getInstance().seekToTime(${parseFloat(data.time)})`);
|
||||||
|
response.message = "Seek";
|
||||||
|
break;
|
||||||
|
case "pause":
|
||||||
|
this._win.webContents.executeJavaScript(`MusicKit.getInstance().pause()`);
|
||||||
|
response.message = "Paused";
|
||||||
|
break;
|
||||||
|
case "play":
|
||||||
|
this._win.webContents.executeJavaScript(`MusicKit.getInstance().play()`);
|
||||||
|
response.message = "Playing";
|
||||||
|
break;
|
||||||
|
case "stop":
|
||||||
|
this._win.webContents.executeJavaScript(`MusicKit.getInstance().stop()`);
|
||||||
|
response.message = "Stopped";
|
||||||
|
break;
|
||||||
|
case "volume":
|
||||||
|
this._win.webContents.executeJavaScript(`MusicKit.getInstance().volume = ${parseFloat(data.volume)}`);
|
||||||
|
response.message = "Volume";
|
||||||
|
break;
|
||||||
|
case "mute":
|
||||||
|
this._win.webContents.executeJavaScript(`MusicKit.getInstance().mute()`);
|
||||||
|
response.message = "Muted";
|
||||||
|
break;
|
||||||
|
case "unmute":
|
||||||
|
this._win.webContents.executeJavaScript(`MusicKit.getInstance().unmute()`);
|
||||||
|
response.message = "Unmuted";
|
||||||
|
break;
|
||||||
|
case "next":
|
||||||
|
this._win.webContents.executeJavaScript(`MusicKit.getInstance().skipToNextItem()`);
|
||||||
|
response.message = "Next";
|
||||||
|
break;
|
||||||
|
case "previous":
|
||||||
|
this._win.webContents.executeJavaScript(`MusicKit.getInstance().skipToPreviousItem()`);
|
||||||
|
response.message = "Previous";
|
||||||
|
break;
|
||||||
|
case "musickit-api":
|
||||||
|
this._win.webContents.executeJavaScript(`wsapi.musickitApi(\`${data.method}\`, \`${data.id}\`, ${JSON.stringify(data.params)} , ${data.library})`);
|
||||||
|
break;
|
||||||
|
case "musickit-library-api":
|
||||||
|
break;
|
||||||
|
case "set-autoplay":
|
||||||
|
this._win.webContents.executeJavaScript(`wsapi.setAutoplay(${data.autoplay})`);
|
||||||
|
break;
|
||||||
|
case "queue-move":
|
||||||
|
this._win.webContents.executeJavaScript(`wsapi.moveQueueItem(${data.from},${data.to})`);
|
||||||
|
break;
|
||||||
|
case "get-queue":
|
||||||
|
this._win.webContents.executeJavaScript(`wsapi.getQueue()`);
|
||||||
|
break;
|
||||||
|
case "search":
|
||||||
|
if (!data.limit) {
|
||||||
|
data.limit = 10;
|
||||||
|
}
|
||||||
|
this._win.webContents.executeJavaScript(`wsapi.search(\`${data.term}\`, \`${data.limit}\`)`);
|
||||||
|
break;
|
||||||
|
case "library-search":
|
||||||
|
if (!data.limit) {
|
||||||
|
data.limit = 10;
|
||||||
|
}
|
||||||
|
this._win.webContents.executeJavaScript(`wsapi.searchLibrary(\`${data.term}\`, \`${data.limit}\`)`);
|
||||||
|
break;
|
||||||
|
case "show-window":
|
||||||
|
this._win.show()
|
||||||
|
break;
|
||||||
|
case "hide-window":
|
||||||
|
this._win.hide()
|
||||||
|
break;
|
||||||
|
case "play-mediaitem":
|
||||||
|
this._win.webContents.executeJavaScript(`wsapi.playTrackById("${data.id}", \`${data.kind}\`)`);
|
||||||
|
response.message = "Playing track";
|
||||||
|
break;
|
||||||
|
case "get-status":
|
||||||
|
response.data = {
|
||||||
|
isAuthorized: true
|
||||||
|
};
|
||||||
|
response.message = "Status";
|
||||||
|
break;
|
||||||
|
case "get-currentmediaitem":
|
||||||
|
this._win.webContents.executeJavaScript(`wsapi.getPlaybackState()`);
|
||||||
|
break;
|
||||||
|
case "quit":
|
||||||
|
electron.app.quit();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ws.send(JSON.stringify(response));
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.on('close', () => {
|
||||||
|
// remove client from list
|
||||||
|
this.clients.splice(wsapi.clients.indexOf(ws), 1);
|
||||||
|
console.log(`Client ${ws.id} disconnected`);
|
||||||
|
});
|
||||||
|
ws.send(JSON.stringify(defaultResponse));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
sendToClient(id : any) {
|
||||||
|
// replace the clients.forEach with a filter to find the client that requested
|
||||||
|
}
|
||||||
|
updatePlaybackState(attr : any) {
|
||||||
|
const response : standardResponse = {status: 0, data: attr, message: "OK", type:"playbackStateUpdate"};
|
||||||
|
this.clients.forEach(function each(client: any) {
|
||||||
|
client.send(JSON.stringify(response));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
returnMusicKitApi(results :any, method :any) {
|
||||||
|
const response : standardResponse = {status :0, data: results, message:"OK", type:`musickitapi.${method}`};
|
||||||
|
this.clients.forEach(function each(client :any) {
|
||||||
|
client.send(JSON.stringify(response));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
returnDynamic(results :any, type :any) {
|
||||||
|
const response : standardResponse = {status :0, data: results, message: "OK", type: type};
|
||||||
|
this.clients.forEach(function each(client :any) {
|
||||||
|
client.send(JSON.stringify(response));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
returnLyrics(results :any) {
|
||||||
|
const response : standardResponse = {status :0, data: results, message: "OK", type: "lyrics"};
|
||||||
|
this.clients.forEach(function each(client :any) {
|
||||||
|
client.send(JSON.stringify(response));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
returnSearch(results :any) {
|
||||||
|
const response : standardResponse = {status :0, data: results, message: "OK", type: "searchResults"};
|
||||||
|
this.clients.forEach(function each(client :any) {
|
||||||
|
client.send(JSON.stringify(response));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
returnSearchLibrary(results :any) {
|
||||||
|
const response: standardResponse = {status :0, data :results, message:"OK", type:"searchResultsLibrary"};
|
||||||
|
this.clients.forEach(function each(client :any) {
|
||||||
|
client.send(JSON.stringify(response));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
returnQueue(queue :any) {
|
||||||
|
const response : standardResponse = {status :0,data :queue, message:"OK", type:"queue"};
|
||||||
|
this.clients.forEach(function each(client :any) {
|
||||||
|
client.send(JSON.stringify(response));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,446 +0,0 @@
|
||||||
const { BrowserWindow, ipcMain, shell, app, screen } = require("electron")
|
|
||||||
const { join } = require("path")
|
|
||||||
const getPort = require("get-port");
|
|
||||||
const express = require("express");
|
|
||||||
const path = require("path");
|
|
||||||
const windowStateKeeper = require("electron-window-state");
|
|
||||||
const os = require('os');
|
|
||||||
const yt = require('youtube-search-without-api-key');
|
|
||||||
const discord = require('./discordrpc');
|
|
||||||
const lastfm = require('./lastfm');
|
|
||||||
const { writeFile, writeFileSync, existsSync, mkdirSync } = require('fs');
|
|
||||||
const fs = require('fs');
|
|
||||||
const mpris = require('./mpris');
|
|
||||||
const mm = require('music-metadata');
|
|
||||||
//const mdns = require('mdns')
|
|
||||||
const qrcode = require('qrcode-terminal')
|
|
||||||
const fetch = require('electron-fetch').default;
|
|
||||||
const { Stream } = require('stream');
|
|
||||||
|
|
||||||
// Analytics for debugging.
|
|
||||||
const ElectronSentry = require("@sentry/electron");
|
|
||||||
ElectronSentry.init({ dsn: "https://68c422bfaaf44dea880b86aad5a820d2@o954055.ingest.sentry.io/6112214" });
|
|
||||||
|
|
||||||
const CiderBase = {
|
|
||||||
win: null,
|
|
||||||
requests: [],
|
|
||||||
audiostream: new Stream.PassThrough(),
|
|
||||||
async Start() {
|
|
||||||
this.clientPort = await getPort({ port: 9000 });
|
|
||||||
this.win = this.CreateBrowserWindow()
|
|
||||||
},
|
|
||||||
clientPort: 0,
|
|
||||||
CreateBrowserWindow() {
|
|
||||||
this.VerifyFiles()
|
|
||||||
// Set default window sizes
|
|
||||||
const mainWindowState = windowStateKeeper({
|
|
||||||
defaultWidth: 1024,
|
|
||||||
defaultHeight: 600
|
|
||||||
});
|
|
||||||
|
|
||||||
let win = null
|
|
||||||
const options = {
|
|
||||||
icon: join(__dirname, `../../resources/icons/icon.` + (process.platform === "win32" ? "ico" : "png")),
|
|
||||||
width: mainWindowState.width,
|
|
||||||
height: mainWindowState.height,
|
|
||||||
x: mainWindowState.x,
|
|
||||||
y: mainWindowState.y,
|
|
||||||
minWidth: 844,
|
|
||||||
minHeight: 410,
|
|
||||||
frame: false,
|
|
||||||
title: "Cider",
|
|
||||||
vibrancy: 'dark',
|
|
||||||
// transparent: true,
|
|
||||||
hasShadow: false,
|
|
||||||
webPreferences: {
|
|
||||||
webviewTag: true,
|
|
||||||
plugins: true,
|
|
||||||
nodeIntegration: true,
|
|
||||||
nodeIntegrationInWorker: false,
|
|
||||||
webSecurity: false,
|
|
||||||
allowRunningInsecureContent: true,
|
|
||||||
enableRemoteModule: true,
|
|
||||||
sandbox: true,
|
|
||||||
nativeWindowOpen: true,
|
|
||||||
contextIsolation: false,
|
|
||||||
preload: join(__dirname, '../preload/cider-preload.js')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CiderBase.InitWebServer()
|
|
||||||
|
|
||||||
// Create the BrowserWindow
|
|
||||||
win = new BrowserWindow(options)
|
|
||||||
|
|
||||||
// intercept "https://js-cdn.music.apple.com/hls.js/2.141.0/hls.js/hls.js" and redirect to local file "./apple-hls.js" instead
|
|
||||||
win.webContents.session.webRequest.onBeforeRequest({
|
|
||||||
urls: ["https://*/*.js"]
|
|
||||||
},
|
|
||||||
(details, callback) => {
|
|
||||||
if (details.url.includes("hls.js")) {
|
|
||||||
callback({
|
|
||||||
redirectURL: `http://localhost:${CiderBase.clientPort}/apple-hls.js`
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
callback({
|
|
||||||
cancel: false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
win.webContents.session.webRequest.onBeforeSendHeaders(async(details, callback) => {
|
|
||||||
if (details.url === "https://buy.itunes.apple.com/account/web/info") {
|
|
||||||
details.requestHeaders['sec-fetch-site'] = 'same-site';
|
|
||||||
details.requestHeaders['DNT'] = '1';
|
|
||||||
let itspod = await win.webContents.executeJavaScript(`window.localStorage.getItem("music.ampwebplay.itspod")`)
|
|
||||||
if (itspod != null)
|
|
||||||
details.requestHeaders['Cookie'] = `itspod=${itspod}`
|
|
||||||
}
|
|
||||||
callback({ requestHeaders: details.requestHeaders })
|
|
||||||
})
|
|
||||||
|
|
||||||
win.webContents.session.webRequest.onHeadersReceived((details, callback) => {
|
|
||||||
if (details.url.match(/^https:\/\/store-\d{3}\.blobstore\.apple\.com/) || details.url.startsWith("https://store-037.blobstore.apple.com")) {
|
|
||||||
details.responseHeaders['Access-Control-Allow-Origin'] = '*';
|
|
||||||
}
|
|
||||||
callback({ responseHeaders: details.responseHeaders })
|
|
||||||
})
|
|
||||||
|
|
||||||
let location = `http://localhost:${CiderBase.clientPort}/`
|
|
||||||
win.loadURL(location)
|
|
||||||
win.on("closed", () => {
|
|
||||||
win = null
|
|
||||||
})
|
|
||||||
|
|
||||||
// Register listeners on Window to track size and position of the Window.
|
|
||||||
mainWindowState.manage(win);
|
|
||||||
|
|
||||||
// IPC stuff (senders)
|
|
||||||
ipcMain.on("cider-platform", (event) => {
|
|
||||||
event.returnValue = process.platform
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.on("get-gpu-mode", (event) => {
|
|
||||||
event.returnValue = process.platform
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.on("is-dev", (event) => {
|
|
||||||
event.returnValue = !app.isPackaged
|
|
||||||
})
|
|
||||||
|
|
||||||
// IPC stuff (listeners)
|
|
||||||
ipcMain.on('close', () => { // listen for close event
|
|
||||||
win.close();
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.on('put-library-songs', (event, arg) => {
|
|
||||||
fs.writeFileSync(join(app.paths.ciderCache, "library-songs.json"), JSON.stringify(arg))
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.on('put-library-artists', (event, arg) => {
|
|
||||||
fs.writeFileSync(join(app.paths.ciderCache, "library-artists.json"), JSON.stringify(arg))
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.on('put-library-albums', (event, arg) => {
|
|
||||||
fs.writeFileSync(join(app.paths.ciderCache, "library-albums.json"), JSON.stringify(arg))
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.on('put-library-playlists', (event, arg) => {
|
|
||||||
fs.writeFileSync(join(app.paths.ciderCache, "library-playlists.json"), JSON.stringify(arg))
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.on('put-library-recentlyAdded', (event, arg) => {
|
|
||||||
fs.writeFileSync(join(app.paths.ciderCache, "library-recentlyAdded.json"), JSON.stringify(arg))
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.on('get-library-songs', (event) => {
|
|
||||||
let librarySongs = fs.readFileSync(join(app.paths.ciderCache, "library-songs.json"), "utf8")
|
|
||||||
event.returnValue = JSON.parse(librarySongs)
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.on('get-library-artists', (event) => {
|
|
||||||
let libraryArtists = fs.readFileSync(join(app.paths.ciderCache, "library-artists.json"), "utf8")
|
|
||||||
event.returnValue = JSON.parse(libraryArtists)
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.on('get-library-albums', (event) => {
|
|
||||||
let libraryAlbums = fs.readFileSync(join(app.paths.ciderCache, "library-albums.json"), "utf8")
|
|
||||||
event.returnValue = JSON.parse(libraryAlbums)
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.on('get-library-playlists', (event) => {
|
|
||||||
let libraryPlaylists = fs.readFileSync(join(app.paths.ciderCache, "library-playlists.json"), "utf8")
|
|
||||||
event.returnValue = JSON.parse(libraryPlaylists)
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.on('get-library-recentlyAdded', (event) => {
|
|
||||||
let libraryRecentlyAdded = fs.readFileSync(join(app.paths.ciderCache, "library-recentlyAdded.json"), "utf8")
|
|
||||||
event.returnValue = JSON.parse(libraryRecentlyAdded)
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.handle('getYTLyrics', async(event, track, artist) => {
|
|
||||||
var u = track + " " + artist + " official video";
|
|
||||||
const videos = await yt.search(u);
|
|
||||||
return videos
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.handle('getStoreValue', (event, key, defaultValue) => {
|
|
||||||
return (defaultValue ? app.cfg.get(key, true) : app.cfg.get(key));
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.handle('setStoreValue', (event, key, value) => {
|
|
||||||
app.cfg.set(key, value);
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.on('getStore', (event) => {
|
|
||||||
event.returnValue = app.cfg.store
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.on('setStore', (event, store) => {
|
|
||||||
app.cfg.store = store
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.handle('setVibrancy', (event, key, value) => {
|
|
||||||
win.setVibrancy(value)
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.on('maximize', () => { // listen for maximize event
|
|
||||||
if (win.isMaximized()) {
|
|
||||||
win.unmaximize()
|
|
||||||
} else {
|
|
||||||
win.maximize()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.on('minimize', () => { // listen for minimize event
|
|
||||||
win.minimize();
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.on('setFullScreen', (event, flag) => {
|
|
||||||
win.setFullScreen(flag)
|
|
||||||
})
|
|
||||||
|
|
||||||
if (process.platform === "win32") {
|
|
||||||
let WND_STATE = {
|
|
||||||
MINIMIZED: 0,
|
|
||||||
NORMAL: 1,
|
|
||||||
MAXIMIZED: 2,
|
|
||||||
FULL_SCREEN: 3
|
|
||||||
}
|
|
||||||
let wndState = WND_STATE.NORMAL
|
|
||||||
|
|
||||||
win.on("resize", (_event) => {
|
|
||||||
const isMaximized = win.isMaximized()
|
|
||||||
const isMinimized = win.isMinimized()
|
|
||||||
const isFullScreen = win.isFullScreen()
|
|
||||||
const state = wndState;
|
|
||||||
if (isMinimized && state !== WND_STATE.MINIMIZED) {
|
|
||||||
wndState = WND_STATE.MINIMIZED
|
|
||||||
} else if (isFullScreen && state !== WND_STATE.FULL_SCREEN) {
|
|
||||||
wndState = WND_STATE.FULL_SCREEN
|
|
||||||
} else if (isMaximized && state !== WND_STATE.MAXIMIZED) {
|
|
||||||
wndState = WND_STATE.MAXIMIZED
|
|
||||||
win.webContents.executeJavaScript(`app.chrome.maximized = true`)
|
|
||||||
} else if (state !== WND_STATE.NORMAL) {
|
|
||||||
wndState = WND_STATE.NORMAL
|
|
||||||
win.webContents.executeJavaScript(`app.chrome.maximized = false`)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set window Handler
|
|
||||||
win.webContents.setWindowOpenHandler(({ url }) => {
|
|
||||||
if (url.includes("apple") || url.includes("localhost")) {
|
|
||||||
return { action: "allow" }
|
|
||||||
}
|
|
||||||
shell.openExternal(url).catch(() => {})
|
|
||||||
return {
|
|
||||||
action: 'deny'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Set scale
|
|
||||||
ipcMain.on('setScreenScale', (event, scale) => {
|
|
||||||
win.webContents.setZoomFactor(parseFloat(scale))
|
|
||||||
})
|
|
||||||
|
|
||||||
win.webContents.setZoomFactor(screen.getPrimaryDisplay().scaleFactor)
|
|
||||||
|
|
||||||
mpris.connect(win)
|
|
||||||
mpris.SetButtons(win, false)
|
|
||||||
|
|
||||||
lastfm.authenticate()
|
|
||||||
// Discord
|
|
||||||
discord.connect((app.cfg.get("general.discord_rpc") == 1) ? '911790844204437504' : '886578863147192350');
|
|
||||||
ipcMain.on('playbackStateDidChange', (_event, a) => {
|
|
||||||
app.media = a;
|
|
||||||
discord.updateActivity(a)
|
|
||||||
mpris.SetButtons(win, a)
|
|
||||||
mpris.updateState(a)
|
|
||||||
lastfm.scrobbleSong(a)
|
|
||||||
lastfm.updateNowPlayingSong(a)
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.on('nowPlayingItemDidChange', (_event, a) => {
|
|
||||||
app.media = a;
|
|
||||||
discord.updateActivity(a)
|
|
||||||
mpris.SetButtons(win, a)
|
|
||||||
mpris.updateAttributes(a)
|
|
||||||
lastfm.scrobbleSong(a)
|
|
||||||
lastfm.updateNowPlayingSong(a)
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.on("getPreviewURL", (_event, url) => {
|
|
||||||
fetch(url)
|
|
||||||
.then(res => res.buffer())
|
|
||||||
.then(async(buffer) => {
|
|
||||||
try {
|
|
||||||
const metadata = await mm.parseBuffer(buffer, 'audio/x-m4a');
|
|
||||||
SoundCheckTag = metadata.native.iTunes[1].value
|
|
||||||
win.webContents.send('SoundCheckTag', SoundCheckTag)
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error.message);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.on('writeAudio', function(event, buffer) {
|
|
||||||
CiderBase.audiostream.write(Buffer.from(buffer));
|
|
||||||
})
|
|
||||||
|
|
||||||
return win
|
|
||||||
},
|
|
||||||
VerifyFiles() {
|
|
||||||
const expectedDirectories = [
|
|
||||||
"CiderCache"
|
|
||||||
]
|
|
||||||
const expectedFiles = [
|
|
||||||
"library-songs.json",
|
|
||||||
"library-artists.json",
|
|
||||||
"library-albums.json",
|
|
||||||
"library-playlists.json",
|
|
||||||
"library-recentlyAdded.json",
|
|
||||||
]
|
|
||||||
for (let i = 0; i < expectedDirectories.length; i++) {
|
|
||||||
if (!existsSync(path.join(app.getPath("userData"), expectedDirectories[i]))) {
|
|
||||||
mkdirSync(path.join(app.getPath("userData"), expectedDirectories[i]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (let i = 0; i < expectedFiles.length; i++) {
|
|
||||||
const file = path.join(app.paths.ciderCache, expectedFiles[i])
|
|
||||||
if (!existsSync(file)) {
|
|
||||||
writeFileSync(file, JSON.stringify([]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
EnvironmentVariables: {
|
|
||||||
"env": {
|
|
||||||
platform: os.platform(),
|
|
||||||
dev: app.isPackaged
|
|
||||||
}
|
|
||||||
},
|
|
||||||
LinkHandler: (startArgs) => {
|
|
||||||
if (!startArgs) return;
|
|
||||||
|
|
||||||
if (String(startArgs).includes('auth')) {
|
|
||||||
let authURI = String(startArgs).split('/auth/')[1]
|
|
||||||
if (authURI.startsWith('lastfm')) { // If we wanted more auth options
|
|
||||||
console.log("lfmtoken", String(startArgs))
|
|
||||||
const authKey = authURI.split('lastfm?token=')[1];
|
|
||||||
app.cfg.set('lastfm.enabled', true);
|
|
||||||
app.cfg.set('lastfm.auth_token', authKey);
|
|
||||||
CiderBase.win.webContents.send('LastfmAuthenticated', authKey);
|
|
||||||
lastfm.authenticate()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (String(startArgs).includes('/play/')) { //Steer away from protocal:// specific conditionals
|
|
||||||
const playParam = String(startArgs).split('/play/')[1]
|
|
||||||
if (playParam.includes('s/')) { // setQueue can be done with album, song, url, playlist id
|
|
||||||
console.log(playParam)
|
|
||||||
let song = playParam.split('s/')[1]
|
|
||||||
console.warn(`[LinkHandler] Attempting to load song by id: ${song}`);
|
|
||||||
this.win.webContents.executeJavaScript(`
|
|
||||||
MusicKit.getInstance().setQueue({ song: '${song}'}).then(function(queue) {
|
|
||||||
MusicKit.getInstance().play();
|
|
||||||
});
|
|
||||||
`).catch((err) => console.error(err));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
async InitWebServer() {
|
|
||||||
const webapp = express();
|
|
||||||
const webRemotePath = path.join(__dirname, '../renderer/');
|
|
||||||
webapp.set("views", path.join(webRemotePath, "views"));
|
|
||||||
webapp.set("view engine", "ejs");
|
|
||||||
let firstRequest = true
|
|
||||||
//const webRemoteMDNS = mdns.createAdvertisement(mdns.tcp('https'), 9000, { name: "cider", domain: 'local' })
|
|
||||||
//webRemoteMDNS.start()
|
|
||||||
//* Prep for remote -quack
|
|
||||||
webapp.use(function(req, res, next) {
|
|
||||||
// if not localhost
|
|
||||||
if (req.url.includes("audio.webm") || (req.headers.host.includes("localhost") && req.headers["user-agent"].includes("Cider"))) {
|
|
||||||
next();
|
|
||||||
|
|
||||||
} else {
|
|
||||||
console.log(req.get('host'))
|
|
||||||
res.redirect("https://discord.gg/applemusic")
|
|
||||||
}
|
|
||||||
});
|
|
||||||
webapp.use(express.static(webRemotePath));
|
|
||||||
webapp.get('/', function(req, res) {
|
|
||||||
//if (!req.headers["user-agent"].includes("Cider"))
|
|
||||||
//res.sendFile(path.join(webRemotePath, 'index_old.html'));
|
|
||||||
if (firstRequest) {
|
|
||||||
console.log("---- Ignore Me ;) ---")
|
|
||||||
qrcode.generate(`http://${os.hostname}:9000`) //Prep for remote
|
|
||||||
console.log("---- Ignore Me ;) ---")
|
|
||||||
/*
|
|
||||||
*
|
|
||||||
* USING https://www.npmjs.com/package/qrcode-terminal for terminal
|
|
||||||
* WE SHOULD USE https://www.npmjs.com/package/qrcode for the remote (or others)
|
|
||||||
* -quack
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
firstRequest = false
|
|
||||||
|
|
||||||
res.render("main", CiderBase.EnvironmentVariables)
|
|
||||||
});
|
|
||||||
webapp.get('/audio.webm', function(req, res) {
|
|
||||||
console.log('hi')
|
|
||||||
try {
|
|
||||||
req.connection.setTimeout(Number.MAX_SAFE_INTEGER);
|
|
||||||
// CiderBase.requests.push({req: req, res: res});
|
|
||||||
// var pos = CiderBase.requests.length - 1;
|
|
||||||
// req.on("close", () => {
|
|
||||||
// console.info("CLOSED", CiderBase.requests.length);
|
|
||||||
// requests.splice(pos, 1);
|
|
||||||
// console.info("CLOSED", CiderBase.requests.length);
|
|
||||||
// });
|
|
||||||
CiderBase.audiostream.on('data', (data) => {
|
|
||||||
try {
|
|
||||||
res.write(data);
|
|
||||||
} catch (ex) {
|
|
||||||
console.log(ex)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} catch (ex) { console.log(ex) }
|
|
||||||
});
|
|
||||||
webapp.listen(CiderBase.clientPort, function() {
|
|
||||||
console.log(`Cider hosted on: ${CiderBase.clientPort}`);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = CiderBase;
|
|
|
@ -1,142 +0,0 @@
|
||||||
const { app } = require('electron'),
|
|
||||||
DiscordRPC = require('discord-rpc')
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Connects to Discord RPC
|
|
||||||
* @param {string} clientId
|
|
||||||
*/
|
|
||||||
connect: function(clientId) {
|
|
||||||
app.discord = { isConnected: false };
|
|
||||||
if (app.cfg.get('general.discord_rpc') == 0 || app.discord.isConnected) return;
|
|
||||||
|
|
||||||
DiscordRPC.register(clientId) // Apparently needed for ask to join, join, spectate etc.
|
|
||||||
const client = new DiscordRPC.Client({ transport: "ipc" });
|
|
||||||
app.discord = Object.assign(client, { error: false, activityCache: null, isConnected: false });
|
|
||||||
|
|
||||||
// Login to Discord
|
|
||||||
app.discord.login({ clientId })
|
|
||||||
.then(() => {
|
|
||||||
app.discord.isConnected = true;
|
|
||||||
})
|
|
||||||
.catch((e) => console.error(`[DiscordRPC][connect] ${e}`));
|
|
||||||
|
|
||||||
app.discord.on('ready', () => {
|
|
||||||
console.log(`[DiscordRPC][connect] Successfully Connected to Discord. Authed for user: ${client.user.username} (${client.user.id})`);
|
|
||||||
})
|
|
||||||
|
|
||||||
// Handles Errors
|
|
||||||
app.discord.on('error', err => {
|
|
||||||
console.error(`[DiscordRPC] ${err}`);
|
|
||||||
this.disconnect()
|
|
||||||
app.discord.isConnected = false;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disconnects from Discord RPC
|
|
||||||
*/
|
|
||||||
disconnect: function() {
|
|
||||||
if (app.cfg.get('general.discord_rpc') == 0 || !app.discord.isConnected) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
app.discord.destroy().then(() => {
|
|
||||||
app.discord.isConnected = false;
|
|
||||||
console.log('[DiscordRPC][disconnect] Disconnected from discord.')
|
|
||||||
}).catch((e) => console.error(`[DiscordRPC][disconnect] ${e}`));
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the activity of the client
|
|
||||||
* @param {object} attributes
|
|
||||||
*/
|
|
||||||
updateActivity: function(attributes) {
|
|
||||||
if (app.cfg.get('general.discord_rpc') == 0) return;
|
|
||||||
|
|
||||||
if (!app.discord.isConnected) {
|
|
||||||
app.discord.clearActivity().catch((e) => console.error(`[DiscordRPC][updateActivity] ${e}`));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// console.log('[DiscordRPC][updateActivity] Updating Discord Activity.')
|
|
||||||
|
|
||||||
const listenURL = `https://cider.sh/p?s&id=${attributes.playParams.id}` // cider://play/s/[id] (for song)
|
|
||||||
//console.log(attributes)
|
|
||||||
let ActivityObject = {
|
|
||||||
details: attributes.name,
|
|
||||||
state: `by ${attributes.artistName}`,
|
|
||||||
startTimestamp: attributes.startTime,
|
|
||||||
endTimestamp: attributes.endTime,
|
|
||||||
largeImageKey: (attributes.artwork.url.replace('{w}', '1024').replace('{h}', '1024')) ?? 'cider',
|
|
||||||
largeImageText: attributes.albumName,
|
|
||||||
smallImageKey: (attributes.status ? 'play' : 'pause'),
|
|
||||||
smallImageText: (attributes.status ? 'Playing' : 'Paused'),
|
|
||||||
instance: true,
|
|
||||||
buttons: [
|
|
||||||
{ label: "Listen on Cider", url: listenURL },
|
|
||||||
]
|
|
||||||
};
|
|
||||||
if (ActivityObject.largeImageKey == "" || ActivityObject.largeImageKey == null) {
|
|
||||||
ActivityObject.largeImageKey = (app.cfg.get("general.discord_rpc") == 1) ? "cider" : "logo"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the pause/play icon and test for clear activity on pause
|
|
||||||
if (app.cfg.get('general.discordClearActivityOnPause') == 1) {
|
|
||||||
delete ActivityObject.smallImageKey
|
|
||||||
delete ActivityObject.smallImageText
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deletes the timestamp if its not greater than 0
|
|
||||||
if (!((new Date(attributes.endTime)).getTime() > 0)) {
|
|
||||||
delete ActivityObject.startTimestamp
|
|
||||||
delete ActivityObject.endTimestamp
|
|
||||||
}
|
|
||||||
|
|
||||||
// Artist check
|
|
||||||
if (!attributes.artistName) {
|
|
||||||
delete ActivityObject.state
|
|
||||||
}
|
|
||||||
|
|
||||||
// Album text check
|
|
||||||
if (!ActivityObject.largeImageText || ActivityObject.largeImageText.length < 2) {
|
|
||||||
delete ActivityObject.largeImageText
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checks if the name is greater than 128 because some songs can be that long
|
|
||||||
if (ActivityObject.details.length > 128) {
|
|
||||||
ActivityObject.details = ActivityObject.details.substring(0, 125) + '...'
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Check if its pausing (false) or playing (true)
|
|
||||||
if (!attributes.status) {
|
|
||||||
if (app.cfg.get('general.discordClearActivityOnPause') == 1) {
|
|
||||||
app.discord.clearActivity().catch((e) => console.error(`[DiscordRPC][clearActivity] ${e}`));
|
|
||||||
ActivityObject = null
|
|
||||||
} else {
|
|
||||||
delete ActivityObject.startTimestamp
|
|
||||||
delete ActivityObject.endTimestamp
|
|
||||||
ActivityObject.smallImageKey = 'pause'
|
|
||||||
ActivityObject.smallImageText = 'Paused'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (ActivityObject && ActivityObject !== app.discord.activityCache && ActivityObject.details && ActivityObject.state) {
|
|
||||||
try {
|
|
||||||
// console.log(`[DiscordRPC][setActivity] Setting activity to ${JSON.stringify(ActivityObject)}`);
|
|
||||||
app.discord.setActivity(ActivityObject)
|
|
||||||
app.discord.activityCache = ActivityObject
|
|
||||||
} catch (err) {
|
|
||||||
console.error(`[DiscordRPC][setActivity] ${err}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
84
src/main/index.ts
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
require('v8-compile-cache');
|
||||||
|
|
||||||
|
// Analytics for debugging fun yeah.
|
||||||
|
import * as sentry from '@sentry/electron';
|
||||||
|
import * as electron from 'electron';
|
||||||
|
import {Win} from "./base/win";
|
||||||
|
import {ConfigStore} from "./base/store";
|
||||||
|
import {AppEvents} from "./base/app";
|
||||||
|
import PluginHandler from "./base/plugins";
|
||||||
|
|
||||||
|
sentry.init({dsn: "https://68c422bfaaf44dea880b86aad5a820d2@o954055.ingest.sentry.io/6112214"});
|
||||||
|
|
||||||
|
const config = new ConfigStore();
|
||||||
|
const App = new AppEvents(config.store);
|
||||||
|
const Cider = new Win(electron.app, config.store)
|
||||||
|
const plug = new PluginHandler(config.store);
|
||||||
|
|
||||||
|
let win: Electron.BrowserWindow;
|
||||||
|
|
||||||
|
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
* App Event Handlers
|
||||||
|
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
|
||||||
|
|
||||||
|
electron.app.on('ready', () => {
|
||||||
|
App.ready(plug);
|
||||||
|
|
||||||
|
console.log('[Cider] Application is Ready. Creating Window.')
|
||||||
|
if (!electron.app.isPackaged) {
|
||||||
|
console.info('[Cider] Running in development mode.')
|
||||||
|
require('vue-devtools').install()
|
||||||
|
}
|
||||||
|
|
||||||
|
electron.components.whenReady().then(async () => {
|
||||||
|
win = await Cider.createWindow()
|
||||||
|
App.bwCreated(win);
|
||||||
|
/// please dont change this for plugins to get proper and fully initialized Win objects
|
||||||
|
plug.callPlugins('onReady', win);
|
||||||
|
win.on("ready-to-show", () => {
|
||||||
|
win.show();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
* Renderer Event Handlers
|
||||||
|
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
|
||||||
|
|
||||||
|
electron.ipcMain.on('playbackStateDidChange', (event, attributes) => {
|
||||||
|
plug.callPlugins('onPlaybackStateDidChange', attributes);
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.ipcMain.on('nowPlayingItemDidChange', (event, attributes) => {
|
||||||
|
plug.callPlugins('onNowPlayingItemDidChange', attributes);
|
||||||
|
});
|
||||||
|
|
||||||
|
electron.app.on('before-quit', () => {
|
||||||
|
plug.callPlugins('onBeforeQuit');
|
||||||
|
console.warn(`${electron.app.getName()} exited.`);
|
||||||
|
});
|
||||||
|
|
||||||
|
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
* Widevine Event Handlers
|
||||||
|
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
electron.app.on('widevine-ready', (version, lastVersion) => {
|
||||||
|
if (null !== lastVersion) {
|
||||||
|
console.log('[Cider][Widevine] Widevine ' + version + ', upgraded from ' + lastVersion + ', is ready to be used!')
|
||||||
|
} else {
|
||||||
|
console.log('[Cider][Widevine] Widevine ' + version + ' is ready to be used!')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
electron.app.on('widevine-update-pending', (currentVersion, pendingVersion) => {
|
||||||
|
console.log('[Cider][Widevine] Widevine ' + currentVersion + ' is ready to be upgraded to ' + pendingVersion + '!')
|
||||||
|
})
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
electron.app.on('widevine-error', (error) => {
|
||||||
|
console.log('[Cider][Widevine] Widevine installation encountered an error: ' + error)
|
||||||
|
electron.app.exit()
|
||||||
|
})
|
|
@ -1,153 +0,0 @@
|
||||||
const {app, Notification} = require('electron'),
|
|
||||||
fs = require('fs'),
|
|
||||||
{resolve} = require('path'),
|
|
||||||
sessionPath = resolve(app.getPath('userData'), 'session.json'),
|
|
||||||
apiCredentials = require('../../resources/lfmApiCredentials.json'),
|
|
||||||
LastfmAPI = require('lastfmapi');
|
|
||||||
|
|
||||||
const lfm = {
|
|
||||||
authenticateFromFile: function () {
|
|
||||||
let sessionData = require(sessionPath)
|
|
||||||
console.log("[LastFM][authenticateFromFile] Logging in with Session Info.")
|
|
||||||
app.lastfm.setSessionCredentials(sessionData.name, sessionData.key)
|
|
||||||
console.log("[LastFM][authenticateFromFile] Logged in.")
|
|
||||||
},
|
|
||||||
|
|
||||||
authenticate: function () {
|
|
||||||
if (app.cfg.get('lastfm.auth_token')) {
|
|
||||||
app.cfg.set('lastfm.enabled', true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!app.cfg.get('lastfm.enabled') || !app.cfg.get('lastfm.auth_token')) {
|
|
||||||
app.cfg.set('lastfm.enabled', false);
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const lfmAPI = new LastfmAPI({
|
|
||||||
'api_key': apiCredentials.key,
|
|
||||||
'secret': apiCredentials.secret
|
|
||||||
});
|
|
||||||
|
|
||||||
app.lastfm = Object.assign(lfmAPI, {cachedAttributes: false, cachedNowPlayingAttributes: false});
|
|
||||||
|
|
||||||
fs.stat(sessionPath, function (err) {
|
|
||||||
if (err) {
|
|
||||||
console.error("[LastFM][Session] Session file couldn't be opened or doesn't exist,", err)
|
|
||||||
console.log("[LastFM][Auth] Beginning authentication from configuration")
|
|
||||||
app.lastfm.authenticate(app.cfg.get('lastfm.auth_token'), function (err, session) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
console.log("[LastFM] Successfully obtained LastFM session info,", session); // {"name": "LASTFM_USERNAME", "key": "THE_USER_SESSION_KEY"}
|
|
||||||
console.log("[LastFM] Saving session info to disk.")
|
|
||||||
let tempData = JSON.stringify(session)
|
|
||||||
fs.writeFile(sessionPath, tempData, (err) => {
|
|
||||||
if (err)
|
|
||||||
console.log("[LastFM][fs]", err)
|
|
||||||
else {
|
|
||||||
console.log("[LastFM][fs] File was written successfully.")
|
|
||||||
lfm.authenticateFromFile()
|
|
||||||
new Notification({
|
|
||||||
title: app.getName(),
|
|
||||||
body: "Successfully logged into LastFM using Authentication Key."
|
|
||||||
}).show()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
lfm.authenticateFromFile()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
scrobbleSong: async function (attributes) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, Math.round(attributes.durationInMillis * (app.cfg.get('lastfm.scrobble_after') / 100))));
|
|
||||||
const currentAttributes = app.media;
|
|
||||||
|
|
||||||
if (!app.lastfm || app.lastfm.cachedAttributes === attributes ) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (app.lastfm.cachedAttributes) {
|
|
||||||
if (app.lastfm.cachedAttributes.playParams.id === attributes.playParams.id) return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentAttributes.status && currentAttributes === attributes) {
|
|
||||||
if (fs.existsSync(sessionPath)) {
|
|
||||||
// Scrobble playing song.
|
|
||||||
if (attributes.status === true) {
|
|
||||||
app.lastfm.track.scrobble({
|
|
||||||
'artist': lfm.filterArtistName(attributes.artistName),
|
|
||||||
'track': attributes.name,
|
|
||||||
'album': attributes.albumName,
|
|
||||||
'albumArtist': this.filterArtistName(attributes.artistName),
|
|
||||||
'timestamp': new Date().getTime() / 1000
|
|
||||||
}, function (err, scrobbled) {
|
|
||||||
if (err) {
|
|
||||||
return console.error('[LastFM] An error occurred while scrobbling', err);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('[LastFM] Successfully scrobbled: ', scrobbled);
|
|
||||||
});
|
|
||||||
app.lastfm.cachedAttributes = attributes
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.authenticate();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return console.log('[LastFM] Did not add ', attributes.name , '—' , lfm.filterArtistName(attributes.artistName), 'because now playing a other song.');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
filterArtistName: function (artist) {
|
|
||||||
if (!app.cfg.get('lastfm.enabledRemoveFeaturingArtists')) return artist;
|
|
||||||
|
|
||||||
artist = artist.split(' ');
|
|
||||||
if (artist.includes('&')) {
|
|
||||||
artist.length = artist.indexOf('&');
|
|
||||||
}
|
|
||||||
if (artist.includes('and')) {
|
|
||||||
artist.length = artist.indexOf('and');
|
|
||||||
}
|
|
||||||
artist = artist.join(' ');
|
|
||||||
if (artist.includes(',')) {
|
|
||||||
artist = artist.split(',')
|
|
||||||
artist = artist[0]
|
|
||||||
}
|
|
||||||
return artist.charAt(0).toUpperCase() + artist.slice(1);
|
|
||||||
},
|
|
||||||
|
|
||||||
updateNowPlayingSong: function (attributes) {
|
|
||||||
if (!app.lastfm ||app.lastfm.cachedNowPlayingAttributes === attributes | !app.cfg.get('lastfm.NowPlaying')) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (app.lastfm.cachedNowPlayingAttributes) {
|
|
||||||
if (app.lastfm.cachedNowPlayingAttributes.playParams.id === attributes.playParams.id) return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fs.existsSync(sessionPath)) {
|
|
||||||
// update Now Playing
|
|
||||||
if (attributes.status === true) {
|
|
||||||
app.lastfm.track.updateNowPlaying({
|
|
||||||
'artist': lfm.filterArtistName(attributes.artistName),
|
|
||||||
'track': attributes.name,
|
|
||||||
'album': attributes.albumName,
|
|
||||||
'albumArtist': this.filterArtistName(attributes.artistName)
|
|
||||||
}, function (err, nowPlaying) {
|
|
||||||
if (err) {
|
|
||||||
return console.error('[LastFM] An error occurred while updating nowPlayingSong', err);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('[LastFM] Successfully updated nowPlayingSong', nowPlaying);
|
|
||||||
});
|
|
||||||
app.lastfm.cachedNowPlayingAttributes = attributes
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
this.authenticate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = lfm;
|
|
|
@ -1,149 +0,0 @@
|
||||||
const { nativeImage } = require("electron");
|
|
||||||
const path = require('path')
|
|
||||||
|
|
||||||
let mediaPlayer = null;
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Connects to the MPRIS interface.
|
|
||||||
* @param {Object} win - The BrowserWindow.
|
|
||||||
*/
|
|
||||||
connect: (win) => {
|
|
||||||
if (process.platform !== "linux") return;
|
|
||||||
|
|
||||||
const Player = require('mpris-service');
|
|
||||||
|
|
||||||
mediaPlayer = Player({
|
|
||||||
name: 'Cider',
|
|
||||||
identity: 'Cider',
|
|
||||||
supportedUriSchemes: [],
|
|
||||||
supportedMimeTypes: [],
|
|
||||||
supportedInterfaces: ['player']
|
|
||||||
});
|
|
||||||
mediaPlayer = Object.assign(mediaPlayer, { canQuit: true, canControl: true, canPause: true, canPlay: true, canGoNext: true })
|
|
||||||
|
|
||||||
|
|
||||||
let pos_atr = {durationInMillis: 0};
|
|
||||||
mediaPlayer.getPosition = function () {
|
|
||||||
const durationInMicro = pos_atr.durationInMillis * 1000;
|
|
||||||
const percentage = parseFloat("0") || 0;
|
|
||||||
return durationInMicro * percentage;
|
|
||||||
}
|
|
||||||
|
|
||||||
mediaPlayer.active = true
|
|
||||||
|
|
||||||
mediaPlayer.on('playpause', async () => {
|
|
||||||
win.webContents.executeJavaScript('MusicKitInterop.pausePlay()').catch(err => console.error(err))
|
|
||||||
});
|
|
||||||
|
|
||||||
mediaPlayer.on('play', async () => {
|
|
||||||
win.webContents.executeJavaScript('MusicKitInterop.pausePlay()').catch(err => console.error(err))
|
|
||||||
});
|
|
||||||
|
|
||||||
mediaPlayer.on('pause', async () => {
|
|
||||||
win.webContents.executeJavaScript('MusicKitInterop.pausePlay()').catch(err => console.error(err))
|
|
||||||
});
|
|
||||||
|
|
||||||
mediaPlayer.on('next', async () => {
|
|
||||||
win.webContents.executeJavaScript('MusicKitInterop.nextTrack()').catch(err => console.error(err))
|
|
||||||
});
|
|
||||||
|
|
||||||
mediaPlayer.on('previous', async () => {
|
|
||||||
win.webContents.executeJavaScript('MusicKitInterop.previousTrack()').catch(err => console.error(err))
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the MPRIS interface.
|
|
||||||
* @param {Object} attributes - The attributes of the track.
|
|
||||||
*/
|
|
||||||
updateAttributes: (attributes) => {
|
|
||||||
if (process.platform !== "linux") return;
|
|
||||||
|
|
||||||
const MetaData = {
|
|
||||||
'mpris:trackid': mediaPlayer.objectPath(`track/${attributes.playParams.id.replace(/[.]+/g, "")}`),
|
|
||||||
'mpris:length': attributes.durationInMillis * 1000, // In microseconds
|
|
||||||
'mpris:artUrl': (attributes.artwork.url.replace('/{w}x{h}bb', '/512x512bb')).replace('/2000x2000bb', '/35x35bb'),
|
|
||||||
'xesam:title': `${attributes.name}`,
|
|
||||||
'xesam:album': `${attributes.albumName}`,
|
|
||||||
'xesam:artist': [`${attributes.artistName}`,],
|
|
||||||
'xesam:genre': attributes.genreNames
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mediaPlayer.metadata["mpris:trackid"] === MetaData["mpris:trackid"]) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
mediaPlayer.metadata = MetaData
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the playback state of the MPRIS interface.
|
|
||||||
* @param {Object} attributes - The attributes of the track.
|
|
||||||
*/
|
|
||||||
updateState: (attributes) => {
|
|
||||||
if (process.platform !== "linux") return;
|
|
||||||
|
|
||||||
function setPlaybackIfNeeded(status) {
|
|
||||||
if (mediaPlayer.playbackStatus === status) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
mediaPlayer.playbackStatus = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (attributes.status) {
|
|
||||||
case true: // Playing
|
|
||||||
setPlaybackIfNeeded('Playing');
|
|
||||||
break;
|
|
||||||
case false: // Paused
|
|
||||||
setPlaybackIfNeeded('Paused');
|
|
||||||
break;
|
|
||||||
default: // Stopped
|
|
||||||
setPlaybackIfNeeded('Stopped');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
SetButtons: (win, attributes) => {
|
|
||||||
if (process.platform === 'win32') { // Set the Windows Thumbnail Toolbar Buttons
|
|
||||||
win.setThumbarButtons([
|
|
||||||
{
|
|
||||||
tooltip: 'Previous',
|
|
||||||
icon: nativeImage.createFromPath(path.join(__dirname, 'thumbaricons/backwardPng.png')),
|
|
||||||
click() {
|
|
||||||
console.log("Clicked the bc taskbar button!")
|
|
||||||
win.webContents.executeJavaScript('MusicKitInterop.previousTrack()').catch(err => console.error(err))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tooltip: attributes.status ? 'Pause' : 'Play',
|
|
||||||
//tooltip: 'Play',
|
|
||||||
icon: attributes.status ? nativeImage.createFromPath(path.join(__dirname, 'thumbaricons/pausePng.png')) : nativeImage.createFromPath(path.join(__dirname, 'thumbaricons/playPng.png')),
|
|
||||||
click() {
|
|
||||||
console.log("Clicked the pl taskbar button!")
|
|
||||||
win.webContents.executeJavaScript('MusicKitInterop.pausePlay()').catch(err => console.error(err))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tooltip: 'Next',
|
|
||||||
icon: nativeImage.createFromPath(path.join(__dirname, 'thumbaricons/forwardPng.png')),
|
|
||||||
click() {
|
|
||||||
console.log("Clicked the fw taskbar button!")
|
|
||||||
win.webContents.executeJavaScript('MusicKitInterop.nextTrack()').catch(err => console.error(err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes the MPRIS interface.
|
|
||||||
*/
|
|
||||||
clearActivity: () => {
|
|
||||||
if (process.platform !== "linux") return;
|
|
||||||
mediaPlayer.metadata = {'mpris:trackid': '/org/mpris/MediaPlayer2/TrackList/NoTrack'}
|
|
||||||
mediaPlayer.playbackStatus = 'Stopped';
|
|
||||||
},
|
|
||||||
}
|
|
60
src/main/plugins/Extras/examplePlugin.ts
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
let i = 1, k = 1;
|
||||||
|
export default class ExamplePlugin {
|
||||||
|
/**
|
||||||
|
* Private variables for interaction in plugins
|
||||||
|
*/
|
||||||
|
private _win: any;
|
||||||
|
private _app: any;
|
||||||
|
private _store: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base Plugin Details (Eventually implemented into a GUI in settings)
|
||||||
|
*/
|
||||||
|
public name: string = 'examplePlugin';
|
||||||
|
public description: string = 'Example plugin';
|
||||||
|
public version: string = '1.0.0';
|
||||||
|
public author: string = 'Example author';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs on plugin load (Currently run on application start)
|
||||||
|
*/
|
||||||
|
constructor(app: any, store: any) {
|
||||||
|
this._app = app;
|
||||||
|
this._store = store;
|
||||||
|
console.debug(`[Plugin][${this.name}] Loading Complete.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs on app ready
|
||||||
|
*/
|
||||||
|
onReady(win: any): void {
|
||||||
|
this._win = win;
|
||||||
|
console.debug(`[Plugin][${this.name}] Ready.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs on app stop
|
||||||
|
*/
|
||||||
|
onBeforeQuit(): void {
|
||||||
|
console.debug(`[Plugin][${this.name}] Stopped.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs on playback State Change
|
||||||
|
* @param attributes Music Attributes (attributes.state = current state)
|
||||||
|
*/
|
||||||
|
onPlaybackStateDidChange(attributes: object): void {
|
||||||
|
console.log('onPlaybackStateDidChange has been called ' + i + ' times');
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs on song change
|
||||||
|
* @param attributes Music Attributes
|
||||||
|
*/
|
||||||
|
onNowPlayingItemDidChange(attributes: object): void {
|
||||||
|
console.log('onNowPlayingDidChange has been called ' + k + ' times');
|
||||||
|
k++
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
37
src/main/plugins/Extras/sendSongToTitlebar.ts
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
export default class sendSongToTitlebar {
|
||||||
|
/**
|
||||||
|
* Base Plugin Details (Eventually implemented into a GUI in settings)
|
||||||
|
*/
|
||||||
|
public name: string = 'sendSongToTitlebar';
|
||||||
|
public description: string = 'Sets the app\'s titlebar to the Song title';
|
||||||
|
public version: string = '0.0.1';
|
||||||
|
public author: string = 'Cider Collective (credit to 8times9 via #147)';
|
||||||
|
/**
|
||||||
|
* Runs on plugin load (Currently run on application start)
|
||||||
|
*/
|
||||||
|
private _win: any;
|
||||||
|
private _app: any;
|
||||||
|
constructor() {}
|
||||||
|
/**
|
||||||
|
* Runs on app ready
|
||||||
|
*/
|
||||||
|
onReady(win: any): void {
|
||||||
|
this._win = win;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Runs on app stop
|
||||||
|
*/
|
||||||
|
onBeforeQuit(): void {}
|
||||||
|
/**
|
||||||
|
* Runs on playback State Change
|
||||||
|
* @param attributes Music Attributes (attributes.state = current state)
|
||||||
|
*/
|
||||||
|
onPlaybackStateDidChange(attributes: any): void {
|
||||||
|
this._win.setTitle(`${(attributes != null && attributes.name != null && attributes.name.length > 0) ? (attributes.name + " - ") : ''}Cider`)
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Runs on song change
|
||||||
|
* @param attributes Music Attributes
|
||||||
|
*/
|
||||||
|
onNowPlayingItemDidChange(attributes: object): void {}
|
||||||
|
}
|
195
src/main/plugins/discordrpc.ts
Normal file
|
@ -0,0 +1,195 @@
|
||||||
|
import * as RPC from 'discord-rpc'
|
||||||
|
|
||||||
|
export default class DiscordRichPresence {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private variables for interaction in plugins
|
||||||
|
*/
|
||||||
|
private static _store: any;
|
||||||
|
private static _connection: boolean = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base Plugin Details (Eventually implemented into a GUI in settings)
|
||||||
|
*/
|
||||||
|
public name: string = 'Discord Rich Presence';
|
||||||
|
public description: string = 'Discord RPC plugin for Cider';
|
||||||
|
public version: string = '1.0.0';
|
||||||
|
public author: string = 'vapormusic/Core (Cider Collective)';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plugin Initialization
|
||||||
|
*/
|
||||||
|
private _client: any = null;
|
||||||
|
private _activity: RPC.Presence = {
|
||||||
|
details: '',
|
||||||
|
state: '',
|
||||||
|
largeImageKey: '',
|
||||||
|
largeImageText: '',
|
||||||
|
smallImageKey: '',
|
||||||
|
smallImageText: '',
|
||||||
|
instance: false
|
||||||
|
};
|
||||||
|
private _activityCache: RPC.Presence = {
|
||||||
|
details: '',
|
||||||
|
state: '',
|
||||||
|
largeImageKey: '',
|
||||||
|
largeImageText: '',
|
||||||
|
smallImageKey: '',
|
||||||
|
smallImageText: '',
|
||||||
|
instance: false
|
||||||
|
};
|
||||||
|
|
||||||
|
/*******************************************************************************************
|
||||||
|
* Private Methods
|
||||||
|
* ****************************************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect to Discord
|
||||||
|
* @param clientId
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private connect(clientId: any) {
|
||||||
|
if (DiscordRichPresence._store.general.discord_rpc == 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apparently needed for ask to join, join, spectate etc.
|
||||||
|
RPC.register(clientId)
|
||||||
|
|
||||||
|
// Create the client
|
||||||
|
this._client = new RPC.Client({transport: "ipc"});
|
||||||
|
|
||||||
|
// Runs on Ready
|
||||||
|
this._client.on('ready', () => {
|
||||||
|
console.info(`[DiscordRPC][connect] Successfully Connected to Discord. Authed for user: ${this._client.user.id}.`);
|
||||||
|
})
|
||||||
|
|
||||||
|
// Handles Errors
|
||||||
|
this._client.on('error', (err: any) => {
|
||||||
|
console.error(`[DiscordRichPresence] ${err}`);
|
||||||
|
this.disconnect()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Login to Discord
|
||||||
|
this._client.login({clientId})
|
||||||
|
.then(() => {
|
||||||
|
DiscordRichPresence._connection = true;
|
||||||
|
})
|
||||||
|
.catch((e: any) => console.error(`[DiscordRichPresence][connect] ${e}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnects from Discord RPC
|
||||||
|
*/
|
||||||
|
private disconnect() {
|
||||||
|
if (!this._client) return;
|
||||||
|
|
||||||
|
this._client.destroy().then(() => {
|
||||||
|
DiscordRichPresence._connection = false;
|
||||||
|
console.log('[DiscordRPC][disconnect] Disconnected from discord.')
|
||||||
|
}).catch((e: any) => console.error(`[DiscordRPC][disconnect] ${e}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the activity of the client
|
||||||
|
* @param {object} attributes
|
||||||
|
*/
|
||||||
|
private updateActivity(attributes: any) {
|
||||||
|
if (!this._client) return;
|
||||||
|
|
||||||
|
if (!DiscordRichPresence._connection) {
|
||||||
|
this._client.clearActivity().catch((e: any) => console.error(`[DiscordRichPresence][clearActivity] ${e}`));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._activity = {
|
||||||
|
details: attributes.name,
|
||||||
|
state: `${attributes.artistName ? `by ${attributes.artistName}` : ''}`,
|
||||||
|
startTimestamp: ((new Date(attributes.endTime).getTime() < 0) ? null : attributes.startTime),
|
||||||
|
endTimestamp: ((new Date(attributes.endTime).getTime() < 0) ? null : attributes.endTime),
|
||||||
|
largeImageKey: (attributes.artwork.url.replace('{w}', '1024').replace('{h}', '1024')) ?? 'cider',
|
||||||
|
largeImageText: attributes.albumName,
|
||||||
|
instance: false, // Whether the activity is in a game session
|
||||||
|
|
||||||
|
buttons: [
|
||||||
|
{label: "Listen on Cider", url: attributes.url.cider},
|
||||||
|
{label: "View on Apple Music", url: attributes.url.appleMusic},
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Checks if the name is greater than 128 because some songs can be that long
|
||||||
|
if (this._activity.details && this._activity.details.length > 128) {
|
||||||
|
this._activity.details = this._activity.details.substring(0, 125) + '...'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if its pausing (false) or playing (true)
|
||||||
|
if (!attributes.status) {
|
||||||
|
if (DiscordRichPresence._store.general.discordClearActivityOnPause == 1) {
|
||||||
|
this._client.clearActivity()
|
||||||
|
.catch((e: any) => console.error(`[DiscordRichPresence][clearActivity] ${e}`));
|
||||||
|
} else {
|
||||||
|
this._activity.smallImageKey = 'pause';
|
||||||
|
this._activity.smallImageText = 'Paused';
|
||||||
|
delete this._activity.endTimestamp;
|
||||||
|
delete this._activity.startTimestamp;
|
||||||
|
this._client.setActivity(this._activity)
|
||||||
|
.catch((e: any) => console.error(`[DiscordRichPresence][setActivity] ${e}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (this._activity && this._activityCache !== this._activity && this._activity.details) {
|
||||||
|
if (DiscordRichPresence._store.general.discordClearActivityOnPause != 1) {
|
||||||
|
this._activity.smallImageKey = 'play';
|
||||||
|
this._activity.smallImageText = 'Playing';
|
||||||
|
}
|
||||||
|
|
||||||
|
this._client.setActivity(this._activity)
|
||||||
|
.catch((e: any) => console.error(`[DiscordRichPresence][updateActivity] ${e}`));
|
||||||
|
this._activityCache = this._activity;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************************
|
||||||
|
* Public Methods
|
||||||
|
* ****************************************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs on plugin load (Currently run on application start)
|
||||||
|
*/
|
||||||
|
constructor(_app: any, store: any) {
|
||||||
|
DiscordRichPresence._store = store
|
||||||
|
console.debug(`[Plugin][${this.name}] Loading Complete.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs on app ready
|
||||||
|
*/
|
||||||
|
onReady(_win: any): void {
|
||||||
|
this.connect((DiscordRichPresence._store.general.discord_rpc == 1) ? '911790844204437504' : '886578863147192350');
|
||||||
|
console.debug(`[Plugin][${this.name}] Ready.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs on app stop
|
||||||
|
*/
|
||||||
|
onBeforeQuit(): void {
|
||||||
|
console.debug(`[Plugin][${this.name}] Stopped.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs on playback State Change
|
||||||
|
* @param attributes Music Attributes (attributes.state = current state)
|
||||||
|
*/
|
||||||
|
onPlaybackStateDidChange(attributes: object): void {
|
||||||
|
this.updateActivity(attributes)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs on song change
|
||||||
|
* @param attributes Music Attributes
|
||||||
|
*/
|
||||||
|
onNowPlayingItemDidChange(attributes: object): void {
|
||||||
|
this.updateActivity(attributes)
|
||||||
|
}
|
||||||
|
}
|
253
src/main/plugins/lastfm.ts
Normal file
|
@ -0,0 +1,253 @@
|
||||||
|
import * as electron from 'electron';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import {resolve} from 'path';
|
||||||
|
|
||||||
|
export default class LastFMPlugin {
|
||||||
|
private sessionPath = resolve(electron.app.getPath('userData'), 'session.json');
|
||||||
|
private apiCredentials = {
|
||||||
|
key: "f9986d12aab5a0fe66193c559435ede3",
|
||||||
|
secret: "acba3c29bd5973efa38cc2f0b63cc625"
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Private variables for interaction in plugins
|
||||||
|
*/
|
||||||
|
private _win: any;
|
||||||
|
private _app: any;
|
||||||
|
private _lastfm: any;
|
||||||
|
private _store: any;
|
||||||
|
|
||||||
|
private authenticateFromFile() {
|
||||||
|
let sessionData = require(this.sessionPath)
|
||||||
|
console.log("[LastFM][authenticateFromFile] Logging in with Session Info.")
|
||||||
|
this._lastfm.setSessionCredentials(sessionData.username, sessionData.key)
|
||||||
|
console.log("[LastFM][authenticateFromFile] Logged in.", sessionData.username, sessionData.key)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
authenticate() {
|
||||||
|
try {
|
||||||
|
if (this._store.lastfm.auth_token) {
|
||||||
|
this._store.lastfm.enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this._store.lastfm.enabled || !this._store.lastfm.auth_token) {
|
||||||
|
this._store.lastfm.enabled = false;
|
||||||
|
return
|
||||||
|
}
|
||||||
|
/// dont move this require to top , app wont load
|
||||||
|
const LastfmAPI = require('lastfmapi');
|
||||||
|
const lfmAPI = new LastfmAPI({
|
||||||
|
'api_key': this.apiCredentials.key,
|
||||||
|
'secret': this.apiCredentials.secret
|
||||||
|
});
|
||||||
|
|
||||||
|
this._lastfm = Object.assign(lfmAPI, {cachedAttributes: false, cachedNowPlayingAttributes: false});
|
||||||
|
|
||||||
|
fs.stat(this.sessionPath, (err: any) => {
|
||||||
|
if (err) {
|
||||||
|
console.error("[LastFM][Session] Session file couldn't be opened or doesn't exist,", err)
|
||||||
|
console.log("[LastFM][Auth] Beginning authentication from configuration")
|
||||||
|
console.log("[LastFM][tk]", this._store.lastfm.auth_token)
|
||||||
|
this._lastfm.authenticate(this._store.lastfm.auth_token, (err: any, session: any) => {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
console.log("[LastFM] Successfully obtained LastFM session info,", session); // {"name": "LASTFM_USERNAME", "key": "THE_USER_SESSION_KEY"}
|
||||||
|
console.log("[LastFM] Saving session info to disk.")
|
||||||
|
let tempData = JSON.stringify(session)
|
||||||
|
fs.writeFile(this.sessionPath, tempData, (err: any) => {
|
||||||
|
if (err)
|
||||||
|
console.log("[LastFM][fs]", err)
|
||||||
|
else {
|
||||||
|
console.log("[LastFM][fs] File was written successfully.")
|
||||||
|
this.authenticateFromFile()
|
||||||
|
new electron.Notification({
|
||||||
|
title: electron.app.getName(),
|
||||||
|
body: "Successfully logged into LastFM using Authentication Key."
|
||||||
|
}).show()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.authenticateFromFile()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async scrobbleSong(attributes: any) {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, Math.round(attributes.durationInMillis * (this._store.lastfm.scrobble_after / 100))));
|
||||||
|
const currentAttributes = attributes;
|
||||||
|
|
||||||
|
if (!this._lastfm || this._lastfm.cachedAttributes === attributes) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._lastfm.cachedAttributes) {
|
||||||
|
if (this._lastfm.cachedAttributes.playParams.id === attributes.playParams.id) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentAttributes.status && currentAttributes === attributes) {
|
||||||
|
if (fs.existsSync(this.sessionPath)) {
|
||||||
|
// Scrobble playing song.
|
||||||
|
if (attributes.status === true) {
|
||||||
|
this._lastfm.track.scrobble({
|
||||||
|
'artist': this.filterArtistName(attributes.artistName),
|
||||||
|
'track': attributes.name,
|
||||||
|
'album': attributes.albumName,
|
||||||
|
'albumArtist': this.filterArtistName(attributes.artistName),
|
||||||
|
'timestamp': new Date().getTime() / 1000
|
||||||
|
}, function (err: any, scrobbled: any) {
|
||||||
|
if (err) {
|
||||||
|
return console.error('[LastFM] An error occurred while scrobbling', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[LastFM] Successfully scrobbled: ', scrobbled);
|
||||||
|
});
|
||||||
|
this._lastfm.cachedAttributes = attributes
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.authenticate();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return console.log('[LastFM] Did not add ', attributes.name, '—', this.filterArtistName(attributes.artistName), 'because now playing a other song.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private filterArtistName(artist: any) {
|
||||||
|
if (!this._store.lastfm.enabledRemoveFeaturingArtists) return artist;
|
||||||
|
|
||||||
|
artist = artist.split(' ');
|
||||||
|
if (artist.includes('&')) {
|
||||||
|
artist.length = artist.indexOf('&');
|
||||||
|
}
|
||||||
|
if (artist.includes('and')) {
|
||||||
|
artist.length = artist.indexOf('and');
|
||||||
|
}
|
||||||
|
artist = artist.join(' ');
|
||||||
|
if (artist.includes(',')) {
|
||||||
|
artist = artist.split(',')
|
||||||
|
artist = artist[0]
|
||||||
|
}
|
||||||
|
return artist.charAt(0).toUpperCase() + artist.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateNowPlayingSong(attributes: any) {
|
||||||
|
if (!this._lastfm || this._lastfm.cachedNowPlayingAttributes === attributes || !this._store.lastfm.NowPlaying) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._lastfm.cachedNowPlayingAttributes) {
|
||||||
|
if (this._lastfm.cachedNowPlayingAttributes.playParams.id === attributes.playParams.id) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fs.existsSync(this.sessionPath)) {
|
||||||
|
// update Now Playing
|
||||||
|
if (attributes.status === true) {
|
||||||
|
this._lastfm.track.updateNowPlaying({
|
||||||
|
'artist': this.filterArtistName(attributes.artistName),
|
||||||
|
'track': attributes.name,
|
||||||
|
'album': attributes.albumName,
|
||||||
|
'albumArtist': this.filterArtistName(attributes.artistName)
|
||||||
|
}, function (err: any, nowPlaying: any) {
|
||||||
|
if (err) {
|
||||||
|
return console.error('[LastFM] An error occurred while updating nowPlayingSong', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[LastFM] Successfully updated nowPlayingSong', nowPlaying);
|
||||||
|
});
|
||||||
|
this._lastfm.cachedNowPlayingAttributes = attributes
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
this.authenticate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base Plugin Details (Eventually implemented into a GUI in settings)
|
||||||
|
*/
|
||||||
|
public name: string = 'LastFMPlugin';
|
||||||
|
public description: string = 'LastFM plugin for Cider';
|
||||||
|
public version: string = '0.0.1';
|
||||||
|
public author: string = 'vapormusic / Cider Collective';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs on plugin load (Currently run on application start)
|
||||||
|
*/
|
||||||
|
constructor(app: any, store: any) {
|
||||||
|
this._app = app;
|
||||||
|
this._store = store
|
||||||
|
electron.app.on('second-instance', (_e: any, argv: any) => {
|
||||||
|
// Checks if first instance is authorized and if second instance has protocol args
|
||||||
|
argv.forEach((value: any) => {
|
||||||
|
if (value.includes('auth')) {
|
||||||
|
console.log('[LastFMPlugin ok]')
|
||||||
|
let authURI = String(argv).split('/auth/')[1];
|
||||||
|
if (authURI.startsWith('lastfm')) { // If we wanted more auth options
|
||||||
|
const authKey = authURI.split('lastfm?token=')[1];
|
||||||
|
this._store.lastfm.enabled = true;
|
||||||
|
this._store.lastfm.auth_token = authKey;
|
||||||
|
console.log(authKey);
|
||||||
|
this._win.webContents.send('LastfmAuthenticated', authKey);
|
||||||
|
this.authenticate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
electron.app.on('open-url', (event: any, arg: any) => {
|
||||||
|
console.log('[LastFMPlugin] yes')
|
||||||
|
event.preventDefault();
|
||||||
|
if (arg.includes('auth')) {
|
||||||
|
let authURI = String(arg).split('/auth/')[1];
|
||||||
|
if (authURI.startsWith('lastfm')) { // If we wanted more auth options
|
||||||
|
const authKey = authURI.split('lastfm?token=')[1];
|
||||||
|
this._store.lastfm.enabled = true;
|
||||||
|
this._store.lastfm.auth_token = authKey;
|
||||||
|
this._win.webContents.send('LastfmAuthenticated', authKey);
|
||||||
|
console.log(authKey);
|
||||||
|
this.authenticate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs on app ready
|
||||||
|
*/
|
||||||
|
onReady(win: any): void {
|
||||||
|
this._win = win;
|
||||||
|
this.authenticate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs on app stop
|
||||||
|
*/
|
||||||
|
onBeforeQuit(): void {
|
||||||
|
console.log('Example plugin stopped');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs on playback State Change
|
||||||
|
* @param attributes Music Attributes (attributes.state = current state)
|
||||||
|
*/
|
||||||
|
onPlaybackStateDidChange(attributes: object): void {
|
||||||
|
this.scrobbleSong(attributes)
|
||||||
|
this.updateNowPlayingSong(attributes)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs on song change
|
||||||
|
* @param attributes Music Attributes
|
||||||
|
*/
|
||||||
|
onNowPlayingItemDidChange(attributes: object): void {
|
||||||
|
if (!this._store.lastfm.filterLoop){
|
||||||
|
this._lastfm.cachedNowPlayingAttributes = false;
|
||||||
|
this._lastfm.cachedAttributes = false}
|
||||||
|
this.scrobbleSong(attributes)
|
||||||
|
this.updateNowPlayingSong(attributes)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
165
src/main/plugins/minimizeToTray.ts
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
import * as electron from 'electron';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
|
||||||
|
export default class MinimizeToTray {
|
||||||
|
/**
|
||||||
|
* Private variables for interaction in plugins
|
||||||
|
*/
|
||||||
|
private _win: any;
|
||||||
|
private _app: any;
|
||||||
|
private _store: any;
|
||||||
|
private _tray: any;
|
||||||
|
private _forceQuit = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base Plugin Details (Eventually implemented into a GUI in settings)
|
||||||
|
*/
|
||||||
|
public name: string = 'Minimize to tray';
|
||||||
|
public description: string = 'Allow Cider to minimize to tray';
|
||||||
|
public version: string = '1.0.0';
|
||||||
|
public author: string = 'vapormusic';
|
||||||
|
|
||||||
|
constructor(app: any, store: any) {
|
||||||
|
this._app = app;
|
||||||
|
this._store = store;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SetContextMenu(visibility : any) {
|
||||||
|
let self = this
|
||||||
|
if (visibility) {
|
||||||
|
this._tray.setContextMenu(electron.Menu.buildFromTemplate([
|
||||||
|
// {
|
||||||
|
// label: 'Check for Updates',
|
||||||
|
// click: function () {
|
||||||
|
// app.ame.utils.checkForUpdates(true)
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
label: 'Minimize to Tray',
|
||||||
|
click: function () {
|
||||||
|
if (typeof self._win.hide === 'function') {
|
||||||
|
self._win.hide();
|
||||||
|
self.SetContextMenu(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Quit',
|
||||||
|
click: function () {
|
||||||
|
self._forceQuit = true; self._app.quit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]));
|
||||||
|
} else {
|
||||||
|
this._tray.setContextMenu(electron.Menu.buildFromTemplate([
|
||||||
|
// {
|
||||||
|
// label: 'Check for Updates',
|
||||||
|
// click: function () {
|
||||||
|
// this._app.ame.utils.checkForUpdates(true)
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
label: `Show ${electron.app.getName()}`,
|
||||||
|
click: function () {
|
||||||
|
if (typeof self._win.show === 'function') {
|
||||||
|
self._win.show();
|
||||||
|
self.SetContextMenu(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Quit',
|
||||||
|
click: function () {
|
||||||
|
self._forceQuit = true; self._app.quit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs on app ready
|
||||||
|
*/
|
||||||
|
onReady(win: any): void {
|
||||||
|
this._win = win;
|
||||||
|
const winTray = electron.nativeImage.createFromPath(path.join(__dirname, `../../resources/icons/icon.ico`)).resize({
|
||||||
|
width: 32,
|
||||||
|
height: 32
|
||||||
|
})
|
||||||
|
const macTray = electron.nativeImage.createFromPath(path.join(__dirname, `../../resources/icons/icon.png`)).resize({
|
||||||
|
width: 20,
|
||||||
|
height: 20
|
||||||
|
})
|
||||||
|
const linuxTray = electron.nativeImage.createFromPath(path.join(__dirname, `../../resources/icons/icon.png`)).resize({
|
||||||
|
width: 32,
|
||||||
|
height: 32
|
||||||
|
})
|
||||||
|
let trayIcon : any ;
|
||||||
|
if (process.platform === "win32") {
|
||||||
|
trayIcon = winTray
|
||||||
|
} else if (process.platform === "linux") {
|
||||||
|
trayIcon = linuxTray
|
||||||
|
} else if (process.platform === "darwin") {
|
||||||
|
trayIcon = macTray
|
||||||
|
}
|
||||||
|
|
||||||
|
this._tray = new electron.Tray(trayIcon)
|
||||||
|
this._tray.setToolTip(this._app.getName());
|
||||||
|
this.SetContextMenu(true);
|
||||||
|
|
||||||
|
this._tray.on('double-click', () => {
|
||||||
|
if (typeof this._win.show === 'function') {
|
||||||
|
if (this._win.isVisible()) {
|
||||||
|
this._win.focus()
|
||||||
|
} else {
|
||||||
|
this._win.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
electron.ipcMain.on("minimizeTray", (event, value) => {
|
||||||
|
// listen for close event
|
||||||
|
this._win.hide();
|
||||||
|
this.SetContextMenu(false);
|
||||||
|
});
|
||||||
|
electron.ipcMain.handle("update-store-mtt", (event, value) => {
|
||||||
|
this._store.general["close_behavior"] = value;
|
||||||
|
})
|
||||||
|
this._win.on("close", (e :any) => {
|
||||||
|
if (this._forceQuit || this._store.general["close_behavior"] == '0' ) {
|
||||||
|
this._app.quit();
|
||||||
|
} else if (this._store.general["close_behavior"] == '1') {
|
||||||
|
e.preventDefault();
|
||||||
|
this._win.minimize();
|
||||||
|
} else {
|
||||||
|
e.preventDefault();
|
||||||
|
this._win.hide();
|
||||||
|
this.SetContextMenu(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs on app stop
|
||||||
|
*/
|
||||||
|
onBeforeQuit(): void {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs on playback State Change
|
||||||
|
* @param attributes Music Attributes (attributes.state = current state)
|
||||||
|
*/
|
||||||
|
onPlaybackStateDidChange(attributes: object): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs on song change
|
||||||
|
* @param attributes Music Attributes
|
||||||
|
*/
|
||||||
|
onNowPlayingItemDidChange(attributes: object): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
196
src/main/plugins/mpris.ts
Normal file
|
@ -0,0 +1,196 @@
|
||||||
|
// @ts-ignore
|
||||||
|
import * as Player from 'mpris-service';
|
||||||
|
|
||||||
|
export default class MPRIS {
|
||||||
|
/**
|
||||||
|
* Private variables for interaction in plugins
|
||||||
|
*/
|
||||||
|
private _win: any;
|
||||||
|
private _app: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base Plugin Details (Eventually implemented into a GUI in settings)
|
||||||
|
*/
|
||||||
|
public name: string = 'MPRIS Service';
|
||||||
|
public description: string = 'Handles MPRIS service calls for Linux systems.';
|
||||||
|
public version: string = '1.0.0';
|
||||||
|
public author: string = 'Core';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MPRIS Service
|
||||||
|
*/
|
||||||
|
private mpris: any;
|
||||||
|
private mprisEvents: Object = {
|
||||||
|
"playpause": "pausePlay",
|
||||||
|
"play": "pausePlay",
|
||||||
|
"pause": "pausePlay",
|
||||||
|
"next": "nextTrack",
|
||||||
|
"previous": "previousTrack",
|
||||||
|
}
|
||||||
|
|
||||||
|
/*******************************************************************************************
|
||||||
|
* Private Methods
|
||||||
|
* ****************************************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs a media event
|
||||||
|
* @param type - pausePlay, nextTrack, PreviousTrack
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private runMediaEvent(type: string) {
|
||||||
|
if (this._win) {
|
||||||
|
this._win.webContents.executeJavaScript(`MusicKitInterop.${type}()`).catch(console.error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Blocks non-linux systems from running this plugin
|
||||||
|
* @private
|
||||||
|
* @decorator
|
||||||
|
*/
|
||||||
|
private static linuxOnly(_target: any, _propertyKey: string, descriptor: PropertyDescriptor) {
|
||||||
|
if (process.platform !== 'linux') {
|
||||||
|
descriptor.value = function () {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connects to MPRIS Service
|
||||||
|
*/
|
||||||
|
@MPRIS.linuxOnly
|
||||||
|
private connect() {
|
||||||
|
this.mpris = Player({
|
||||||
|
name: 'Cider',
|
||||||
|
identity: 'Cider',
|
||||||
|
supportedUriSchemes: [],
|
||||||
|
supportedMimeTypes: [],
|
||||||
|
supportedInterfaces: ['player']
|
||||||
|
});
|
||||||
|
this.mpris = Object.assign(this.mpris, {
|
||||||
|
canQuit: true,
|
||||||
|
canControl: true,
|
||||||
|
canPause: true,
|
||||||
|
canPlay: true,
|
||||||
|
canGoNext: true,
|
||||||
|
active: true
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const pos_atr = {durationInMillis: 0};
|
||||||
|
this.mpris.getPosition = function () {
|
||||||
|
const durationInMicro = pos_atr.durationInMillis * 1000;
|
||||||
|
const percentage = parseFloat("0") || 0;
|
||||||
|
return durationInMicro * percentage;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(this.mprisEvents)) {
|
||||||
|
this.mpris.on(key, () => {
|
||||||
|
this.runMediaEvent(value)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update MPRIS Player Attributes
|
||||||
|
*/
|
||||||
|
@MPRIS.linuxOnly
|
||||||
|
private updatePlayer(attributes: any) {
|
||||||
|
|
||||||
|
const MetaData = {
|
||||||
|
'mpris:trackid': this.mpris.objectPath(`track/${attributes.playParams.id.replace(/[.]+/g, "")}`),
|
||||||
|
'mpris:length': attributes.durationInMillis * 1000, // In microseconds
|
||||||
|
'mpris:artUrl': (attributes.artwork.url.replace('/{w}x{h}bb', '/512x512bb')).replace('/2000x2000bb', '/35x35bb'),
|
||||||
|
'xesam:title': `${attributes.name}`,
|
||||||
|
'xesam:album': `${attributes.albumName}`,
|
||||||
|
'xesam:artist': [`${attributes.artistName}`,],
|
||||||
|
'xesam:genre': attributes.genreNames
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.mpris.metadata["mpris:trackid"] === MetaData["mpris:trackid"]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.mpris.metadata = MetaData
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update MPRIS Player State
|
||||||
|
* @private
|
||||||
|
* @param attributes
|
||||||
|
*/
|
||||||
|
@MPRIS.linuxOnly
|
||||||
|
private updatePlayerState(attributes: any) {
|
||||||
|
|
||||||
|
let status = 'Stopped';
|
||||||
|
if (attributes.status) {
|
||||||
|
status = 'Playing';
|
||||||
|
} else if (attributes.status === false) {
|
||||||
|
status = 'Paused';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.mpris.playbackStatus === status) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.mpris.playbackStatus = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear state
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private clearState() {
|
||||||
|
this.mpris.metadata = {'mpris:trackid': '/org/mpris/MediaPlayer2/TrackList/NoTrack'}
|
||||||
|
this.mpris.playbackStatus = 'Stopped';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************************
|
||||||
|
* Public Methods
|
||||||
|
* ****************************************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs on plugin load (Currently run on application start)
|
||||||
|
*/
|
||||||
|
constructor(app: any, _store: any) {
|
||||||
|
this._app = app;
|
||||||
|
console.debug(`[Plugin][${this.name}] Loading Complete.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs on app ready
|
||||||
|
*/
|
||||||
|
onReady(win: any): void {
|
||||||
|
this._win = win;
|
||||||
|
console.debug(`[Plugin][${this.name}] Ready.`);
|
||||||
|
this.connect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs on app stop
|
||||||
|
*/
|
||||||
|
onBeforeQuit(): void {
|
||||||
|
console.debug(`[Plugin][${this.name}] Stopped.`);
|
||||||
|
this.clearState()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs on playback State Change
|
||||||
|
* @param attributes Music Attributes (attributes.state = current state)
|
||||||
|
*/
|
||||||
|
onPlaybackStateDidChange(attributes: object): void {
|
||||||
|
this.updatePlayerState(attributes)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs on song change
|
||||||
|
* @param attributes Music Attributes
|
||||||
|
*/
|
||||||
|
onNowPlayingItemDidChange(attributes: object): void {
|
||||||
|
this.updatePlayer(attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,5 +1,4 @@
|
||||||
const electron = require('electron')
|
global.ipcRenderer = require('electron').ipcRenderer;
|
||||||
|
|
||||||
console.log('Loaded Preload')
|
console.log('Loaded Preload')
|
||||||
|
|
||||||
let cache = {playParams: {id: 0}, status: null, remainingTime: 0},
|
let cache = {playParams: {id: 0}, status: null, remainingTime: 0},
|
||||||
|
@ -9,16 +8,22 @@ const MusicKitInterop = {
|
||||||
init: function () {
|
init: function () {
|
||||||
MusicKit.getInstance().addEventListener(MusicKit.Events.playbackStateDidChange, () => {
|
MusicKit.getInstance().addEventListener(MusicKit.Events.playbackStateDidChange, () => {
|
||||||
if (MusicKitInterop.filterTrack(MusicKitInterop.getAttributes(), true, false)) {
|
if (MusicKitInterop.filterTrack(MusicKitInterop.getAttributes(), true, false)) {
|
||||||
console.log("ayy");
|
|
||||||
global.ipcRenderer.send('playbackStateDidChange', MusicKitInterop.getAttributes())
|
global.ipcRenderer.send('playbackStateDidChange', MusicKitInterop.getAttributes())
|
||||||
|
ipcRenderer.send('wsapi-updatePlaybackState', MusicKitInterop.getAttributes());
|
||||||
// if (typeof _plugins != "undefined") {
|
// if (typeof _plugins != "undefined") {
|
||||||
// _plugins.execute("OnPlaybackStateChanged", {Attributes: MusicKitInterop.getAttributes()})
|
// _plugins.execute("OnPlaybackStateChanged", {Attributes: MusicKitInterop.getAttributes()})
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** wsapi */
|
||||||
|
MusicKit.getInstance().addEventListener(MusicKit.Events.playbackProgressDidChange, () => {
|
||||||
|
ipcRenderer.send('wsapi-updatePlaybackState', MusicKitInterop.getAttributes());
|
||||||
|
});
|
||||||
|
/** wsapi */
|
||||||
|
|
||||||
MusicKit.getInstance().addEventListener(MusicKit.Events.nowPlayingItemDidChange, () => {
|
MusicKit.getInstance().addEventListener(MusicKit.Events.nowPlayingItemDidChange, () => {
|
||||||
if (MusicKitInterop.filterTrack(MusicKitInterop.getAttributes(), false, true)) {
|
if (MusicKitInterop.filterTrack(MusicKitInterop.getAttributes(), false, true) || !app.cfg.lastfm.filterLoop) {
|
||||||
global.ipcRenderer.send('nowPlayingItemDidChange', MusicKitInterop.getAttributes());
|
global.ipcRenderer.send('nowPlayingItemDidChange', MusicKitInterop.getAttributes());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -33,17 +38,25 @@ const MusicKitInterop = {
|
||||||
},
|
},
|
||||||
|
|
||||||
getAttributes: function () {
|
getAttributes: function () {
|
||||||
const nowPlayingItem = MusicKit.getInstance().nowPlayingItem;
|
const mk = MusicKit.getInstance()
|
||||||
const isPlayingExport = MusicKit.getInstance().isPlaying;
|
const nowPlayingItem = mk.nowPlayingItem;
|
||||||
const remainingTimeExport = MusicKit.getInstance().currentPlaybackTimeRemaining;
|
const isPlayingExport = mk.isPlaying;
|
||||||
|
const remainingTimeExport = mk.currentPlaybackTimeRemaining;
|
||||||
const attributes = (nowPlayingItem != null ? nowPlayingItem.attributes : {});
|
const attributes = (nowPlayingItem != null ? nowPlayingItem.attributes : {});
|
||||||
|
|
||||||
attributes.status = isPlayingExport ?? false;
|
attributes.status = isPlayingExport ?? false;
|
||||||
attributes.name = attributes?.name ?? 'No Title Found';
|
attributes.name = attributes?.name ?? 'No Title Found';
|
||||||
attributes.artwork = attributes?.artwork ?? {url: ''};
|
attributes.artwork = attributes?.artwork ?? {url: ''};
|
||||||
attributes.artwork.url = attributes?.artwork?.url ?? '';
|
attributes.artwork.url = (attributes?.artwork?.url ?? '').replace(`{f}`, "png");
|
||||||
attributes.playParams = attributes?.playParams ?? {id: 'no-id-found'};
|
attributes.playParams = attributes?.playParams ?? {id: 'no-id-found'};
|
||||||
attributes.playParams.id = attributes?.playParams?.id ?? 'no-id-found';
|
attributes.playParams.id = attributes?.playParams?.id ?? 'no-id-found';
|
||||||
|
attributes.url = {
|
||||||
|
cider: "cider://play/s/" + nowPlayingItem?._songId ?? 'no-id-found',
|
||||||
|
appleMusic: "https://music.apple.com/song/" + nowPlayingItem?._songId ?? 'no-id-found'
|
||||||
|
}
|
||||||
|
if (attributes.playParams.id === 'no-id-found') {
|
||||||
|
attributes.playParams.id = nowPlayingItem?.id ?? 'no-id-found';
|
||||||
|
}
|
||||||
attributes.albumName = attributes?.albumName ?? '';
|
attributes.albumName = attributes?.albumName ?? '';
|
||||||
attributes.artistName = attributes?.artistName ?? '';
|
attributes.artistName = attributes?.artistName ?? '';
|
||||||
attributes.genreNames = attributes?.genreNames ?? [];
|
attributes.genreNames = attributes?.genreNames ?? [];
|
||||||
|
@ -57,7 +70,6 @@ const MusicKitInterop = {
|
||||||
? Date.now() + attributes?.remainingTime
|
? Date.now() + attributes?.remainingTime
|
||||||
: attributes?.startTime + attributes?.durationInMillis
|
: attributes?.startTime + attributes?.durationInMillis
|
||||||
);
|
);
|
||||||
|
|
||||||
return attributes;
|
return attributes;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -97,6 +109,5 @@ const MusicKitInterop = {
|
||||||
|
|
||||||
process.once('loaded', () => {
|
process.once('loaded', () => {
|
||||||
console.log("Setting ipcRenderer")
|
console.log("Setting ipcRenderer")
|
||||||
global.ipcRenderer = electron.ipcRenderer;
|
|
||||||
global.MusicKitInterop = MusicKitInterop;
|
global.MusicKitInterop = MusicKitInterop;
|
||||||
});
|
});
|
5
src/renderer/.jsbeautifyrc
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"js": {
|
||||||
|
"beautify.ignore": "src/renderer/index.js"
|
||||||
|
}
|
||||||
|
}
|
4
src/renderer/AppHeader.svg
Normal file
After Width: | Height: | Size: 14 KiB |
|
@ -27,6 +27,7 @@
|
||||||
|
|
||||||
.md-option-segment.md-option-segment_auto {
|
.md-option-segment.md-option-segment_auto {
|
||||||
width: auto;
|
width: auto;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.md-option-container .md-option-line:not(:last-child) {
|
.md-option-container .md-option-line:not(:last-child) {
|
||||||
|
|
1
src/renderer/assets/feather/mic.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-mic"><path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"></path><path d="M19 10v2a7 7 0 0 1-14 0v-2"></path><line x1="12" y1="19" x2="12" y2="23"></line><line x1="8" y1="23" x2="16" y2="23"></line></svg>
|
After Width: | Height: | Size: 418 B |
1
src/renderer/assets/feather/plus-circle-white.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-plus-circle"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="16"></line><line x1="8" y1="12" x2="16" y2="12"></line></svg>
|
After Width: | Height: | Size: 344 B |
1
src/renderer/assets/feather/video.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-video"><polygon points="23 7 16 12 23 17 23 7"></polygon><rect x="1" y="5" width="15" height="14" rx="2" ry="2"></rect></svg>
|
After Width: | Height: | Size: 329 B |
1
src/renderer/assets/feather/volume-2.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" class="feather feather-volume-2"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon><path d="M19.07 4.93a10 10 0 0 1 0 14.14M15.54 8.46a5 5 0 0 1 0 7.07"></path></svg>
|
After Width: | Height: | Size: 354 B |
1
src/renderer/assets/feather/volume.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" class="feather feather-volume"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon></svg>
|
After Width: | Height: | Size: 275 B |
1
src/renderer/assets/feather/x-circle-white.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-x-circle"><circle cx="12" cy="12" r="10"></circle><line x1="15" y1="9" x2="9" y2="15"></line><line x1="9" y1="9" x2="15" y2="15"></line></svg>
|
After Width: | Height: | Size: 339 B |
BIN
src/renderer/assets/feather/x-circlePng.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
1
src/renderer/assets/infinity.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512" fill="white"><!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M471.1 96C405 96 353.3 137.3 320 174.6 286.7 137.3 235 96 168.9 96 75.8 96 0 167.8 0 256s75.8 160 168.9 160c66.1 0 117.8-41.3 151.1-78.6 33.3 37.3 85 78.6 151.1 78.6 93.1 0 168.9-71.8 168.9-160S564.2 96 471.1 96zM168.9 320c-40.2 0-72.9-28.7-72.9-64s32.7-64 72.9-64c38.2 0 73.4 36.1 94 64-20.4 27.6-55.9 64-94 64zm302.2 0c-38.2 0-73.4-36.1-94-64 20.4-27.6 55.9-64 94-64 40.2 0 72.9 28.7 72.9 64s-32.7 64-72.9 64z"/></svg>
|
After Width: | Height: | Size: 684 B |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 12 KiB |
8
src/renderer/assets/pip.svg
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 24.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
fill="white" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
||||||
|
<path id="XMLID_11_" d="M418.5,139.4H232.4v139.8h186.1V139.4z M464.8,46.7H46.3C20.5,46.7,0,68.1,0,93.1v325.9
|
||||||
|
c0,25.8,21.4,46.3,46.3,46.3h419.4c25.8,0,46.3-20.5,46.3-46.3V93.1C512,67.2,490.6,46.7,464.8,46.7z M464.8,418.9H46.3V92.2h419.4
|
||||||
|
v326.8H464.8z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 627 B |
33
src/renderer/assets/repeatOne.svg
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
<svg width="512" height="512" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" fill="white" version="1.1">
|
||||||
|
|
||||||
|
<g>
|
||||||
|
<title>Layer 1</title>
|
||||||
|
<g id="svg_1">
|
||||||
|
<g id="svg_2">
|
||||||
|
<g id="svg_3">
|
||||||
|
<path id="svg_4" d="m482.197,374.266l-78.717,-45.448c-15.89,-9.174 -35.829,2.308 -35.829,20.686l0,27.587l-216.593,0c-63.597,-0.001 -115.337,-51.74 -115.337,-115.338c0,-9.864 -7.997,-17.86 -17.86,-17.86c-9.864,0 -17.86,7.997 -17.86,17.86c0,83.294 67.765,151.058 151.058,151.058l216.591,0l0,27.587c0,18.347 19.913,29.876 35.829,20.686l78.717,-45.447c15.89,-9.172 15.917,-32.181 0.001,-41.371z"/>
|
||||||
|
<path id="svg_5" d="m360.942,99.189l-216.593,0l0,-27.588c0,-18.347 -19.913,-29.876 -35.829,-20.686l-78.717,45.447c-15.889,9.173 -15.917,32.182 0,41.372l78.717,45.448c15.89,9.174 35.829,-2.309 35.829,-20.686l0,-27.587l216.593,0c63.598,0 115.337,51.739 115.337,115.337c0,9.864 7.997,17.86 17.86,17.86c9.864,0 17.86,-7.997 17.86,-17.86c0.001,-83.293 -67.764,-151.057 -151.057,-151.057z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g id="svg_6"/>
|
||||||
|
<g id="svg_7"/>
|
||||||
|
<g id="svg_8"/>
|
||||||
|
<g id="svg_9"/>
|
||||||
|
<g id="svg_10"/>
|
||||||
|
<g id="svg_11"/>
|
||||||
|
<g id="svg_12"/>
|
||||||
|
<g id="svg_13"/>
|
||||||
|
<g id="svg_14"/>
|
||||||
|
<g id="svg_15"/>
|
||||||
|
<g id="svg_16"/>
|
||||||
|
<g id="svg_17"/>
|
||||||
|
<g id="svg_18"/>
|
||||||
|
<g id="svg_19"/>
|
||||||
|
<g id="svg_20"/>
|
||||||
|
<g id="svg_24">
|
||||||
|
<path id="svg_21" d="m118,511.5c-65.19337,0 -118,-52.80663 -118,-118c0,-65.19337 52.80663,-118 118,-118c65.19337,0 118,52.80663 118,118c0,65.19337 -52.80663,118 -118,118z" opacity="undefined" stroke-width="0" stroke="#000" fill="#fff"/>
|
||||||
|
<text font-weight="bold" xml:space="preserve" text-anchor="start" font-family="Noto Sans JP" font-size="250" id="svg_23" y="470" x="54.64063" stroke-width="0" stroke="#000" fill="#000000">1</text>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
|
@ -5,6 +5,8 @@ var CiderAudio = {
|
||||||
gainNode : null,
|
gainNode : null,
|
||||||
spatialNode : null,
|
spatialNode : null,
|
||||||
spatialInput: null,
|
spatialInput: null,
|
||||||
|
audioBands : null,
|
||||||
|
preampNode : null,
|
||||||
},
|
},
|
||||||
init: function (cb = function () { }) {
|
init: function (cb = function () { }) {
|
||||||
//AudioOutputs.fInit = true;
|
//AudioOutputs.fInit = true;
|
||||||
|
@ -21,8 +23,14 @@ var CiderAudio = {
|
||||||
},
|
},
|
||||||
off: function(){
|
off: function(){
|
||||||
try{
|
try{
|
||||||
CiderAudio.audioNodes.gainNode.disconnect();
|
try{
|
||||||
CiderAudio.audioNodes.spatialNode.disconnect();
|
CiderAudio.audioNodes.gainNode.disconnect(); } catch(e){}
|
||||||
|
try{ CiderAudio.audioNodes.spatialNode.disconnect();} catch(e){}
|
||||||
|
try{
|
||||||
|
CiderAudio.audioNodes.preampNode.disconnect();
|
||||||
|
CiderAudio.audioNodes.audioBands[0].disconnect();
|
||||||
|
CiderAudio.audioNodes.audioBands[9].disconnect();
|
||||||
|
} catch(e){}
|
||||||
CiderAudio.source.connect(CiderAudio.context.destination);} catch(e){}
|
CiderAudio.source.connect(CiderAudio.context.destination);} catch(e){}
|
||||||
},
|
},
|
||||||
connectContext: function (mediaElem){
|
connectContext: function (mediaElem){
|
||||||
|
@ -42,6 +50,7 @@ var CiderAudio = {
|
||||||
if (app.cfg.audio.spatial){
|
if (app.cfg.audio.spatial){
|
||||||
CiderAudio.spatialOn()
|
CiderAudio.spatialOn()
|
||||||
}
|
}
|
||||||
|
CiderAudio.equalizer()
|
||||||
},
|
},
|
||||||
normalizerOn: function (){},
|
normalizerOn: function (){},
|
||||||
normalizerOff: function (){
|
normalizerOff: function (){
|
||||||
|
@ -49,7 +58,7 @@ var CiderAudio = {
|
||||||
},
|
},
|
||||||
spatialOn: function (){
|
spatialOn: function (){
|
||||||
try{
|
try{
|
||||||
CiderAudio.audioNodes.gainNode.connect(CiderAudio.context.destination);} catch(e){}
|
CiderAudio.audioNodes.gainNode.disconnect(CiderAudio.context.destination);} catch(e){}
|
||||||
CiderAudio.audioNodes.spatialNode = new ResonanceAudio(CiderAudio.context);
|
CiderAudio.audioNodes.spatialNode = new ResonanceAudio(CiderAudio.context);
|
||||||
CiderAudio.audioNodes.spatialNode.output.connect(CiderAudio.context.destination);
|
CiderAudio.audioNodes.spatialNode.output.connect(CiderAudio.context.destination);
|
||||||
let roomDimensions = {
|
let roomDimensions = {
|
||||||
|
@ -90,6 +99,42 @@ var CiderAudio = {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
equalizer: function (){
|
||||||
|
let BANDS = app.cfg.audio.equalizer.frequencies;
|
||||||
|
let GAIN = app.cfg.audio.equalizer.gain;
|
||||||
|
let Q = app.cfg.audio.equalizer.Q;
|
||||||
|
CiderAudio.audioNodes.audioBands = [];
|
||||||
|
|
||||||
|
for (i = 0; i < BANDS.length; i++) {
|
||||||
|
CiderAudio.audioNodes.audioBands[i] = CiderAudio.context.createBiquadFilter();
|
||||||
|
CiderAudio.audioNodes.audioBands[i].type = 'peaking'; // 'peaking';
|
||||||
|
CiderAudio.audioNodes.audioBands[i].frequency.value = BANDS[i];
|
||||||
|
CiderAudio.audioNodes.audioBands[i].Q.value = Q[i];
|
||||||
|
CiderAudio.audioNodes.audioBands[i].gain.value = GAIN[i] * app.cfg.audio.equalizer.mix;
|
||||||
|
}
|
||||||
|
|
||||||
|
CiderAudio.audioNodes.preampNode = CiderAudio.context.createBiquadFilter();
|
||||||
|
CiderAudio.audioNodes.preampNode.type = 'highshelf';
|
||||||
|
CiderAudio.audioNodes.preampNode.frequency.value = 0; // allow all
|
||||||
|
CiderAudio.audioNodes.preampNode.gain.value = app.cfg.audio.equalizer.preamp;
|
||||||
|
|
||||||
|
if (app.cfg.audio.spatial) {
|
||||||
|
try{
|
||||||
|
CiderAudio.audioNodes.spatialNode.output.disconnect(CiderAudio.context.destination); } catch(e){}
|
||||||
|
CiderAudio.audioNodes.spatialNode.output.connect(CiderAudio.audioNodes.preampNode);
|
||||||
|
} else {
|
||||||
|
try{
|
||||||
|
CiderAudio.audioNodes.gainNode.disconnect(CiderAudio.context.destination);} catch(e){}
|
||||||
|
CiderAudio.audioNodes.gainNode.connect(CiderAudio.audioNodes.preampNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
CiderAudio.audioNodes.preampNode.connect(CiderAudio.audioNodes.audioBands[0]);
|
||||||
|
|
||||||
|
for (i = 1; i < BANDS.length; i ++) {
|
||||||
|
CiderAudio.audioNodes.audioBands[i-1].connect(CiderAudio.audioNodes.audioBands[i]);
|
||||||
|
}
|
||||||
|
CiderAudio.audioNodes.audioBands[BANDS.length-1].connect(CiderAudio.context.destination);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
108
src/renderer/js/WSAPI_Interop.js
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
const wsapi = {
|
||||||
|
cache: {playParams: {id: 0}, status: null, remainingTime: 0},
|
||||||
|
playbackCache: {status: null, time: Date.now()},
|
||||||
|
search(term, limit) {
|
||||||
|
MusicKit.getInstance().api.search(term, {limit: limit, types: 'songs,artists,albums,playlists'}).then((results)=>{
|
||||||
|
ipcRenderer.send('wsapi-returnSearch', JSON.stringify(results))
|
||||||
|
})
|
||||||
|
},
|
||||||
|
searchLibrary(term, limit) {
|
||||||
|
MusicKit.getInstance().api.library.search(term, {limit: limit, types: 'library-songs,library-artists,library-albums,library-playlists'}).then((results)=>{
|
||||||
|
ipcRenderer.send('wsapi-returnSearchLibrary', JSON.stringify(results))
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getAttributes: function () {
|
||||||
|
const mk = MusicKit.getInstance();
|
||||||
|
const nowPlayingItem = mk.nowPlayingItem;
|
||||||
|
const isPlayingExport = mk.isPlaying;
|
||||||
|
const remainingTimeExport = mk.currentPlaybackTimeRemaining;
|
||||||
|
const attributes = (nowPlayingItem != null ? nowPlayingItem.attributes : {});
|
||||||
|
|
||||||
|
attributes.status = isPlayingExport ? isPlayingExport : false;
|
||||||
|
attributes.name = attributes.name ? attributes.name : 'No Title Found';
|
||||||
|
attributes.artwork = attributes.artwork ? attributes.artwork : {url: ''};
|
||||||
|
attributes.artwork.url = attributes.artwork.url ? attributes.artwork.url : '';
|
||||||
|
attributes.playParams = attributes.playParams ? attributes.playParams : {id: 'no-id-found'};
|
||||||
|
attributes.playParams.id = attributes.playParams.id ? attributes.playParams.id : 'no-id-found';
|
||||||
|
attributes.albumName = attributes.albumName ? attributes.albumName : '';
|
||||||
|
attributes.artistName = attributes.artistName ? attributes.artistName : '';
|
||||||
|
attributes.genreNames = attributes.genreNames ? attributes.genreNames : [];
|
||||||
|
attributes.remainingTime = remainingTimeExport ? (remainingTimeExport * 1000) : 0;
|
||||||
|
attributes.durationInMillis = attributes.durationInMillis ? attributes.durationInMillis : 0;
|
||||||
|
attributes.startTime = Date.now();
|
||||||
|
attributes.endTime = attributes.endTime ? attributes.endTime : Date.now();
|
||||||
|
attributes.volume = mk.volume;
|
||||||
|
attributes.shuffleMode = mk.shuffleMode;
|
||||||
|
attributes.repeatMode = mk.repeatMode;
|
||||||
|
attributes.autoplayEnabled = mk.autoplayEnabled;
|
||||||
|
return attributes
|
||||||
|
},
|
||||||
|
moveQueueItem(oldPosition, newPosition) {
|
||||||
|
MusicKit.getInstance().queue._queueItems.splice(newPosition,0,MusicKit.getInstance().queue._queueItems.splice(oldPosition,1)[0])
|
||||||
|
MusicKit.getInstance().queue._reindex()
|
||||||
|
},
|
||||||
|
setAutoplay(value) {
|
||||||
|
MusicKit.getInstance().autoplayEnabled = value
|
||||||
|
},
|
||||||
|
returnDynamic(data, type) {
|
||||||
|
ipcRenderer.send('wsapi-returnDynamic', JSON.stringify(data), type)
|
||||||
|
},
|
||||||
|
musickitApi(method, id, params, library = false) {
|
||||||
|
if (library) {
|
||||||
|
MusicKit.getInstance().api.library[method](id, params).then((results)=>{
|
||||||
|
ipcRenderer.send('wsapi-returnMusicKitApi', JSON.stringify(results), method)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
MusicKit.getInstance().api[method](id, params).then((results)=>{
|
||||||
|
ipcRenderer.send('wsapi-returnMusicKitApi', JSON.stringify(results), method)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getPlaybackState () {
|
||||||
|
ipcRenderer.send('wsapi-updatePlaybackState', MusicKitInterop.getAttributes());
|
||||||
|
},
|
||||||
|
getLyrics() {
|
||||||
|
ipcRenderer.send('wsapi-returnLyrics',JSON.stringify(app.lyrics));
|
||||||
|
},
|
||||||
|
getQueue() {
|
||||||
|
ipcRenderer.send('wsapi-returnQueue', JSON.stringify(MusicKit.getInstance().queue))
|
||||||
|
},
|
||||||
|
playNext(type, id) {
|
||||||
|
var request = {}
|
||||||
|
request[type] = id
|
||||||
|
MusicKit.getInstance().playNext(request)
|
||||||
|
},
|
||||||
|
playLater(type, id) {
|
||||||
|
var request = {}
|
||||||
|
request[type] = id
|
||||||
|
MusicKit.getInstance().playLater(request)
|
||||||
|
},
|
||||||
|
love() {
|
||||||
|
|
||||||
|
},
|
||||||
|
playTrackById(id, kind = "song") {
|
||||||
|
MusicKit.getInstance().setQueue({ [kind]: id }).then(function (queue) {
|
||||||
|
MusicKit.getInstance().play()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
quickPlay(term) {
|
||||||
|
// Quick play by song name
|
||||||
|
MusicKit.getInstance().api.search(term, { limit: 2, types: 'songs' }).then(function (data) {
|
||||||
|
MusicKit.getInstance().setQueue({ song: data["songs"][0]["id"] }).then(function (queue) {
|
||||||
|
MusicKit.getInstance().play()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
toggleShuffle() {
|
||||||
|
MusicKit.getInstance().shuffleMode = MusicKit.getInstance().shuffleMode === 0 ? 1 : 0
|
||||||
|
},
|
||||||
|
toggleRepeat() {
|
||||||
|
if(MusicKit.getInstance().repeatMode == 0) {
|
||||||
|
MusicKit.getInstance().repeatMode = 1
|
||||||
|
}else if(MusicKit.getInstance().repeatMode == 1){
|
||||||
|
MusicKit.getInstance().repeatMode = 2
|
||||||
|
}else{
|
||||||
|
MusicKit.getInstance().repeatMode = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1
src/renderer/js/bootbox.min.js
vendored
Normal file
7
src/renderer/js/bootstrap.min.js
vendored
Normal file
4
src/renderer/js/jquery-3.2.1.slim.min.js
vendored
Normal file
1
src/renderer/js/notyf.min.js
vendored
Normal file
5
src/renderer/js/popper.min.js
vendored
Normal file
1107
src/renderer/less/bootstrap.less
vendored
Normal file
|
@ -45,7 +45,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 951px) {
|
@media (max-width: 951px) {
|
||||||
.content-inner {
|
#app-content {
|
||||||
zoom: 0.8;
|
zoom: 0.8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@
|
||||||
|
|
||||||
// if page width is less than 951px
|
// if page width is less than 951px
|
||||||
@media (max-width: 951px) {
|
@media (max-width: 951px) {
|
||||||
.content-inner {
|
#app-content {
|
||||||
zoom: 0.8;
|
zoom: 0.8;
|
||||||
}
|
}
|
||||||
}
|
}
|
370
src/renderer/less/notyf.less
Normal file
|
@ -0,0 +1,370 @@
|
||||||
|
@-webkit-keyframes notyf-fadeinup {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(25%)
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes notyf-fadeinup {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(25%)
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes notyf-fadeinleft {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(25%)
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes notyf-fadeinleft {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(25%)
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes notyf-fadeoutright {
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(25%)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes notyf-fadeoutright {
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(25%)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes notyf-fadeoutdown {
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(25%)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes notyf-fadeoutdown {
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(25%)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes ripple {
|
||||||
|
0% {
|
||||||
|
transform: scale(0) translateY(-45%) translateX(13%)
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: scale(1) translateY(-45%) translateX(13%)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes ripple {
|
||||||
|
0% {
|
||||||
|
transform: scale(0) translateY(-45%) translateX(13%)
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: scale(1) translateY(-45%) translateX(13%)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.notyf {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
color: #fff;
|
||||||
|
z-index: 9999;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: flex-end;
|
||||||
|
pointer-events: none;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 20px
|
||||||
|
}
|
||||||
|
|
||||||
|
.notyf__icon--error,
|
||||||
|
.notyf__icon--success {
|
||||||
|
height: 21px;
|
||||||
|
width: 21px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
position: relative
|
||||||
|
}
|
||||||
|
|
||||||
|
.notyf__icon--error:after,
|
||||||
|
.notyf__icon--error:before {
|
||||||
|
content: "";
|
||||||
|
background: currentColor;
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
width: 3px;
|
||||||
|
border-radius: 3px;
|
||||||
|
left: 9px;
|
||||||
|
height: 12px;
|
||||||
|
top: 5px
|
||||||
|
}
|
||||||
|
|
||||||
|
.notyf__icon--error:after {
|
||||||
|
transform: rotate(-45deg)
|
||||||
|
}
|
||||||
|
|
||||||
|
.notyf__icon--error:before {
|
||||||
|
transform: rotate(45deg)
|
||||||
|
}
|
||||||
|
|
||||||
|
.notyf__icon--success:after,
|
||||||
|
.notyf__icon--success:before {
|
||||||
|
content: "";
|
||||||
|
background: currentColor;
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
width: 3px;
|
||||||
|
border-radius: 3px
|
||||||
|
}
|
||||||
|
|
||||||
|
.notyf__icon--success:after {
|
||||||
|
height: 6px;
|
||||||
|
transform: rotate(-45deg);
|
||||||
|
top: 9px;
|
||||||
|
left: 6px
|
||||||
|
}
|
||||||
|
|
||||||
|
.notyf__icon--success:before {
|
||||||
|
height: 11px;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
top: 5px;
|
||||||
|
left: 10px
|
||||||
|
}
|
||||||
|
|
||||||
|
.notyf__toast {
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
pointer-events: auto;
|
||||||
|
-webkit-animation: notyf-fadeinup .3s ease-in forwards;
|
||||||
|
animation: notyf-fadeinup .3s ease-in forwards;
|
||||||
|
box-shadow: 0 3px 7px 0 rgba(0, 0, 0, .25);
|
||||||
|
position: relative;
|
||||||
|
padding: 0 15px;
|
||||||
|
border-radius: 2px;
|
||||||
|
max-width: 300px;
|
||||||
|
transform: translateY(25%);
|
||||||
|
box-sizing: border-box;
|
||||||
|
flex-shrink: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.notyf__toast--disappear {
|
||||||
|
transform: translateY(0);
|
||||||
|
-webkit-animation: notyf-fadeoutdown .3s forwards;
|
||||||
|
animation: notyf-fadeoutdown .3s forwards;
|
||||||
|
-webkit-animation-delay: .25s;
|
||||||
|
animation-delay: .25s
|
||||||
|
}
|
||||||
|
|
||||||
|
.notyf__toast--disappear .notyf__icon,
|
||||||
|
.notyf__toast--disappear .notyf__message {
|
||||||
|
-webkit-animation: notyf-fadeoutdown .3s forwards;
|
||||||
|
animation: notyf-fadeoutdown .3s forwards;
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
.notyf__toast--disappear .notyf__dismiss {
|
||||||
|
-webkit-animation: notyf-fadeoutright .3s forwards;
|
||||||
|
animation: notyf-fadeoutright .3s forwards;
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
.notyf__toast--disappear .notyf__message {
|
||||||
|
-webkit-animation-delay: .05s;
|
||||||
|
animation-delay: .05s
|
||||||
|
}
|
||||||
|
|
||||||
|
.notyf__toast--upper {
|
||||||
|
margin-bottom: 20px
|
||||||
|
}
|
||||||
|
|
||||||
|
.notyf__toast--lower {
|
||||||
|
margin-top: 20px
|
||||||
|
}
|
||||||
|
|
||||||
|
.notyf__toast--dismissible .notyf__wrapper {
|
||||||
|
padding-right: 30px
|
||||||
|
}
|
||||||
|
|
||||||
|
.notyf__ripple {
|
||||||
|
height: 400px;
|
||||||
|
width: 400px;
|
||||||
|
position: absolute;
|
||||||
|
transform-origin: bottom right;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
border-radius: 50%;
|
||||||
|
transform: scale(0) translateY(-51%) translateX(13%);
|
||||||
|
z-index: 5;
|
||||||
|
-webkit-animation: ripple .4s ease-out forwards;
|
||||||
|
animation: ripple .4s ease-out forwards
|
||||||
|
}
|
||||||
|
|
||||||
|
.notyf__wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-top: 17px;
|
||||||
|
padding-bottom: 17px;
|
||||||
|
padding-right: 15px;
|
||||||
|
border-radius: 3px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 10
|
||||||
|
}
|
||||||
|
|
||||||
|
.notyf__icon {
|
||||||
|
width: 22px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 1.3em;
|
||||||
|
opacity: 0;
|
||||||
|
-webkit-animation: notyf-fadeinup .3s forwards;
|
||||||
|
animation: notyf-fadeinup .3s forwards;
|
||||||
|
-webkit-animation-delay: .3s;
|
||||||
|
animation-delay: .3s;
|
||||||
|
margin-right: 13px
|
||||||
|
}
|
||||||
|
|
||||||
|
.notyf__dismiss {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 26px;
|
||||||
|
margin-right: -15px;
|
||||||
|
-webkit-animation: notyf-fadeinleft .3s forwards;
|
||||||
|
animation: notyf-fadeinleft .3s forwards;
|
||||||
|
-webkit-animation-delay: .35s;
|
||||||
|
animation-delay: .35s;
|
||||||
|
opacity: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.notyf__dismiss-btn {
|
||||||
|
background-color: rgba(0, 0, 0, .25);
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: opacity .2s ease, background-color .2s ease;
|
||||||
|
outline: none;
|
||||||
|
opacity: .35;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%
|
||||||
|
}
|
||||||
|
|
||||||
|
.notyf__dismiss-btn:after,
|
||||||
|
.notyf__dismiss-btn:before {
|
||||||
|
content: "";
|
||||||
|
background: #fff;
|
||||||
|
height: 12px;
|
||||||
|
width: 2px;
|
||||||
|
border-radius: 3px;
|
||||||
|
position: absolute;
|
||||||
|
left: calc(50% - 1px);
|
||||||
|
top: calc(50% - 5px)
|
||||||
|
}
|
||||||
|
|
||||||
|
.notyf__dismiss-btn:after {
|
||||||
|
transform: rotate(-45deg)
|
||||||
|
}
|
||||||
|
|
||||||
|
.notyf__dismiss-btn:before {
|
||||||
|
transform: rotate(45deg)
|
||||||
|
}
|
||||||
|
|
||||||
|
.notyf__dismiss-btn:hover {
|
||||||
|
opacity: .7;
|
||||||
|
background-color: rgba(0, 0, 0, .15)
|
||||||
|
}
|
||||||
|
|
||||||
|
.notyf__dismiss-btn:active {
|
||||||
|
opacity: .8
|
||||||
|
}
|
||||||
|
|
||||||
|
.notyf__message {
|
||||||
|
vertical-align: middle;
|
||||||
|
position: relative;
|
||||||
|
opacity: 0;
|
||||||
|
-webkit-animation: notyf-fadeinup .3s forwards;
|
||||||
|
animation: notyf-fadeinup .3s forwards;
|
||||||
|
-webkit-animation-delay: .25s;
|
||||||
|
animation-delay: .25s;
|
||||||
|
line-height: 1.5em
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width:480px) {
|
||||||
|
.notyf {
|
||||||
|
padding: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.notyf__ripple {
|
||||||
|
height: 600px;
|
||||||
|
width: 600px;
|
||||||
|
-webkit-animation-duration: .5s;
|
||||||
|
animation-duration: .5s
|
||||||
|
}
|
||||||
|
|
||||||
|
.notyf__toast {
|
||||||
|
max-width: none;
|
||||||
|
border-radius: 0;
|
||||||
|
box-shadow: 0 -2px 7px 0 rgba(0, 0, 0, .13);
|
||||||
|
width: 100%
|
||||||
|
}
|
||||||
|
|
||||||
|
.notyf__dismiss {
|
||||||
|
width: 56px
|
||||||
|
}
|
||||||
|
}
|
Before Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
12492
src/renderer/vibrant.min.js
vendored
|
@ -1,18 +1,15 @@
|
||||||
<script type="text/x-template" id="add-to-playlist">
|
<script type="text/x-template" id="add-to-playlist">
|
||||||
<template>
|
<template>
|
||||||
<div class="modal-fullscreen addtoplaylist-panel" @click.self="app.resetState()" @contextmenu.self="app.resetState()">
|
<div class="modal-fullscreen addtoplaylist-panel" @click.self="app.resetState()"
|
||||||
|
@contextmenu.self="app.resetState()">
|
||||||
<div class="modal-window">
|
<div class="modal-window">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<div class="modal-title">Add to Playlist</div>
|
<div class="modal-title">{{app.getLz('action.addToPlaylist')}}</div>
|
||||||
<button class="close-btn" @click="app.resetState()"></button>
|
<button class="close-btn" @click="app.resetState()"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<button class="playlist-item"
|
<sidebar-playlist :playlist-select="playlistSelect" v-for="item in $root.getPlaylistFolderChildren('p.playlistsroot')" :item="item">
|
||||||
:class="{ focused: playlist.id == focused }"
|
</sidebar-playlist>
|
||||||
@click="addToPlaylist(playlist.id)" style="width:100%;" v-for="playlist in playlistSorted" v-if="playlist.attributes.canEdit && playlist.type != 'library-playlist-folders'">
|
|
||||||
<div class="icon"><%- include("../svg/playlist.svg") %></div>
|
|
||||||
<div class="name">{{ playlist.attributes.name }}</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-search">
|
<div class="modal-search">
|
||||||
<div class="search-input-container" style="width:100%;margin: 16px 0;">
|
<div class="search-input-container" style="width:100%;margin: 16px 0;">
|
||||||
|
@ -21,7 +18,7 @@
|
||||||
ref="searchInput"
|
ref="searchInput"
|
||||||
style="width:100%;"
|
style="width:100%;"
|
||||||
spellcheck="false"
|
spellcheck="false"
|
||||||
placeholder="Search..."
|
:placeholder="app.getLz('term.search') + '...'"
|
||||||
v-model="searchQuery"
|
v-model="searchQuery"
|
||||||
@input="search()"
|
@input="search()"
|
||||||
class="search-input">
|
class="search-input">
|
||||||
|
@ -61,6 +58,11 @@
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
playlistSelect(playlist) {
|
||||||
|
if(playlist.type != "library-playlist-folders") {
|
||||||
|
this.addToPlaylist(playlist.id)
|
||||||
|
}
|
||||||
|
},
|
||||||
addToPlaylist(id) {
|
addToPlaylist(id) {
|
||||||
app.addSelectedToPlaylist(id)
|
app.addSelectedToPlaylist(id)
|
||||||
},
|
},
|
||||||
|
|
37
src/renderer/views/components/artwork-material.ejs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
<script type="text/x-template" id="artwork-material">
|
||||||
|
<div class="artworkMaterial">
|
||||||
|
<img :src="src" v-for="image in images"/>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
Vue.component('artwork-material', {
|
||||||
|
template: '#artwork-material',
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
src: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.src = app.getMediaItemArtwork(this.url, this.size)
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
url: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: [String, Number],
|
||||||
|
required: false,
|
||||||
|
default: '32'
|
||||||
|
},
|
||||||
|
images: {
|
||||||
|
type: [String, Number],
|
||||||
|
required: false,
|
||||||
|
default: '2'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
82
src/renderer/views/components/cider-modal.ejs
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
<script type="text/x-template" id="add-to-playlist">
|
||||||
|
<template>
|
||||||
|
<div class="modal-fullscreen modal-generic" @click.self="app.resetState()" @contextmenu.self="app.resetState()">
|
||||||
|
<div class="modal-window">
|
||||||
|
<div class="modal-header">
|
||||||
|
<div class="modal-title">{{app.getLz('action.addToLibrary')}}</div>
|
||||||
|
<button class="close-btn" @click="app.resetState()"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-content">
|
||||||
|
<button class="playlist-item"
|
||||||
|
:class="{ focused: playlist.id == focused }"
|
||||||
|
@click="addToPlaylist(playlist.id)" style="width:100%;" v-for="playlist in playlistSorted" v-if="playlist.attributes.canEdit && playlist.type != 'library-playlist-folders'">
|
||||||
|
<div class="icon"><%- include("../svg/playlist.svg") %></div>
|
||||||
|
<div class="name">{{ playlist.attributes.name }}</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-search">
|
||||||
|
<div class="search-input-container" style="width:100%;margin: 16px 0;">
|
||||||
|
<div class="search-input--icon"></div>
|
||||||
|
<input type="search"
|
||||||
|
ref="searchInput"
|
||||||
|
style="width:100%;"
|
||||||
|
spellcheck="false"
|
||||||
|
:placeholder="app.getLz('term.search') + '...'"
|
||||||
|
v-model="searchQuery"
|
||||||
|
@input="search()"
|
||||||
|
class="search-input">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
Vue.component('add-to-playlist', {
|
||||||
|
template: '#add-to-playlist',
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
playlistSorted: [],
|
||||||
|
searchQuery: "",
|
||||||
|
focused: "",
|
||||||
|
app: this.$root,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
playlists: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.search()
|
||||||
|
this.$refs.searchInput.focus()
|
||||||
|
this.$refs.searchInput.addEventListener('keydown', (e) => {
|
||||||
|
if (e.keyCode == 13) {
|
||||||
|
if (this.focused != "") {
|
||||||
|
this.addToPlaylist(this.focused)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
addToPlaylist(id) {
|
||||||
|
app.addSelectedToPlaylist(id)
|
||||||
|
},
|
||||||
|
search() {
|
||||||
|
this.focused = ""
|
||||||
|
if (this.searchQuery == "") {
|
||||||
|
this.playlistSorted = this.playlists
|
||||||
|
} else {
|
||||||
|
this.playlistSorted = this.playlists.filter(playlist => {
|
||||||
|
return playlist.attributes.name.toLowerCase().indexOf(this.searchQuery.toLowerCase()) > -1
|
||||||
|
})
|
||||||
|
if (this.playlistSorted.length == 1) {
|
||||||
|
this.focused = this.playlistSorted[0].id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
387
src/renderer/views/components/equalizer.ejs
Normal file
|
@ -0,0 +1,387 @@
|
||||||
|
<script type="text/x-template" id="eq-view">
|
||||||
|
<div class="modal-fullscreen equalizer-panel">
|
||||||
|
<div class="modal-window" >
|
||||||
|
<div class="modal-header">
|
||||||
|
<div class="modal-title">{{$root.getLz('term.equalizer')}}</div>
|
||||||
|
<button class="close-btn" @click="close()"></button>
|
||||||
|
<div class="md-option-segment md-option-segment_auto">
|
||||||
|
<select class="md-select" style="width:220px;text-align:center;margin-right:16.75em" v-model="$root.cfg.audio.equalizer.preset" v-on:change="changePreset($root.cfg.audio.equalizer.preset)">
|
||||||
|
<optgroup label="User Presets">
|
||||||
|
<option v-for="preset in $root.cfg.audio.equalizer.presets" :value="preset.preset">{{preset.name}}</option>
|
||||||
|
</optgroup>
|
||||||
|
<optgroup label="Default Presets">
|
||||||
|
<option v-for="preset in defaultPresets" :value="preset.preset">{{preset.name}}</option>
|
||||||
|
</optgroup>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-content">
|
||||||
|
<!-- BANDS = [60, 170, 310, 600, 1000, 3000, 6000, 12000, 14000, 16000]; -->
|
||||||
|
<div class="inputs-container">
|
||||||
|
<div class="input-container mini">
|
||||||
|
<input tabindex="0" type="number" class="eq-freq" orient="vertical" min="-12" max="12" step="0.1" v-model="$root.cfg.audio.equalizer.preamp" @change="changePreamp()">
|
||||||
|
<input tabindex="0" type="range" class="eq-slider mini" orient="vertical" min="-12" max="12" step="0.1" v-model="$root.cfg.audio.equalizer.preamp" @change="changePreamp()">
|
||||||
|
Preamp
|
||||||
|
</div>
|
||||||
|
<div class="input-container mini">
|
||||||
|
{{$root.cfg.audio.equalizer.mix}}
|
||||||
|
<input tabindex="0" type="range" class="eq-slider mini" orient="vertical" min="0" max="1" step="0.1" v-model="$root.cfg.audio.equalizer.mix" @change="changeMix()">
|
||||||
|
Mix
|
||||||
|
</div>
|
||||||
|
<div class="input-container header mini">
|
||||||
|
Gain
|
||||||
|
<input type="range" class="eq-slider" orient="vertical" min="-12" max="12" step="0.1" >
|
||||||
|
<div class="freq-header">Freq</div>
|
||||||
|
<div>Q</div>
|
||||||
|
</div>
|
||||||
|
<div class="input-container">
|
||||||
|
<input tabindex="0" type="number" class="eq-freq" orient="vertical" min="-12" max="12" step="0.1" v-model="$root.cfg.audio.equalizer.gain[0]" @change="changeGain(0)">
|
||||||
|
<input tabindex="0" type="range" class="eq-slider" orient="vertical" min="-12" max="12" step="0.1" v-model="$root.cfg.audio.equalizer.gain[0]" @change="changeGain(0)">
|
||||||
|
<input type="number" class="eq-freq" orient="vertical" min="22" max="44" step="2" v-model="$root.cfg.audio.equalizer.frequencies[0]" @change="changeFreq(0)">
|
||||||
|
<input type="number" class="eq-q" orient="vertical" min="0" max="5" step="0.1" v-model="$root.cfg.audio.equalizer.Q[0]" @change="changeQ(0)">
|
||||||
|
</div>
|
||||||
|
<div class="input-container">
|
||||||
|
<input tabindex="0" type="number" class="eq-freq" orient="vertical" min="-12" max="12" step="0.1" v-model="$root.cfg.audio.equalizer.gain[1]" @change="changeGain(1)">
|
||||||
|
<input tabindex="0" type="range" class="eq-slider" orient="vertical" min="-12" max="12" step="0.1" v-model="$root.cfg.audio.equalizer.gain[1]" @change="changeGain(1)">
|
||||||
|
<input type="number" class="eq-freq" orient="vertical" min="44" max="88" step="4" v-model="$root.cfg.audio.equalizer.frequencies[1]" @change="changeFreq(1)">
|
||||||
|
<input type="number" class="eq-q" orient="vertical" min="0" max="5" step="0.1" v-model="$root.cfg.audio.equalizer.Q[1]" @change="changeQ(1)">
|
||||||
|
</div>
|
||||||
|
<div class="input-container">
|
||||||
|
<input tabindex="0" type="number" class="eq-freq" orient="vertical" min="-12" max="12" step="0.1" v-model="$root.cfg.audio.equalizer.gain[2]" @change="changeGain(2)">
|
||||||
|
<input tabindex="0" type="range" class="eq-slider" orient="vertical" min="-12" max="12" step="0.1" v-model="$root.cfg.audio.equalizer.gain[2]" @change="changeGain(2)">
|
||||||
|
<input type="number" class="eq-freq" orient="vertical" min="88" max="177" step="8" v-model="$root.cfg.audio.equalizer.frequencies[2]" @change="changeFreq(2)">
|
||||||
|
<input type="number" class="eq-q" orient="vertical" min="0" max="5" step="0.1" v-model="$root.cfg.audio.equalizer.Q[2]" @change="changeQ(2)">
|
||||||
|
</div>
|
||||||
|
<div class="input-container">
|
||||||
|
<input tabindex="0" type="number" class="eq-freq" orient="vertical" min="-12" max="12" step="0.1" v-model="$root.cfg.audio.equalizer.gain[3]" @change="changeGain(3)">
|
||||||
|
<input tabindex="0" type="range" class="eq-slider" orient="vertical" min="-12" max="12" step="0.1" v-model="$root.cfg.audio.equalizer.gain[3]" @change="changeGain(3)">
|
||||||
|
<input type="number" class="eq-freq" orient="vertical" min="177" max="355" step="16" v-model="$root.cfg.audio.equalizer.frequencies[3]" @change="changeFreq(3)">
|
||||||
|
<input type="number" class="eq-q" orient="vertical" min="0" max="5" step="0.1" v-model="$root.cfg.audio.equalizer.Q[3]" @change="changeQ(3)">
|
||||||
|
</div>
|
||||||
|
<div class="input-container">
|
||||||
|
<input tabindex="0" type="number" class="eq-freq" orient="vertical" min="-12" max="12" step="0.1" v-model="$root.cfg.audio.equalizer.gain[4]" @change="changeGain(4)">
|
||||||
|
<input tabindex="0" type="range" class="eq-slider" orient="vertical" min="-12" max="12" step="0.1" v-model="$root.cfg.audio.equalizer.gain[4]" @change="changeGain(4)">
|
||||||
|
<input type="number" class="eq-freq" orient="vertical" min="355" max="710" step="32" v-model="$root.cfg.audio.equalizer.frequencies[4]" @change="changeFreq(4)">
|
||||||
|
<input type="number" class="eq-q" orient="vertical" min="0" max="5" step="0.1" v-model="$root.cfg.audio.equalizer.Q[4]" @change="changeQ(4)">
|
||||||
|
</div>
|
||||||
|
<div class="input-container">
|
||||||
|
<input tabindex="0" type="number" class="eq-freq" orient="vertical" min="-12" max="12" step="0.1" v-model="$root.cfg.audio.equalizer.gain[5]" @change="changeGain(5)">
|
||||||
|
<input tabindex="0" type="range" class="eq-slider" orient="vertical" min="-12" max="12" step="0.1" v-model="$root.cfg.audio.equalizer.gain[5]" @change="changeGain(5)">
|
||||||
|
<input type="number" class="eq-freq" orient="vertical" min="710" max="1420" step="64" v-model="$root.cfg.audio.equalizer.frequencies[5]" @change="changeFreq(5)">
|
||||||
|
<input type="number" class="eq-q" orient="vertical" min="0" max="5" step="0.1" v-model="$root.cfg.audio.equalizer.Q[5]" @change="changeQ(5)">
|
||||||
|
</div>
|
||||||
|
<div class="input-container">
|
||||||
|
<input tabindex="0" type="number" class="eq-freq" orient="vertical" min="-12" max="12" step="0.1" v-model="$root.cfg.audio.equalizer.gain[6]" @change="changeGain(6)">
|
||||||
|
<input tabindex="0" type="range" class="eq-slider" orient="vertical" min="-12" max="12" step="0.1" v-model="$root.cfg.audio.equalizer.gain[6]" @change="changeGain(6)">
|
||||||
|
<input type="number" class="eq-freq" orient="vertical" min="1420" max="2840" step="128" v-model="$root.cfg.audio.equalizer.frequencies[6]" @change="changeFreq(6)">
|
||||||
|
<input type="number" class="eq-q" orient="vertical" min="0" max="5" step="0.1" v-model="$root.cfg.audio.equalizer.Q[6]" @change="changeQ(6)">
|
||||||
|
</div>
|
||||||
|
<div class="input-container">
|
||||||
|
<input tabindex="0" type="number" class="eq-freq" orient="vertical" min="-12" max="12" step="0.1" v-model="$root.cfg.audio.equalizer.gain[7]" @change="changeGain(7)">
|
||||||
|
<input tabindex="0" type="range" class="eq-slider" orient="vertical" min="-12" max="12" step="0.1" v-model="$root.cfg.audio.equalizer.gain[7]" @change="changeGain(7)">
|
||||||
|
<input type="number" class="eq-freq" orient="vertical" min="2840" max="5680" step="256" v-model="$root.cfg.audio.equalizer.frequencies[7]" @change="changeFreq(7)">
|
||||||
|
<input type="number" class="eq-q" orient="vertical" min="0" max="5" step="0.1" v-model="$root.cfg.audio.equalizer.Q[7]" @change="changeQ(7)">
|
||||||
|
</div>
|
||||||
|
<div class="input-container">
|
||||||
|
<input tabindex="0" type="number" class="eq-freq" orient="vertical" min="-12" max="12" step="0.1" v-model="$root.cfg.audio.equalizer.gain[8]" @change="changeGain(8)">
|
||||||
|
<input tabindex="0" type="range" class="eq-slider" orient="vertical" min="-12" max="12" step="0.1" v-model="$root.cfg.audio.equalizer.gain[8]" @change="changeGain(8)">
|
||||||
|
<input type="number" class="eq-freq" orient="vertical" min="5680" max="11360" step="512" v-model="$root.cfg.audio.equalizer.frequencies[8]" @change="changeFreq(8)">
|
||||||
|
<input type="number" class="eq-q" orient="vertical" min="0" max="5" step="0.1" v-model="$root.cfg.audio.equalizer.Q[8]" @change="changeQ(8)">
|
||||||
|
</div>
|
||||||
|
<div class="input-container">
|
||||||
|
<input tabindex="0" type="number" class="eq-freq" orient="vertical" min="-12" max="12" step="0.1" v-model="$root.cfg.audio.equalizer.gain[9]" @change="changeGain(9)">
|
||||||
|
<input tabindex="0" type="range" class="eq-slider" orient="vertical" min="-12" max="12" step="0.1" v-model="$root.cfg.audio.equalizer.gain[9]" @change="changeGain(9)">
|
||||||
|
<input type="number" class="eq-freq" orient="vertical" min="11360" max="22720" step="1024" v-model="$root.cfg.audio.equalizer.frequencies[9]" @change="changeFreq(9)">
|
||||||
|
<input type="number" class="eq-q" orient="vertical" min="0" max="5" step="0.1" v-model="$root.cfg.audio.equalizer.Q[9]" @change="changeQ(9)">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="modal-lowercontent">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<button class="md-btn" style="width:100%" @click="resetGain()">{{$root.getLz('term.reset')}}</button>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<button class="md-btn" style="width:100%" @click="presetOptions($event)">Menu</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
Vue.component('eq-view', {
|
||||||
|
template: '#eq-view',
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
// app: this.$root,
|
||||||
|
eqPreset: function () {
|
||||||
|
this.preset = uuidv4()
|
||||||
|
this.name = ""
|
||||||
|
this.frequencies = []
|
||||||
|
this.gain = []
|
||||||
|
this.Q = []
|
||||||
|
this.preamp = 0
|
||||||
|
this.mix = 1
|
||||||
|
this.userGenerated = true
|
||||||
|
},
|
||||||
|
defaultPresets: [{
|
||||||
|
'preset': 'default',
|
||||||
|
'name': 'Default',
|
||||||
|
'frequencies': [32, 63, 125, 250, 500, 1000, 2000, 4000, 8000, 16000],
|
||||||
|
'gain': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
'Q': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||||
|
'preamp': 0,
|
||||||
|
'mix': 1,
|
||||||
|
'userGenerated': false
|
||||||
|
}, {
|
||||||
|
'preset': 'warmth',
|
||||||
|
'name': 'Warmth',
|
||||||
|
'frequencies': [32, 75, 125, 197, 500, 1000, 2000, 3040, 8000, 16000],
|
||||||
|
'gain': [0, 2.1, 0, 0.8, 0, 0, 0, -1.5, 0, 0],
|
||||||
|
'Q': [1, 0.7, 1, 1.5, 1, 1, 1, 2, 1, 1],
|
||||||
|
'preamp': 0,
|
||||||
|
'mix': 1,
|
||||||
|
'userGenerated': false
|
||||||
|
}, {
|
||||||
|
'preset': 'boostBrightness',
|
||||||
|
'name': 'Boost Brightness',
|
||||||
|
'frequencies': [32, 63, 125, 250, 466, 1000, 2000, 4000, 8000, 20000],
|
||||||
|
'gain': [0, 0, 0, 0, -2, 0, 0, 0, 0, 10],
|
||||||
|
'Q': [1, 1, 1, 1, 0.6, 1, 1, 1, 1, 0.1],
|
||||||
|
'preamp': 0,
|
||||||
|
'mix': 1,
|
||||||
|
'userGenerated': false
|
||||||
|
}, {
|
||||||
|
'preset': 'acoustic',
|
||||||
|
'name': 'Acoustic',
|
||||||
|
'frequencies': [32, 75, 125, 220, 700, 1000, 2000, 4000, 10000, 16000],
|
||||||
|
'gain': [0, -8, 0, -0.1, -3, 0, 0, 0, 4, 0],
|
||||||
|
'Q': [1, 0.2, 1, 2.0, 1.4, 1, 1, 1, 0.1, 1],
|
||||||
|
'preamp': 0,
|
||||||
|
'mix': 1,
|
||||||
|
'userGenerated': false
|
||||||
|
}, {
|
||||||
|
'preset': 'clearVocal',
|
||||||
|
'name': 'Clear Vocal',
|
||||||
|
'frequencies': [20, 63, 125, 250, 400, 1000, 2000, 4000, 8000, 20000],
|
||||||
|
'gain': [-22, 0, 0, 0, -3, 0, 1.8, 0, 0, 3.5],
|
||||||
|
'Q': [0.3, 1, 1, 1, 2.0, 1, 0.7, 1, 1, 0.8],
|
||||||
|
'preamp': 0,
|
||||||
|
'mix': 1,
|
||||||
|
'userGenerated': false
|
||||||
|
}, {
|
||||||
|
'preset': 'instrumentClarity',
|
||||||
|
'name': 'Instrument Clarity',
|
||||||
|
'frequencies': [20, 63, 155, 250, 500, 1000, 2000, 5000, 11000, 16000],
|
||||||
|
'gain': [-15, 0, -3, 0, 0, 0, 0, 3.1, 0, 0],
|
||||||
|
'Q': [0.5, 1, 2, 1, 1, 1, 1, 1.5, 0.1, 1],
|
||||||
|
'preamp': 0,
|
||||||
|
'mix': 1,
|
||||||
|
'userGenerated': false
|
||||||
|
}, {
|
||||||
|
'preset': 'reduceHarshness',
|
||||||
|
'name': 'Reduce Harshness',
|
||||||
|
'frequencies': [32, 63, 125, 250, 500, 1128, 2000, 4057, 8000, 16000],
|
||||||
|
'gain': [0, 0, 0, 0, 0, 2, 0, -6.4, 0, 0],
|
||||||
|
'Q': [1, 1, 1, 1, 1, 2, 1, 1, 1, 1],
|
||||||
|
'preamp': 0,
|
||||||
|
'mix': 1,
|
||||||
|
'userGenerated': false
|
||||||
|
}, {
|
||||||
|
'preset': 'smileyFace',
|
||||||
|
'name': 'Smiley Face',
|
||||||
|
'frequencies': [35, 63, 125, 250, 500, 800, 2000, 4000, 8000, 20000],
|
||||||
|
'gain': [5, 0, 0, 0, 0, -5, 0, 0, 0, 5],
|
||||||
|
'Q': [0.1, 1, 1, 1, 1, 0.6, 1, 1, 1, 0.2],
|
||||||
|
'preamp': 0,
|
||||||
|
'mix': 1,
|
||||||
|
'userGenerated': false
|
||||||
|
}, {
|
||||||
|
'preset': 'tightPerc',
|
||||||
|
'name': 'Tight Percussion',
|
||||||
|
'frequencies': [20, 63, 125, 250, 402, 1000, 1677, 3000, 8000, 11000],
|
||||||
|
'gain': [-6.5, 0, 0, 0, -4.5, 0, -1.5, 3, 0, 0.1],
|
||||||
|
'Q': [0.8, 1, 1, 1, 6, 1, 0.8, 0.8, 1, 1],
|
||||||
|
'preamp': 0,
|
||||||
|
'mix': 1,
|
||||||
|
'userGenerated': false
|
||||||
|
}, {
|
||||||
|
'preset': 'Maikiwi',
|
||||||
|
'name': 'Maikiwi',
|
||||||
|
'frequencies': [20, 63, 160, 250, 500, 1000, 2000, 3500, 8000, 20000],
|
||||||
|
'gain': [1.5, 0, -0.7, 0, 0, 0, 0, 0.5, 0, 1.2],
|
||||||
|
'Q': [0.4, 1, 2.9, 1, 1, 1, 1, 1.5, 1, 0.1],
|
||||||
|
'preamp': 0,
|
||||||
|
'mix': 1.2,
|
||||||
|
'userGenerated': false
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: ["src", "url"],
|
||||||
|
mounted() {
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
presetOptions(event) {
|
||||||
|
let menu = {
|
||||||
|
items: {
|
||||||
|
"new": {
|
||||||
|
"icon": "./assets/feather/plus.svg",
|
||||||
|
name: "New Preset...",
|
||||||
|
action: () => {
|
||||||
|
this.addPreset()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"icon": "./assets/feather/x-circle.svg",
|
||||||
|
name: "Delete Preset",
|
||||||
|
action: () => {
|
||||||
|
this.deletePreset()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"import": {
|
||||||
|
"icon": "./assets/feather/share.svg",
|
||||||
|
"name": app.getLz('action.import'),
|
||||||
|
"action": function () {
|
||||||
|
notyf.error("Not implemented yet")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"export": {
|
||||||
|
"icon": "./assets/feather/share.svg",
|
||||||
|
"name": app.getLz('action.export'),
|
||||||
|
"action": function () {
|
||||||
|
notyf.error("Not implemented yet")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!this.$root.cfg.audio.equalizer.userGenerated) {
|
||||||
|
delete menu.items.delete
|
||||||
|
}
|
||||||
|
|
||||||
|
app.showMenuPanel(menu, event)
|
||||||
|
},
|
||||||
|
sharePreset(event) {
|
||||||
|
let menu = {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
"icon": "./assets/feather/share.svg",
|
||||||
|
"name": app.getLz('action.import'),
|
||||||
|
"action": function () {
|
||||||
|
notyf.error("Not implemented yet")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon": "./assets/feather/share.svg",
|
||||||
|
"name": app.getLz('action.export'),
|
||||||
|
"action": function () {
|
||||||
|
notyf.error("Not implemented yet")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
app.showMenuPanel(menu, event)
|
||||||
|
},
|
||||||
|
deletePreset() {
|
||||||
|
let presets = this.$root.cfg.audio.equalizer.presets
|
||||||
|
bootbox.confirm("Are you sure you want to delete this preset?", (result) => {
|
||||||
|
if (result) {
|
||||||
|
this.changePreset("default")
|
||||||
|
// find the preset by id (preset) and remove it
|
||||||
|
let index = presets.findIndex(p => p.preset == this.preset)
|
||||||
|
presets.splice(index, 1)
|
||||||
|
notyf.success("Removed preset")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
app.resetState()
|
||||||
|
},
|
||||||
|
changePreamp() {
|
||||||
|
CiderAudio.audioNodes.preampNode.gain.value = app.cfg.audio.equalizer.preamp;
|
||||||
|
},
|
||||||
|
changeMix() {
|
||||||
|
for (var i = 0; i < 10; i++) {
|
||||||
|
CiderAudio.audioNodes.audioBands[i].gain.value = app.cfg.audio.equalizer.gain[i] * app.cfg.audio.equalizer.mix
|
||||||
|
}
|
||||||
|
},
|
||||||
|
changeGain(i) {
|
||||||
|
CiderAudio.audioNodes.audioBands[i].gain.value = app.cfg.audio.equalizer.gain[i] * app.cfg.audio.equalizer.mix
|
||||||
|
},
|
||||||
|
changeFreq(i) {
|
||||||
|
CiderAudio.audioNodes.audioBands[i].frequency.value = app.cfg.audio.equalizer.frequencies[i]
|
||||||
|
},
|
||||||
|
changeQ(i) {
|
||||||
|
CiderAudio.audioNodes.audioBands[i].Q.value = app.cfg.audio.equalizer.Q[i]
|
||||||
|
},
|
||||||
|
resetGain() {
|
||||||
|
this.applyPreset({
|
||||||
|
'frequencies': [32, 63, 125, 250, 500, 1000, 2000, 4000, 8000, 16000],
|
||||||
|
'gain': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
'Q': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||||
|
'preamp': 0,
|
||||||
|
'mix': 1,
|
||||||
|
})
|
||||||
|
if (app.cfg.audio.equalizer.userGenerated) {
|
||||||
|
this.saveSelectedPreset()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
addPreset() {
|
||||||
|
let self = this
|
||||||
|
bootbox.prompt("New EQ Preset Name", (res) => {
|
||||||
|
if (res) {
|
||||||
|
let eqSettings = Clone(app.cfg.audio.equalizer)
|
||||||
|
let newPreset = new self.eqPreset()
|
||||||
|
newPreset.name = res
|
||||||
|
newPreset.frequencies = eqSettings.frequencies
|
||||||
|
newPreset.gain = eqSettings.gain
|
||||||
|
newPreset.Q = eqSettings.Q
|
||||||
|
newPreset.preamp = eqSettings.preamp
|
||||||
|
newPreset.mix = eqSettings.mix
|
||||||
|
app.cfg.audio.equalizer.presets.push(newPreset)
|
||||||
|
notyf.success("Added Preset")
|
||||||
|
self.changePreset(newPreset.preset)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
saveSelectedPreset() {
|
||||||
|
// Save the current settings to the selected preset
|
||||||
|
let self = this
|
||||||
|
//let preset = app.cfg.audio.equalizer.presets[app.cfg.audio.equalizer.preset]
|
||||||
|
// find the preset by its id (preset)
|
||||||
|
let preset = app.cfg.audio.equalizer.presets.find(p => p.preset == app.cfg.audio.equalizer.preset)
|
||||||
|
preset.frequencies = app.cfg.audio.equalizer.frequencies
|
||||||
|
preset.gain = app.cfg.audio.equalizer.gain
|
||||||
|
preset.Q = app.cfg.audio.equalizer.Q
|
||||||
|
preset.preamp = app.cfg.audio.equalizer.preamp
|
||||||
|
preset.mix = app.cfg.audio.equalizer.mix
|
||||||
|
notyf.success("Saved Preset")
|
||||||
|
},
|
||||||
|
applyPreset(preset) {
|
||||||
|
Object.assign(this.$root.cfg.audio.equalizer, preset)
|
||||||
|
for (var i = 0; i < 10; i++) {
|
||||||
|
this.changeGain(i)
|
||||||
|
this.changeFreq(i)
|
||||||
|
this.changeQ(i)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
changePreset(id) {
|
||||||
|
let userPresets = app.cfg.audio.equalizer.presets
|
||||||
|
let defaultPresets = Clone(this.defaultPresets)
|
||||||
|
|
||||||
|
let presets = defaultPresets.concat(userPresets)
|
||||||
|
console.log(presets)
|
||||||
|
let preset = presets.find(p => p.preset == id)
|
||||||
|
|
||||||
|
console.log(preset)
|
||||||
|
|
||||||
|
if (preset) {
|
||||||
|
this.applyPreset(preset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -26,7 +26,7 @@
|
||||||
{{ app.mk.nowPlayingItem["attributes"]["name"] }}
|
{{ app.mk.nowPlayingItem["attributes"]["name"] }}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
style="display: inline-block; -webkit-box-orient: horizontal; white-space: nowrap; margin-top: 0.25vh;">
|
style="display: inline-block; -webkit-box-orient: horizontal; white-space: nowrap; margin-top: 0.25vh; overflow: hidden;">
|
||||||
<div class="item-navigate song-artist" style="display: inline-block;"
|
<div class="item-navigate song-artist" style="display: inline-block;"
|
||||||
@click="app.getNowPlayingItemDetailed(`artist`)">
|
@click="app.getNowPlayingItemDetailed(`artist`)">
|
||||||
{{ app.mk.nowPlayingItem["attributes"]["artistName"] }}
|
{{ app.mk.nowPlayingItem["attributes"]["artistName"] }}
|
||||||
|
@ -71,17 +71,17 @@
|
||||||
<div class="app-chrome-item">
|
<div class="app-chrome-item">
|
||||||
<button class="playback-button--small repeat" v-if="app.mk.repeatMode == 0"
|
<button class="playback-button--small repeat" v-if="app.mk.repeatMode == 0"
|
||||||
@click="app.mk.repeatMode = 1"></button>
|
@click="app.mk.repeatMode = 1"></button>
|
||||||
<button class="playback-button--small repeat active" @click="app.mk.repeatMode = 2"
|
<button class="playback-button--small repeat repeatOne" @click="app.mk.repeatMode = 2"
|
||||||
v-else-if="app.mk.repeatMode == 1"></button>
|
v-else-if="app.mk.repeatMode == 1"></button>
|
||||||
<button class="playback-button--small repeat repeatOne" @click="app.mk.repeatMode = 0"
|
<button class="playback-button--small repeat active" @click="app.mk.repeatMode = 0"
|
||||||
v-else-if="app.mk.repeatMode == 2"></button>
|
v-else-if="app.mk.repeatMode == 2"></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="app-chrome-item volume display--large">
|
<div class="app-chrome-item volume display--large">
|
||||||
<div class="app-chrome-item volume-icon"></div>
|
|
||||||
<div class="input-container">
|
<div class="input-container">
|
||||||
<input type="range" class="slider" @wheel="app.volumeWheel" step="0.01" min="0" max="1" v-model="app.mk.volume"
|
<button class="volume-button--small volume" @click="app.muteButtonPressed()" :class="{'active': app.cfg.audio.volume == 0}"></button>
|
||||||
v-if="typeof app.mk.volume != 'undefined'">
|
<input type="range" class="slider" @wheel="app.volumeWheel" step="0.01" min="0" :max="$root.cfg.audio.maxVolume" v-model="app.mk.volume"
|
||||||
|
v-if="typeof app.mk.volume != 'undefined'" @change="app.checkMuteChange()">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,28 +2,20 @@
|
||||||
<div v-observe-visibility="{callback: visibilityChanged}"
|
<div v-observe-visibility="{callback: visibilityChanged}"
|
||||||
@click="select"
|
@click="select"
|
||||||
class="cd-mediaitem-list-item"
|
class="cd-mediaitem-list-item"
|
||||||
:class="{'mediaitem-selected': app.select_hasMediaItem(guid)}">
|
:class="{'mediaitem-selected': app.select_hasMediaItem(guid)}"
|
||||||
|
@contextmenu="contextMenu">
|
||||||
<template v-if="isVisible">
|
<template v-if="isVisible">
|
||||||
<div class="artwork" v-if="showArtwork == true">
|
<div class="artwork" v-if="showArtwork == true">
|
||||||
<mediaitem-artwork
|
<mediaitem-artwork
|
||||||
:url="getArtwork()"
|
:url="getArtwork()"
|
||||||
size="50"
|
size="50"
|
||||||
:type="item.type"></mediaitem-artwork>
|
:type="item.type"></mediaitem-artwork>
|
||||||
<button class="overlay-play" @click="select"></button>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="info-rect" :style="{'padding-left': (showArtwork ? '' : '16px')}"
|
<div class="info-rect" :style="{'padding-left': (showArtwork ? '' : '16px')}"
|
||||||
@dblclick="app.routeView(item)">
|
@dblclick="app.routeView(item)">
|
||||||
<div class="title text-overflow-elipsis">
|
<div class="title text-overflow-elipsis">
|
||||||
{{ item.attributes.name }}
|
{{ item.attributes.name }}
|
||||||
</div>
|
</div>
|
||||||
<div class="subtitle text-overflow-elipsis" style="-webkit-box-orient: horizontal;">
|
|
||||||
<template v-if="item.attributes.name">
|
|
||||||
<div class="artist item-navigate text-overflow-elipsis"
|
|
||||||
@click="select">
|
|
||||||
{{ item.attributes.artistName }}
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
@ -62,22 +54,19 @@
|
||||||
return minutes + ":" + (seconds < 10 ? '0' : '') + seconds;
|
return minutes + ":" + (seconds < 10 ? '0' : '') + seconds;
|
||||||
},
|
},
|
||||||
getDataType() {
|
getDataType() {
|
||||||
if (this.item.attributes.playParams.isLibrary) {
|
|
||||||
return this.item.type
|
return this.item.type
|
||||||
} else {
|
|
||||||
return this.item.attributes.playParams.kind
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
async select(e) {
|
async select(e) {
|
||||||
let u = this.item
|
let u = this.item
|
||||||
let u1 = await app.mk.api.library.artistRelationship(u.id,"albums",
|
let u1 = await app.mk.api.v3.music(`/v1/me/library/artists/${u.id}/albums`, {
|
||||||
{platform: "web",
|
"platform": "web",
|
||||||
"include[library-albums]": "artists,tracks",
|
"include[library-albums]": "artists,tracks",
|
||||||
"include[library-artists]": "catalog",
|
"include[library-artists]": "catalog",
|
||||||
"fields[artists]": "url",
|
"fields[artists]": "url",
|
||||||
"includeOnly": "catalog,artists"}
|
"includeOnly": "catalog,artists"
|
||||||
)
|
})
|
||||||
app.showCollection({data : Object.assign({},u1)}, u.attributes.name?? '', '');
|
app.showCollection({data : Object.assign({},u1.data.data)}, u.attributes.name?? '', '');
|
||||||
|
app.select_selectMediaItem(u.id, this.getDataType(), this.index, this.guid, true)
|
||||||
},
|
},
|
||||||
getArtwork(){
|
getArtwork(){
|
||||||
let u = ""
|
let u = ""
|
||||||
|
@ -87,80 +76,38 @@
|
||||||
return u;
|
return u;
|
||||||
},
|
},
|
||||||
contextMenu(event) {
|
contextMenu(event) {
|
||||||
|
|
||||||
let self = this
|
let self = this
|
||||||
let data_type = this.getDataType()
|
let data_type = this.getDataType()
|
||||||
let item_id = this.item.attributes.playParams.id ?? this.item.id
|
|
||||||
let isLibrary = this.item.attributes.playParams.isLibrary ?? false
|
let item = self.item
|
||||||
|
item.attributes.artistName = item.attributes.name;
|
||||||
|
|
||||||
let useMenu = "normal"
|
let useMenu = "normal"
|
||||||
if (app.selectedMediaItems.length <= 1) {
|
if (app.selectedMediaItems.length <= 1) {
|
||||||
app.selectedMediaItems = []
|
app.selectedMediaItems = []
|
||||||
app.select_selectMediaItem(item_id, data_type, this.index, this.guid, isLibrary)
|
app.select_selectMediaItem(this.item.id, data_type, this.index, this.guid, true)
|
||||||
} else {
|
} else {
|
||||||
useMenu = "multiple"
|
useMenu = "multiple"
|
||||||
}
|
}
|
||||||
|
|
||||||
let menus = {
|
let menus = {
|
||||||
multiple: {
|
multiple: {
|
||||||
items: [
|
items: [] //
|
||||||
{
|
|
||||||
"name": "Add to Playlist...",
|
|
||||||
"action": function () {
|
|
||||||
app.promptAddToPlaylist()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: `Play ${app.selectedMediaItems.length} tracks next`,
|
|
||||||
action: () => {
|
|
||||||
let itemsToPlay = {}
|
|
||||||
app.selectedMediaItems.forEach(item => {
|
|
||||||
if (!itemsToPlay[item.kind]) {
|
|
||||||
itemsToPlay[item.kind] = []
|
|
||||||
}
|
|
||||||
itemsToPlay[item.kind].push(item.id)
|
|
||||||
})
|
|
||||||
// loop through itemsToPlay
|
|
||||||
for (let kind in itemsToPlay) {
|
|
||||||
let ids = itemsToPlay[kind]
|
|
||||||
if (ids.length > 0) {
|
|
||||||
app.mk.playNext({[kind + "s"]: itemsToPlay[kind]})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.log(itemsToPlay)
|
|
||||||
app.selectedMediaItems = []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: `Play ${app.selectedMediaItems.length} tracks later`,
|
|
||||||
action: () => {
|
|
||||||
let itemsToPlay = {}
|
|
||||||
app.selectedMediaItems.forEach(item => {
|
|
||||||
if (!itemsToPlay[item.kind]) {
|
|
||||||
itemsToPlay[item.kind] = []
|
|
||||||
}
|
|
||||||
itemsToPlay[item.kind].push(item.id)
|
|
||||||
})
|
|
||||||
// loop through itemsToPlay
|
|
||||||
for (let kind in itemsToPlay) {
|
|
||||||
let ids = itemsToPlay[kind]
|
|
||||||
if (ids.length > 0) {
|
|
||||||
app.mk.playLater({[kind + "s"]: itemsToPlay[kind]})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
app.selectedMediaItems = []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
normal: {
|
normal: {
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
"name": "Add to Playlist...",
|
"name": app.getLz('action.goToArtist'),
|
||||||
|
"icon": "./assets/feather/user.svg",
|
||||||
"action": function () {
|
"action": function () {
|
||||||
app.promptAddToPlaylist()
|
app.searchAndNavigate(self.item, 'artist')
|
||||||
|
console.log(self.item)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Start Radio",
|
"icon": "./assets/feather/radio.svg",
|
||||||
|
"name": app.getLz('action.startRadio'),
|
||||||
"action": function () {
|
"action": function () {
|
||||||
app.mk.setStationQueue({song: self.item.attributes.playParams.id ?? self.item.id}).then(() => {
|
app.mk.setStationQueue({song: self.item.attributes.playParams.id ?? self.item.id}).then(() => {
|
||||||
app.mk.play()
|
app.mk.play()
|
||||||
|
@ -169,31 +116,15 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Play Next",
|
"icon": "./assets/feather/share.svg",
|
||||||
|
"name": app.getLz('action.share'),
|
||||||
"action": function () {
|
"action": function () {
|
||||||
app.mk.playNext({[self.item.attributes.playParams.kind ?? self.item.type]: self.item.attributes.playParams.id ?? self.item.id})
|
if (!self.item.attributes.url && self.item.relationships){
|
||||||
app.mk.queue._reindex()
|
if (self.item.relationships.catalog){
|
||||||
app.selectedMediaItems = []
|
app.mkapi(self.item.attributes.playParams.kind, false, self.item.relationships.catalog.data[0].id).then(u => {self.app.copyToClipboard((u.data.data.length && u.data.data.length > 0)? u.data.data[0].attributes.url : u.data.data.attributes.url)})
|
||||||
}
|
}
|
||||||
},
|
} else {
|
||||||
{
|
self.app.copyToClipboard(self.item.attributes.url)}
|
||||||
"name": "Play Later",
|
|
||||||
"action": function () {
|
|
||||||
app.mk.playLater({[self.item.attributes.playParams.kind ?? self.item.type]: self.item.attributes.playParams.id ?? self.item.id})
|
|
||||||
app.mk.queue._reindex()
|
|
||||||
app.selectedMediaItems = []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Go to Artist",
|
|
||||||
"action": function () {
|
|
||||||
app.searchAndNavigate(self.item, 'artist')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Go to Album",
|
|
||||||
"action": function () {
|
|
||||||
app.searchAndNavigate(self.item, 'album')
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
@ -208,7 +139,9 @@
|
||||||
menus.multiple.items = menus.multiple.items.concat(this.contextExt.multiple)
|
menus.multiple.items = menus.multiple.items.concat(this.contextExt.multiple)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CiderContextMenu.Create(event, menus[useMenu])
|
//CiderContextMenu.Create(event, menus[useMenu]); // Depreciated Context Menu
|
||||||
|
app.showMenuPanel(menus[useMenu], event);
|
||||||
|
|
||||||
},
|
},
|
||||||
visibilityChanged: function (isVisible, entry) {
|
visibilityChanged: function (isVisible, entry) {
|
||||||
this.isVisible = isVisible
|
this.isVisible = isVisible
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<h3>{{ recom.attributes.title ? recom.attributes.title.stringForDisplay : ""}}</h3>
|
<h3>{{ recom.attributes.title ? recom.attributes.title.stringForDisplay : ""}}</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto flex-center" v-if="recom.relationships.contents.data.length >= 10">
|
<div class="col-auto flex-center" v-if="recom.relationships.contents.data.length >= 10">
|
||||||
<button class="cd-btn-seeall" @click="app.showCollection(recom.relationships.contents, recom.attributes.title ? recom.attributes.title.stringForDisplay : '', 'listen_now')" >See All</button>
|
<button class="cd-btn-seeall" @click="app.showCollection(recom.relationships.contents, recom.attributes.title ? recom.attributes.title.stringForDisplay : '', 'listen_now')" >{{app.getLz('term.seeAll')}}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<template v-if="recom.attributes.display.kind == 'MusicCoverShelf'">
|
<template v-if="recom.attributes.display.kind == 'MusicCoverShelf'">
|
||||||
|
|
|
@ -35,7 +35,7 @@
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="no-lyrics">
|
<div class="no-lyrics">
|
||||||
Loading... / Lyrics not found./ Instrumental.</div>
|
{{app.getLz('term.noLyrics')}}</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<script type="text/x-template" id="mediaitem-artwork">
|
<script type="text/x-template" id="mediaitem-artwork">
|
||||||
<div class="mediaitem-artwork" :class="[{'rounded': (type == 'artists')}, classes]" :key="url"
|
<div class="mediaitem-artwork" :class="[{'rounded': (type == 'artists')}, classes]" :key="url">
|
||||||
v-observe-visibility="{callback: visibilityChanged}">
|
|
||||||
<img :src="app.getMediaItemArtwork(url, size, width)"
|
<img :src="app.getMediaItemArtwork(url, size, width)"
|
||||||
decoding="async" loading="lazy"
|
decoding="async"
|
||||||
|
:style="{background: bgcolor}"
|
||||||
class="mediaitem-artwork--img">
|
class="mediaitem-artwork--img">
|
||||||
<div v-if="video && isVisible && getVideoPriority()" class="animatedartwork-view-box">
|
<div v-if="video && getVideoPriority()" class="animatedartwork-view-box">
|
||||||
<animatedartwork-view :priority="getVideoPriority()" :video="video"></animatedartwork-view>
|
<animatedartwork-view :priority="getVideoPriority()" :video="video"></animatedartwork-view>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -22,6 +22,10 @@
|
||||||
type: [String, Number],
|
type: [String, Number],
|
||||||
required: false
|
required: false
|
||||||
},
|
},
|
||||||
|
bgcolor: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
url: {
|
url: {
|
||||||
type: String,
|
type: String,
|
||||||
default: ''
|
default: ''
|
||||||
|
@ -88,9 +92,6 @@
|
||||||
width: this.size + 'px',
|
width: this.size + 'px',
|
||||||
height: this.size + 'px'
|
height: this.size + 'px'
|
||||||
};
|
};
|
||||||
},
|
|
||||||
visibilityChanged: function (isVisible, entry) {
|
|
||||||
this.isVisible = isVisible
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
0
src/renderer/views/components/mediaitem-info.ejs
Normal file
|
@ -14,17 +14,26 @@
|
||||||
@mouseleave="showInLibrary = false"
|
@mouseleave="showInLibrary = false"
|
||||||
:class="[{'mediaitem-selected': app.select_hasMediaItem(guid)}, addClasses]">
|
:class="[{'mediaitem-selected': app.select_hasMediaItem(guid)}, addClasses]">
|
||||||
<template v-if="isVisible">
|
<template v-if="isVisible">
|
||||||
<div class="isLibrary" :style="{opacity: (showInLibrary ? 1 : 0)}" v-if="showLibraryStatus == true">
|
<div class="isLibrary"" v-if="showLibraryStatus == true">
|
||||||
<button @click="addToLibrary()"
|
<div v-if="showInLibrary" :style="{display: (showInLibrary ? 'block' : 'none'), 'margin-left':'11px'}">
|
||||||
v-if="!addedToLibrary">
|
<button @click="addToLibrary()" v-if="!addedToLibrary">
|
||||||
<div class="svg-icon" :style="{'--color': 'var(--keyColor)', '--url': 'url(./assets/feather/plus.svg)'}"></div>
|
<div class="svg-icon" :style="{'--color': 'var(--keyColor)', '--url': 'url(./assets/feather/plus.svg)'}"></div>
|
||||||
</button>
|
</button>
|
||||||
<button v-else style="opacity:0;">❤️</button>
|
</div>
|
||||||
|
<div v-if="!(app.mk.isPlaying && (((app.mk.nowPlayingItem._songId ?? app.mk.nowPlayingItem.id ) == item.attributes.playParams.id) || (app.mk.nowPlayingItem.id == item.id ))) && showIndex" :style="{display: ((showIndex && !showInLibrary) ? 'block' : 'none'), 'margin-left':'11px'}">
|
||||||
|
<div>
|
||||||
|
<div>{{ (item.attributes && !showIndexPlaylist) ? (item.attributes.trackNumber ?? '') : ((index * 1 + 1 ) ?? '')}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="app.mk.isPlaying && (((app.mk.nowPlayingItem._songId ?? app.mk.nowPlayingItem.id ) == item.attributes.playParams.id) || (app.mk.nowPlayingItem.id == item.id))" :style="{display: (showInLibrary ? 'none' : 'block')}">
|
||||||
|
<div class="loadbar-sound"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="artwork" v-if="showArtwork == true">
|
<div class="artwork" v-if="showArtwork == true">
|
||||||
<mediaitem-artwork
|
<mediaitem-artwork
|
||||||
:url="item.attributes.artwork ? item.attributes.artwork.url : ''"
|
:url="item.attributes.artwork ? item.attributes.artwork.url : ''"
|
||||||
:size="48"
|
:size="48"
|
||||||
|
:bgcolor="getBgColor()"
|
||||||
:type="item.type"></mediaitem-artwork>
|
:type="item.type"></mediaitem-artwork>
|
||||||
<button class="overlay-play" @click="playTrack()"><%- include("../svg/play.svg") %></button>
|
<button class="overlay-play" @click="playTrack()"><%- include("../svg/play.svg") %></button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -88,6 +97,8 @@
|
||||||
'show-library-status': {type: Boolean, default: true},
|
'show-library-status': {type: Boolean, default: true},
|
||||||
'show-meta-data': {type: Boolean, default: false},
|
'show-meta-data': {type: Boolean, default: false},
|
||||||
'show-duration': {type: Boolean, default: true},
|
'show-duration': {type: Boolean, default: true},
|
||||||
|
'showIndex': {type: Boolean, required: false},
|
||||||
|
'showIndexPlaylist': {type: Boolean, required: false},
|
||||||
'contextExt': {type: Object, required: false},
|
'contextExt': {type: Object, required: false},
|
||||||
'class-list': {type: String, required: false, default: ""},
|
'class-list': {type: String, required: false, default: ""},
|
||||||
},
|
},
|
||||||
|
@ -99,6 +110,10 @@
|
||||||
this.getClasses()
|
this.getClasses()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
getBgColor() {
|
||||||
|
let color = `#${(this.item.attributes.artwork != null && this.item.attributes.artwork.bgColor != null) ? (this.item.attributes.artwork.bgColor) : ``}`
|
||||||
|
return color
|
||||||
|
},
|
||||||
async checkLibrary() {
|
async checkLibrary() {
|
||||||
if(this.addedToLibrary) {return this.addedToLibrary}
|
if(this.addedToLibrary) {return this.addedToLibrary}
|
||||||
if(this.item.type.includes("library-playlists") || this.item.type.includes("station")) {
|
if(this.item.type.includes("library-playlists") || this.item.type.includes("station")) {
|
||||||
|
@ -223,14 +238,14 @@
|
||||||
multiple: {
|
multiple: {
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
"name": "Add to Playlist...",
|
"name": app.getLz('action.addToPlaylist'),
|
||||||
"icon": "./assets/feather/plus.svg",
|
"icon": "./assets/feather/plus.svg",
|
||||||
"action": function () {
|
"action": function () {
|
||||||
app.promptAddToPlaylist()
|
app.promptAddToPlaylist()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: `Play ${app.selectedMediaItems.length} tracks next`,
|
name: app.getLz('action.playTracksNext').replace("${app.selectedMediaItems.length}", app.selectedMediaItems.length),
|
||||||
"icon": "./assets/arrow-bend-up.svg",
|
"icon": "./assets/arrow-bend-up.svg",
|
||||||
action: () => {
|
action: () => {
|
||||||
let itemsToPlay = {}
|
let itemsToPlay = {}
|
||||||
|
@ -252,7 +267,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: `Play ${app.selectedMediaItems.length} tracks later`,
|
name: app.getLz('action.playTracksLater').replace("${app.selectedMediaItems.length}", app.selectedMediaItems.length),
|
||||||
"icon": "./assets/arrow-bend-down.svg",
|
"icon": "./assets/arrow-bend-down.svg",
|
||||||
action: () => {
|
action: () => {
|
||||||
let itemsToPlay = {}
|
let itemsToPlay = {}
|
||||||
|
@ -279,7 +294,7 @@
|
||||||
{
|
{
|
||||||
"icon": "./assets/feather/heart.svg",
|
"icon": "./assets/feather/heart.svg",
|
||||||
"id": "love",
|
"id": "love",
|
||||||
"name": "Love",
|
"name": this.app.getLz('action.love'),
|
||||||
"hidden": false,
|
"hidden": false,
|
||||||
"disabled": true,
|
"disabled": true,
|
||||||
"action": function () {
|
"action": function () {
|
||||||
|
@ -290,7 +305,7 @@
|
||||||
"icon": "./assets/feather/heart.svg",
|
"icon": "./assets/feather/heart.svg",
|
||||||
"id": "unlove",
|
"id": "unlove",
|
||||||
"active": true,
|
"active": true,
|
||||||
"name": "Unlove",
|
"name": this.app.getLz('action.unload'),
|
||||||
"hidden": true,
|
"hidden": true,
|
||||||
"action": function () {
|
"action": function () {
|
||||||
app.unlove(self.item)
|
app.unlove(self.item)
|
||||||
|
@ -299,7 +314,7 @@
|
||||||
{
|
{
|
||||||
"icon": "./assets/feather/thumbs-down.svg",
|
"icon": "./assets/feather/thumbs-down.svg",
|
||||||
"id": "dislike",
|
"id": "dislike",
|
||||||
"name": "Dislike",
|
"name": this.app.getLz('action.dislike'),
|
||||||
"hidden": false,
|
"hidden": false,
|
||||||
"disabled": true,
|
"disabled": true,
|
||||||
"action": function () {
|
"action": function () {
|
||||||
|
@ -309,7 +324,7 @@
|
||||||
{
|
{
|
||||||
"icon": "./assets/feather/thumbs-down.svg",
|
"icon": "./assets/feather/thumbs-down.svg",
|
||||||
"id": "undo_dislike",
|
"id": "undo_dislike",
|
||||||
"name": "Undo dislike",
|
"name": this.app.getLz('action.undoDislike'),
|
||||||
"active": true,
|
"active": true,
|
||||||
"hidden": true,
|
"hidden": true,
|
||||||
"action": function () {
|
"action": function () {
|
||||||
|
@ -321,7 +336,7 @@
|
||||||
{
|
{
|
||||||
"id": "addToLibrary",
|
"id": "addToLibrary",
|
||||||
"icon": "./assets/feather/plus.svg",
|
"icon": "./assets/feather/plus.svg",
|
||||||
"name": "Add to library",
|
"name": this.app.getLz('action.addToLibrary'),
|
||||||
"hidden": false,
|
"hidden": false,
|
||||||
"disabled": true,
|
"disabled": true,
|
||||||
"action": function () {
|
"action": function () {
|
||||||
|
@ -331,7 +346,7 @@
|
||||||
{
|
{
|
||||||
"id": "removeFromLibrary",
|
"id": "removeFromLibrary",
|
||||||
"icon": "./assets/feather/x-circle.svg",
|
"icon": "./assets/feather/x-circle.svg",
|
||||||
"name": "Remove from library",
|
"name": app.getLz('action.removeFromLibrary'),
|
||||||
"hidden": true,
|
"hidden": true,
|
||||||
"action": function () {
|
"action": function () {
|
||||||
self.removeFromLibrary()
|
self.removeFromLibrary()
|
||||||
|
@ -339,13 +354,13 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"icon": "./assets/feather/list.svg",
|
"icon": "./assets/feather/list.svg",
|
||||||
"name": "Add to Playlist...",
|
"name": app.getLz('action.addToPlaylist'),
|
||||||
"action": function () {
|
"action": function () {
|
||||||
app.promptAddToPlaylist()
|
app.promptAddToPlaylist()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Play Next",
|
"name": app.getLz('action.playNext'),
|
||||||
"icon": "./assets/arrow-bend-up.svg",
|
"icon": "./assets/arrow-bend-up.svg",
|
||||||
"action": function () {
|
"action": function () {
|
||||||
app.mk.playNext({[self.item.attributes.playParams.kind ?? self.item.type]: self.item.attributes.playParams.id ?? self.item.id})
|
app.mk.playNext({[self.item.attributes.playParams.kind ?? self.item.type]: self.item.attributes.playParams.id ?? self.item.id})
|
||||||
|
@ -354,7 +369,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Play Later",
|
"name": app.getLz('action.playLater'),
|
||||||
"icon": "./assets/arrow-bend-down.svg",
|
"icon": "./assets/arrow-bend-down.svg",
|
||||||
"action": function () {
|
"action": function () {
|
||||||
app.mk.playLater({[self.item.attributes.playParams.kind ?? self.item.type]: self.item.attributes.playParams.id ?? self.item.id})
|
app.mk.playLater({[self.item.attributes.playParams.kind ?? self.item.type]: self.item.attributes.playParams.id ?? self.item.id})
|
||||||
|
@ -364,7 +379,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"icon": "./assets/feather/radio.svg",
|
"icon": "./assets/feather/radio.svg",
|
||||||
"name": "Start Radio",
|
"name": app.getLz('action.startRadio'),
|
||||||
"action": function () {
|
"action": function () {
|
||||||
app.mk.setStationQueue({song: self.item.attributes.playParams.id ?? self.item.id}).then(() => {
|
app.mk.setStationQueue({song: self.item.attributes.playParams.id ?? self.item.id}).then(() => {
|
||||||
app.mk.play()
|
app.mk.play()
|
||||||
|
@ -374,25 +389,25 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"icon": "./assets/feather/user.svg",
|
"icon": "./assets/feather/user.svg",
|
||||||
"name": "Go to Artist",
|
"name": app.getLz('action.goToArtist'),
|
||||||
"action": function () {
|
"action": function () {
|
||||||
app.searchAndNavigate(self.item, 'artist')
|
app.searchAndNavigate(self.item, 'artist')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"icon": "./assets/feather/disc.svg",
|
"icon": "./assets/feather/disc.svg",
|
||||||
"name": "Go to Album",
|
"name": app.getLz('action.goToAlbum'),
|
||||||
"action": function () {
|
"action": function () {
|
||||||
app.searchAndNavigate(self.item, 'album')
|
app.searchAndNavigate(self.item, 'album')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"icon": "./assets/feather/share.svg",
|
"icon": "./assets/feather/share.svg",
|
||||||
"name": "Share",
|
"name": app.getLz('action.share'),
|
||||||
"action": function () {
|
"action": function () {
|
||||||
if (!self.item.attributes.url && self.item.relationships){
|
if (!self.item.attributes.url && self.item.relationships){
|
||||||
if (self.item.relationships.catalog){
|
if (self.item.relationships.catalog){
|
||||||
app.mkapi(self.item.attributes.playParams.kind, false, self.item.relationships.catalog.data[0].id).then(u => {self.app.copyToClipboard((u.length && u.length > 0)? u[0].attributes.url : u.attributes.url)})
|
app.mkapi(self.item.attributes.playParams.kind, false, self.item.relationships.catalog.data[0].id).then(u => {self.app.copyToClipboard((u.data.data.length && u.data.data.length > 0)? u.data.data[0].attributes.url : u.data.data.attributes.url)})
|
||||||
}
|
}
|
||||||
}else {
|
}else {
|
||||||
self.app.copyToClipboard(self.item.attributes.url)}
|
self.app.copyToClipboard(self.item.attributes.url)}
|
||||||
|
@ -502,22 +517,21 @@
|
||||||
app.mk.setQueue({[truekind]: [item.attributes.playParams.id ?? item.id]}).then(function () {
|
app.mk.setQueue({[truekind]: [item.attributes.playParams.id ?? item.id]}).then(function () {
|
||||||
app.mk.play().then(function (){
|
app.mk.play().then(function (){
|
||||||
var playlistId = id
|
var playlistId = id
|
||||||
function getPlaylist(id, params, isLibrary){
|
function getPlaylist(id, isLibrary){
|
||||||
if (isLibrary){
|
if (isLibrary){
|
||||||
return app.mk.api.library.playlist(id, params)
|
return this.app.mk.api.v3.music(`/v1/me/library/playlists/${id}`)
|
||||||
} else { return app.mk.api.playlist(id, params)}
|
} else { return this.app.mk.api.v3.music(`/v1/catalog/${app.mk.storefrontId}/playlists/${id}`)}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
getPlaylist(id, isLibrary).then(res => {
|
||||||
getPlaylist(id, params, isLibrary).then(res => {
|
|
||||||
//let query = res.relationships.tracks.data.map(item => new MusicKit.MediaItem(item));
|
//let query = res.relationships.tracks.data.map(item => new MusicKit.MediaItem(item));
|
||||||
//if (app.mk.shuffleMode == 1){shuffleArray(query); }
|
//if (app.mk.shuffleMode == 1){shuffleArray(query); }
|
||||||
// console.log(query)
|
// console.log(query)
|
||||||
// app.mk.queue.append(query)
|
// app.mk.queue.append(query)
|
||||||
if (!res.relationships.tracks.next) {
|
if (!res.data.relationships.tracks.next) {
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
getPlaylistTracks(res.relationships.tracks.next)
|
getPlaylistTracks(res.data.relationships.tracks.next)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPlaylistTracks(next) {
|
function getPlaylistTracks(next) {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script type="text/x-template" id="mediaitem-scroller-horizontal">
|
<script type="text/x-template" id="mediaitem-scroller-horizontal">
|
||||||
<template>
|
<template>
|
||||||
<div class="cd-hmedia-scroller" :class="kind">
|
<div class="cd-hmedia-scroller" :class="kind">
|
||||||
|
<slot></slot>
|
||||||
<mediaitem-square :kind="kind" :item="item"
|
<mediaitem-square :kind="kind" :item="item"
|
||||||
v-for="item in items"></mediaitem-square>
|
v-for="item in items"></mediaitem-square>
|
||||||
</div>
|
</div>
|
||||||
|
@ -13,7 +14,7 @@
|
||||||
props: {
|
props: {
|
||||||
'items': {
|
'items': {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: true
|
required: false
|
||||||
},
|
},
|
||||||
'kind': {
|
'kind': {
|
||||||
type: String,
|
type: String,
|
||||||
|
|
|
@ -122,7 +122,11 @@
|
||||||
}
|
}
|
||||||
let kind = this.item.attributes.playParams.kind ?? this.item.type ?? '';
|
let kind = this.item.attributes.playParams.kind ?? this.item.type ?? '';
|
||||||
var truekind = (!kind.endsWith("s")) ? (kind + "s") : kind;
|
var truekind = (!kind.endsWith("s")) ? (kind + "s") : kind;
|
||||||
app.mk.api.library.remove({[truekind]: id})
|
app.mk.api.v3.music(`v1/me/library/${truekind}/${id.toString()}`,{},
|
||||||
|
{
|
||||||
|
fetchOptions: {
|
||||||
|
method: "DELETE"
|
||||||
|
}})
|
||||||
this.addedToLibrary = true
|
this.addedToLibrary = true
|
||||||
},
|
},
|
||||||
async contextMenu(event) {
|
async contextMenu(event) {
|
||||||
|
@ -140,7 +144,7 @@
|
||||||
multiple: {
|
multiple: {
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
name: `Play ${app.selectedMediaItems.length} tracks next`,
|
name: this.$root.getLz('action.playTracksNext').replace("${app.selectedMediaItems.length}", app.selectedMediaItems.length),
|
||||||
action: () => {
|
action: () => {
|
||||||
let itemsToPlay = {}
|
let itemsToPlay = {}
|
||||||
app.selectedMediaItems.forEach(item => {
|
app.selectedMediaItems.forEach(item => {
|
||||||
|
@ -161,7 +165,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: `Play ${app.selectedMediaItems.length} tracks later`,
|
name: app.getLz('action.playTracksLater').replace("${app.selectedMediaItems.length}", app.selectedMediaItems.length),
|
||||||
action: () => {
|
action: () => {
|
||||||
let itemsToPlay = {}
|
let itemsToPlay = {}
|
||||||
app.selectedMediaItems.forEach(item => {
|
app.selectedMediaItems.forEach(item => {
|
||||||
|
@ -225,7 +229,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Share",
|
"name": this.$root.getLz('term.share'),
|
||||||
"action": function () {
|
"action": function () {
|
||||||
self.app.copyToClipboard(self.item.attributes.url)
|
self.app.copyToClipboard(self.item.attributes.url)
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,6 +96,7 @@
|
||||||
if (this.item.type && !this.item.type.includes("library")) {
|
if (this.item.type && !this.item.type.includes("library")) {
|
||||||
var params = {"fields[playlists]": "inLibrary", "fields[albums]": "inLibrary", "relate": "library", "extend": this.revisedRandId()}
|
var params = {"fields[playlists]": "inLibrary", "fields[albums]": "inLibrary", "relate": "library", "extend": this.revisedRandId()}
|
||||||
var res = await app.mkapi(this.item.attributes.playParams.kind ?? this.item.type, this.item.attributes.playParams.isLibrary ?? false, this.item.attributes.playParams.id ?? this.item.id, params);
|
var res = await app.mkapi(this.item.attributes.playParams.kind ?? this.item.type, this.item.attributes.playParams.isLibrary ?? false, this.item.attributes.playParams.id ?? this.item.id, params);
|
||||||
|
res = res.data.data[0]
|
||||||
this.addedToLibrary = (res && res.attributes && res.attributes.inLibrary) ? res.attributes.inLibrary : false
|
this.addedToLibrary = (res && res.attributes && res.attributes.inLibrary) ? res.attributes.inLibrary : false
|
||||||
} else {
|
} else {
|
||||||
this.addedToLibrary = true
|
this.addedToLibrary = true
|
||||||
|
@ -105,12 +106,17 @@
|
||||||
var params = {"fields[playlists]": "inLibrary","fields[songs]": "inLibrary", "fields[albums]": "inLibrary", "relate": "library", "extend": this.revisedRandId()}
|
var params = {"fields[playlists]": "inLibrary","fields[songs]": "inLibrary", "fields[albums]": "inLibrary", "relate": "library", "extend": this.revisedRandId()}
|
||||||
var id = this.item.id ?? this.item.attributes.playParams.id
|
var id = this.item.id ?? this.item.attributes.playParams.id
|
||||||
var res = await app.mkapi(this.item.attributes.playParams.kind ?? this.item.type, this.item.attributes.playParams.isLibrary ?? false, this.item.attributes.playParams.id ?? this.item.id, params);
|
var res = await app.mkapi(this.item.attributes.playParams.kind ?? this.item.type, this.item.attributes.playParams.isLibrary ?? false, this.item.attributes.playParams.id ?? this.item.id, params);
|
||||||
|
res = res.data.data[0]
|
||||||
if (res && res.relationships && res.relationships.library && res.relationships.library.data && res.relationships.library.data.length > 0) {
|
if (res && res.relationships && res.relationships.library && res.relationships.library.data && res.relationships.library.data.length > 0) {
|
||||||
id = res.relationships.library.data[0].id
|
id = res.relationships.library.data[0].id
|
||||||
}
|
}
|
||||||
let kind = this.item.attributes.playParams.kind ?? this.item.type ?? '';
|
let kind = this.item.attributes.playParams.kind ?? this.item.type ?? '';
|
||||||
var truekind = (!kind.endsWith("s")) ? (kind + "s") : kind;
|
var truekind = (!kind.endsWith("s")) ? (kind + "s") : kind;
|
||||||
app.mk.api.library.remove({[truekind]: id})
|
app.mk.api.v3.music(`v1/me/library/${truekind}/${id.toString()}`,{},
|
||||||
|
{
|
||||||
|
fetchOptions: {
|
||||||
|
method: "DELETE"
|
||||||
|
}})
|
||||||
this.addedToLibrary = true
|
this.addedToLibrary = true
|
||||||
},
|
},
|
||||||
subtitleSearchNavigate(item) {
|
subtitleSearchNavigate(item) {
|
||||||
|
@ -152,7 +158,7 @@
|
||||||
multiple: {
|
multiple: {
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
name: `Play ${app.selectedMediaItems.length} tracks next`,
|
name: app.getLz('action.playTracksNext').replace("${app.selectedMediaItems.length}", app.selectedMediaItems.length),
|
||||||
action: () => {
|
action: () => {
|
||||||
let itemsToPlay = {}
|
let itemsToPlay = {}
|
||||||
app.selectedMediaItems.forEach(item => {
|
app.selectedMediaItems.forEach(item => {
|
||||||
|
@ -173,7 +179,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: `Play ${app.selectedMediaItems.length} tracks later`,
|
name: app.getLz('action.playTracksLater').replace("${app.selectedMediaItems.length}", app.selectedMediaItems.length),
|
||||||
action: () => {
|
action: () => {
|
||||||
let itemsToPlay = {}
|
let itemsToPlay = {}
|
||||||
app.selectedMediaItems.forEach(item => {
|
app.selectedMediaItems.forEach(item => {
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
:video="(item.attributes != null && item.attributes.editorialVideo != null) ? (item.attributes.editorialVideo.motionDetailSquare ? item.attributes.editorialVideo.motionDetailSquare.video : (item.attributes.editorialVideo.motionSquareVideo1x1 ? item.attributes.editorialVideo.motionSquareVideo1x1.video : '')) : '' "
|
:video="(item.attributes != null && item.attributes.editorialVideo != null) ? (item.attributes.editorialVideo.motionDetailSquare ? item.attributes.editorialVideo.motionDetailSquare.video : (item.attributes.editorialVideo.motionSquareVideo1x1 ? item.attributes.editorialVideo.motionSquareVideo1x1.video : '')) : '' "
|
||||||
:size="size"
|
:size="size"
|
||||||
shadow="subtle"
|
shadow="subtle"
|
||||||
|
:bgcolor="getBgColor()"
|
||||||
:type="item.type"></mediaitem-artwork>
|
:type="item.type"></mediaitem-artwork>
|
||||||
</div>
|
</div>
|
||||||
<button class="menu-btn" v-if="!nomenu.includes(item.type)"
|
<button class="menu-btn" v-if="!nomenu.includes(item.type)"
|
||||||
|
@ -75,22 +76,8 @@
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getBgColor() {
|
getBgColor() {
|
||||||
let color = `#${(this.item.attributes.artwork != null && this.item.attributes.artwork.bgColor != null) ? (this.item.attributes.artwork.bgColor) : `333333`}`
|
let color = `#${(this.item.attributes.artwork != null && this.item.attributes.artwork.bgColor != null) ? (this.item.attributes.artwork.bgColor) : ``}`
|
||||||
let c = color.substring(1); // strip #
|
|
||||||
var rgb = parseInt(c, 16); // convert rrggbb to decimal
|
|
||||||
var r = (rgb >> 16) & 0xff; // extract red
|
|
||||||
var g = (rgb >> 8) & 0xff; // extract green
|
|
||||||
var b = (rgb >> 0) & 0xff; // extract blue
|
|
||||||
|
|
||||||
var luma = 0.2126 * r + 0.7152 * g + 0.0722 * b; // per ITU-R BT.709
|
|
||||||
|
|
||||||
if (luma > 140) {
|
|
||||||
return "#aaaaaa"
|
|
||||||
}else{
|
|
||||||
return color
|
return color
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
},
|
},
|
||||||
getSubtitle() {
|
getSubtitle() {
|
||||||
if(this.kind == 'card') {
|
if(this.kind == 'card') {
|
||||||
|
@ -145,8 +132,8 @@
|
||||||
let friends = this.badges[id]
|
let friends = this.badges[id]
|
||||||
if (friends) {
|
if (friends) {
|
||||||
friends.forEach(function (friend) {
|
friends.forEach(function (friend) {
|
||||||
self.app.mk.api.socialProfile(friend).then(data => {
|
self.app.mk.api.v3.music(`/v1/social/${app.mk.storefrontId}/social-profiles/${friend}`).then(data => {
|
||||||
self.itemBadges.push(data)
|
self.itemBadges.push(data.data.data[0])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -164,6 +151,7 @@
|
||||||
"extend": this.revisedRandId()
|
"extend": this.revisedRandId()
|
||||||
}
|
}
|
||||||
var res = await app.mkapi(this.item.attributes.playParams.kind ?? this.item.type, this.item.attributes.playParams.isLibrary ?? false, this.item.attributes.playParams.id ?? this.item.id, params);
|
var res = await app.mkapi(this.item.attributes.playParams.kind ?? this.item.type, this.item.attributes.playParams.isLibrary ?? false, this.item.attributes.playParams.id ?? this.item.id, params);
|
||||||
|
res = res.data.data[0]
|
||||||
this.addedToLibrary = (res && res.attributes && res.attributes.inLibrary) ? res.attributes.inLibrary : false
|
this.addedToLibrary = (res && res.attributes && res.attributes.inLibrary) ? res.attributes.inLibrary : false
|
||||||
} else {
|
} else {
|
||||||
this.addedToLibrary = true
|
this.addedToLibrary = true
|
||||||
|
@ -179,12 +167,17 @@
|
||||||
}
|
}
|
||||||
var id = this.item.id ?? this.item.attributes.playParams.id
|
var id = this.item.id ?? this.item.attributes.playParams.id
|
||||||
var res = await app.mkapi(this.item.attributes.playParams.kind ?? this.item.type, this.item.attributes.playParams.isLibrary ?? false, this.item.attributes.playParams.id ?? this.item.id, params);
|
var res = await app.mkapi(this.item.attributes.playParams.kind ?? this.item.type, this.item.attributes.playParams.isLibrary ?? false, this.item.attributes.playParams.id ?? this.item.id, params);
|
||||||
|
res= res.data.data[0]
|
||||||
if (res && res.relationships && res.relationships.library && res.relationships.library.data && res.relationships.library.data.length > 0) {
|
if (res && res.relationships && res.relationships.library && res.relationships.library.data && res.relationships.library.data.length > 0) {
|
||||||
id = res.relationships.library.data[0].id
|
id = res.relationships.library.data[0].id
|
||||||
}
|
}
|
||||||
let kind = this.item.attributes.playParams.kind ?? this.item.type ?? '';
|
let kind = this.item.attributes.playParams.kind ?? this.item.type ?? '';
|
||||||
var truekind = (!kind.endsWith("s")) ? (kind + "s") : kind;
|
var truekind = (!kind.endsWith("s")) ? (kind + "s") : kind;
|
||||||
app.mk.api.library.remove({[truekind]: id})
|
app.mk.api.v3.music(`v1/me/library/${truekind}/${id.toString()}`,{},
|
||||||
|
{
|
||||||
|
fetchOptions: {
|
||||||
|
method: "DELETE"
|
||||||
|
}})
|
||||||
this.addedToLibrary = true
|
this.addedToLibrary = true
|
||||||
},
|
},
|
||||||
uuidv4() {
|
uuidv4() {
|
||||||
|
@ -229,6 +222,7 @@
|
||||||
case "music-videos":
|
case "music-videos":
|
||||||
case "uploadedVideo":
|
case "uploadedVideo":
|
||||||
case "uploaded-videos":
|
case "uploaded-videos":
|
||||||
|
case "library-music-videos":
|
||||||
return "mediaitem-video";
|
return "mediaitem-video";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -257,7 +251,7 @@
|
||||||
multiple: {
|
multiple: {
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
name: `Play ${app.selectedMediaItems.length} tracks next`,
|
name: app.getLz('action.playTracksNext').replace("${app.selectedMediaItems.length}", app.selectedMediaItems.length),
|
||||||
"icon": "./assets/arrow-bend-up.svg",
|
"icon": "./assets/arrow-bend-up.svg",
|
||||||
action: () => {
|
action: () => {
|
||||||
let itemsToPlay = {}
|
let itemsToPlay = {}
|
||||||
|
@ -279,7 +273,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: `Play ${app.selectedMediaItems.length} tracks later`,
|
name: app.getLz('action.playTracksLater').replace("${app.selectedMediaItems.length}", app.selectedMediaItems.length),
|
||||||
"icon": "./assets/arrow-bend-down.svg",
|
"icon": "./assets/arrow-bend-down.svg",
|
||||||
action: () => {
|
action: () => {
|
||||||
let itemsToPlay = {}
|
let itemsToPlay = {}
|
||||||
|
@ -306,7 +300,7 @@
|
||||||
{
|
{
|
||||||
"icon": "./assets/feather/heart.svg",
|
"icon": "./assets/feather/heart.svg",
|
||||||
"id": "love",
|
"id": "love",
|
||||||
"name": "Love",
|
"name": app.getLz('action.love'),
|
||||||
"hidden": false,
|
"hidden": false,
|
||||||
"disabled": true,
|
"disabled": true,
|
||||||
"action": function () {
|
"action": function () {
|
||||||
|
@ -317,7 +311,7 @@
|
||||||
"icon": "./assets/feather/heart.svg",
|
"icon": "./assets/feather/heart.svg",
|
||||||
"id": "unlove",
|
"id": "unlove",
|
||||||
"active": true,
|
"active": true,
|
||||||
"name": "Unlove",
|
"name": app.getLz('action.unlove'),
|
||||||
"hidden": true,
|
"hidden": true,
|
||||||
"action": function () {
|
"action": function () {
|
||||||
app.unlove(self.item)
|
app.unlove(self.item)
|
||||||
|
@ -326,7 +320,7 @@
|
||||||
{
|
{
|
||||||
"icon": "./assets/feather/thumbs-down.svg",
|
"icon": "./assets/feather/thumbs-down.svg",
|
||||||
"id": "dislike",
|
"id": "dislike",
|
||||||
"name": "Dislike",
|
"name": app.getLz('action.dislike'),
|
||||||
"hidden": false,
|
"hidden": false,
|
||||||
"disabled": true,
|
"disabled": true,
|
||||||
"action": function () {
|
"action": function () {
|
||||||
|
@ -336,7 +330,7 @@
|
||||||
{
|
{
|
||||||
"icon": "./assets/feather/thumbs-down.svg",
|
"icon": "./assets/feather/thumbs-down.svg",
|
||||||
"id": "undo_dislike",
|
"id": "undo_dislike",
|
||||||
"name": "Undo dislike",
|
"name": app.getLz('action.undoDislike'),
|
||||||
"active": true,
|
"active": true,
|
||||||
"hidden": true,
|
"hidden": true,
|
||||||
"action": function () {
|
"action": function () {
|
||||||
|
@ -348,7 +342,7 @@
|
||||||
{
|
{
|
||||||
"icon": "./assets/feather/list.svg",
|
"icon": "./assets/feather/list.svg",
|
||||||
"id": "addToPlaylist",
|
"id": "addToPlaylist",
|
||||||
"name": "Add to Playlist...",
|
"name": app.getLz('action.addToPlaylist'),
|
||||||
"action": function () {
|
"action": function () {
|
||||||
app.promptAddToPlaylist()
|
app.promptAddToPlaylist()
|
||||||
}
|
}
|
||||||
|
@ -356,7 +350,7 @@
|
||||||
{
|
{
|
||||||
"id": "addToLibrary",
|
"id": "addToLibrary",
|
||||||
"icon": "./assets/feather/plus.svg",
|
"icon": "./assets/feather/plus.svg",
|
||||||
"name": "Add to library",
|
"name": app.getLz('action.addToLibrary'),
|
||||||
"hidden": false,
|
"hidden": false,
|
||||||
"disabled": true,
|
"disabled": true,
|
||||||
"action": function () {
|
"action": function () {
|
||||||
|
@ -369,7 +363,7 @@
|
||||||
{
|
{
|
||||||
"id": "removeFromLibrary",
|
"id": "removeFromLibrary",
|
||||||
"icon": "./assets/feather/x-circle.svg",
|
"icon": "./assets/feather/x-circle.svg",
|
||||||
"name": "Remove from library",
|
"name": app.getLz('action.removeFromLibrary'),
|
||||||
"hidden": true,
|
"hidden": true,
|
||||||
"action": async function () {
|
"action": async function () {
|
||||||
console.log("remove");
|
console.log("remove");
|
||||||
|
@ -380,7 +374,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Play Next",
|
"name": app.getLz('action.playNext'),
|
||||||
"icon": "./assets/arrow-bend-up.svg",
|
"icon": "./assets/arrow-bend-up.svg",
|
||||||
"action": function () {
|
"action": function () {
|
||||||
app.mk.playNext({[self.item.attributes.playParams.kind ?? self.item.type]: self.item.attributes.playParams.id ?? self.item.id})
|
app.mk.playNext({[self.item.attributes.playParams.kind ?? self.item.type]: self.item.attributes.playParams.id ?? self.item.id})
|
||||||
|
@ -389,7 +383,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Play Later",
|
"name": app.getLz('action.playLater'),
|
||||||
"icon": "./assets/arrow-bend-down.svg",
|
"icon": "./assets/arrow-bend-down.svg",
|
||||||
"action": function () {
|
"action": function () {
|
||||||
app.mk.playLater({[self.item.attributes.playParams.kind ?? self.item.type]: self.item.attributes.playParams.id ?? self.item.id})
|
app.mk.playLater({[self.item.attributes.playParams.kind ?? self.item.type]: self.item.attributes.playParams.id ?? self.item.id})
|
||||||
|
@ -399,7 +393,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"icon": "./assets/feather/share.svg",
|
"icon": "./assets/feather/share.svg",
|
||||||
"name": "Share",
|
"name": app.getLz('action.share'),
|
||||||
"action": function () {
|
"action": function () {
|
||||||
self.app.copyToClipboard(self.item.attributes.url)
|
self.app.copyToClipboard(self.item.attributes.url)
|
||||||
}
|
}
|
||||||
|
|
163
src/renderer/views/components/miniplayer.ejs
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
<script type="text/x-template" id="mini-view">
|
||||||
|
<div class="mini-view" tabindex="0">
|
||||||
|
<div class="background">
|
||||||
|
</div>
|
||||||
|
<div class="player-pin" title="Pin to Top" @click="app.pinMiniPlayer()">
|
||||||
|
<span id="mini-pin">📌</span>
|
||||||
|
</div>
|
||||||
|
<div class="player-exit" title="Close" @click="app.miniPlayer(false)">
|
||||||
|
<svg fill="#323232e3" xmlns="http://www.w3.org/2000/svg" width="21" height="21" viewBox="0 0 21 21"
|
||||||
|
aria-role="presentation" focusable="false">
|
||||||
|
<path
|
||||||
|
d="M10.5 21C4.724 21 0 16.275 0 10.5S4.724 0 10.5 0 21 4.725 21 10.5 16.276 21 10.5 21zm-3.543-5.967a.96.96 0 00.693-.295l2.837-2.842 2.85 2.842c.167.167.41.295.693.295.552 0 1.001-.461 1.001-1.012 0-.281-.115-.512-.295-.704L11.899 10.5l2.85-2.855a.875.875 0 00.295-.68c0-.55-.45-.998-1.001-.998a.871.871 0 00-.668.295l-2.888 2.855-2.862-2.843a.891.891 0 00-.668-.281.99.99 0 00-1.001.986c0 .269.116.512.295.678L9.088 10.5l-2.837 2.843a.926.926 0 00-.295.678c0 .551.45 1.012 1.001 1.012z"
|
||||||
|
fill-rule="nonzero"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="col artwork-col">
|
||||||
|
<div class="artwork" @click="app.miniPlayer(false)">
|
||||||
|
<mediaitem-artwork
|
||||||
|
:size="600"
|
||||||
|
:url="image ?? ''"
|
||||||
|
></mediaitem-artwork>
|
||||||
|
</div>
|
||||||
|
<div class="controls-parents">
|
||||||
|
<template v-if="app.mkReady()">
|
||||||
|
<div class="app-playback-controls" @mouseover="app.chrome.progresshover = true"
|
||||||
|
@mouseleave="app.chrome.progresshover = false" @contextmenu="app.nowPlayingContextMenu">
|
||||||
|
<div class="playback-info">
|
||||||
|
<div class="song-name">
|
||||||
|
{{ app.mk.nowPlayingItem["attributes"]["name"] }}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style="display: inline-block; -webkit-box-orient: horizontal; white-space: nowrap; margin-top: 0.25vh; overflow: hidden; margin-bottom: 5px;">
|
||||||
|
<div class="item-navigate song-artist" style="display: inline-block;"
|
||||||
|
@click="app.getNowPlayingItemDetailed(`artist`)">
|
||||||
|
{{ app.mk.nowPlayingItem["attributes"]["artistName"] }}
|
||||||
|
</div>
|
||||||
|
<div class="song-artist item-navigate" style="display: inline-block;"
|
||||||
|
@click="app.getNowPlayingItemDetailed('album')">
|
||||||
|
{{ (app.mk.nowPlayingItem["attributes"]["albumName"]) ? (" — " +
|
||||||
|
app.mk.nowPlayingItem["attributes"]["albumName"]) : "" }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="song-progress">
|
||||||
|
<div class="song-duration" style="justify-content: space-between; height: 1px; margin-bottom: 1px;"
|
||||||
|
:style="[app.chrome.progresshover ? {'display': 'flex'} : {'display' : 'none'} ]">
|
||||||
|
<p style="width: auto">{{ app.convertToMins(app.getSongProgress()) }}</p>
|
||||||
|
<p style="width: auto">{{ app.convertToMins(app.mk.currentPlaybackDuration) }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input type="range" step="0.01" min="0" :style="app.progressBarStyle()"
|
||||||
|
@input="app.playerLCD.desiredDuration = $event.target.value;app.playerLCD.userInteraction = true"
|
||||||
|
@mouseup="app.mk.seekToTime($event.target.value);app.playerLCD.desiredDuration = 0;app.playerLCD.userInteraction = false"
|
||||||
|
:max="app.mk.currentPlaybackDuration" :value="app.getSongProgress()">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-buttons">
|
||||||
|
<div class="app-chrome-item">
|
||||||
|
<button class="playback-button--small shuffle" v-if="app.mk.shuffleMode == 0"
|
||||||
|
@click="app.mk.shuffleMode = 1"></button>
|
||||||
|
<button class="playback-button--small shuffle active" v-else
|
||||||
|
@click="app.mk.shuffleMode = 0"></button>
|
||||||
|
</div>
|
||||||
|
<div class="app-chrome-item">
|
||||||
|
<button class="playback-button previous" @click="app.prevButton()"></button>
|
||||||
|
</div>
|
||||||
|
<div class="app-chrome-item">
|
||||||
|
<button class="playback-button pause" @click="app.mk.pause()" v-if="app.mk.isPlaying"></button>
|
||||||
|
<button class="playback-button play" @click="app.mk.play()" v-else></button>
|
||||||
|
</div>
|
||||||
|
<div class="app-chrome-item">
|
||||||
|
<button class="playback-button next" @click="app.mk.skipToNextItem()"></button>
|
||||||
|
</div>
|
||||||
|
<div class="app-chrome-item">
|
||||||
|
<button class="playback-button--small repeat" v-if="app.mk.repeatMode == 0"
|
||||||
|
@click="app.mk.repeatMode = 1"></button>
|
||||||
|
<button class="playback-button--small repeat repeatOne" @click="app.mk.repeatMode = 2"
|
||||||
|
v-else-if="app.mk.repeatMode == 1"></button>
|
||||||
|
<button class="playback-button--small repeat active" @click="app.mk.repeatMode = 0"
|
||||||
|
v-else-if="app.mk.repeatMode == 2"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="app-chrome-item volume display--large">
|
||||||
|
<div class="input-container">
|
||||||
|
<button class="volume-button--small volume" @click="app.muteButtonPressed()" :class="{'active': app.cfg.audio.volume == 0}"></button>
|
||||||
|
<input type="range" class="slider" @wheel="app.volumeWheel" step="0.01" min="0" max="1" v-model="app.mk.volume"
|
||||||
|
v-if="typeof app.mk.volume != 'undefined'" @change="app.checkMuteChange()">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- <div class="row fs-row">
|
||||||
|
|
||||||
|
<div class="col right-col" v-if="tabMode != ''">
|
||||||
|
<div class="fs-info">
|
||||||
|
<div>Name</div>
|
||||||
|
<div>Name</div>
|
||||||
|
<div>Name</div>
|
||||||
|
</div>
|
||||||
|
<div class="lyrics-col" v-if="tabMode == 'lyrics'">
|
||||||
|
<lyrics-view :yoffset="120" :time="time" :lyrics="lyrics"
|
||||||
|
:richlyrics="richlyrics"></lyrics-view>
|
||||||
|
</div>
|
||||||
|
<div class="queue-col" v-if="tabMode == 'queue'">
|
||||||
|
<cider-queue v-if="tabMode == 'queue'" ref="queue" ></cider-queue>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div> -->
|
||||||
|
<!-- <div class="tab-toggles">
|
||||||
|
<div class="lyrics" :class="{active: tabMode == 'lyrics'}" @click="tabMode = (tabMode == 'lyrics') ? '' : 'lyrics'"></div>
|
||||||
|
<div class="queue" :class="{active: tabMode == 'queue'}" @click="tabMode = (tabMode == 'queue') ? '' :'queue'"></div>
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
Vue.component('mini-view', {
|
||||||
|
template: '#mini-view',
|
||||||
|
props: {
|
||||||
|
time: {
|
||||||
|
type: Number,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
lyrics: {
|
||||||
|
type: Array,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
richlyrics: {
|
||||||
|
type: Array,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
image: {
|
||||||
|
type: String,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
app: this.$root,
|
||||||
|
tabMode: "",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeMount() {
|
||||||
|
window.addEventListener('keyup', this.onEscapeKeyUp);
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
window.removeEventListener('keyup', this.onEscapeKeyUp)
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onEscapeKeyUp(event) {
|
||||||
|
if (event.which === 27) {
|
||||||
|
app.miniPlayer(false);
|
||||||
|
console.log('js')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
34
src/renderer/views/components/qrcode-modal.ejs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
<script type="text/x-template" id="qrcode-modal">
|
||||||
|
<div class="modal-fullscreen spatialproperties-panel">
|
||||||
|
<div class="modal-window" >
|
||||||
|
<div class="modal-header">
|
||||||
|
<div class="modal-title">{{`Web Remote QR : ` + url }}</div>
|
||||||
|
<button class="close-btn" @click="close()"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-content">
|
||||||
|
<img class="qrimg" :src="src"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
Vue.component('qrcode-modal', {
|
||||||
|
template: '#qrcode-modal',
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
app: this.$root,
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: ["src","url"],
|
||||||
|
mounted() {
|
||||||
|
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
close() {
|
||||||
|
app.resetState()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|