Skip to content

Commit f5dc038

Browse files
committedJul 25, 2020
Support Youtebe Music, Closed #35
1 parent b914747 commit f5dc038

16 files changed

+196
-119
lines changed
 

‎README.md

+4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ If you have any questions, please submit an [issue](https://github.com/mantou132
1313

1414
![screenshot](./screenshot/screenshot3.jpg)
1515

16+
The extension also supports Youtube Music, but does not support videos well.
17+
18+
![screenshot](./screenshot/youtube.jpg)
19+
1620
## How To Work
1721

1822
1. [Listen](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) for track info element.

‎public/manifest.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
"content_scripts": [
2020
{
2121
"matches": [
22-
"*://open.spotify.com/*"
22+
"*://open.spotify.com/*",
23+
"*://music.youtube.com/*"
2324
],
2425
"js": [
2526
"content.js"

‎screenshot/youtube.jpg

273 KB
Loading

‎src/common/consts.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export const I18nMsgKeys = Object.keys(i18nEnMessages).reduce((p, c: keyof typeo
4848
return p;
4949
}, {} as Keys<typeof i18nEnMessages>);
5050

51-
export const isSupportES2018RegExp = (function() {
51+
export const isSupportES2018RegExp = (() => {
5252
try {
5353
/xx/su;
5454
return true;

‎src/common/ga.ts

+1
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ export function sendEvent(cid: string, payload: EventParams, customOptions: Reco
8282
? {
8383
vp: `${innerWidth}x${innerHeight}`,
8484
cs: matchMedia('(display-mode: standalone)').matches ? 'pwa' : 'webpage',
85+
cm: location.host,
8586
}
8687
: {}),
8788
});

‎src/page/btn.ts

+11-30
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,12 @@
11
import { sendEvent, events } from '../common/ga';
22

3-
import config from './config';
3+
import config, { localConfig } from './config';
44

55
import { video, audioPromise, videoMetadataloaded } from './element';
66
import { appendStyle, css, captureException } from './utils';
77
import { sharedData } from './share-data';
88
import { optionsPromise } from './options';
99

10-
const LYRICS_CLASSNAME = 'spoticon-lyrics-16';
11-
const LYRICS_ACTIVE_CLASSNAME = 'active';
12-
13-
appendStyle(css`
14-
.${LYRICS_CLASSNAME}::before {
15-
content: '\\f345';
16-
font-size: 16px;
17-
transform: rotate(90deg);
18-
color: #b3b3b3;
19-
}
20-
.${LYRICS_CLASSNAME}.${LYRICS_ACTIVE_CLASSNAME}::before {
21-
color: #1db954;
22-
}
23-
`);
24-
2510
config.then(({ PIP_BTN_SELECTOR }) => {
2611
appendStyle(css`
2712
${PIP_BTN_SELECTOR} {
@@ -32,8 +17,8 @@ config.then(({ PIP_BTN_SELECTOR }) => {
3217

3318
export async function getLyricsBtn() {
3419
const { BTN_WRAPPER_SELECTOR } = await config;
35-
const btnWrapper = document.querySelector(BTN_WRAPPER_SELECTOR) as HTMLDivElement;
36-
return btnWrapper.getElementsByClassName(LYRICS_CLASSNAME)[0] as HTMLButtonElement;
20+
const btnWrapper = document.querySelector(BTN_WRAPPER_SELECTOR);
21+
return btnWrapper?.getElementsByClassName(localConfig.LYRICS_CLASSNAME)[0] as HTMLButtonElement;
3722
}
3823

3924
window.addEventListener('keyup', async e => {
@@ -64,43 +49,39 @@ export const insetLyricsBtn = async () => {
6449
const likeBtn = document.querySelector(BTN_LIKE_SELECTOR) as HTMLButtonElement;
6550
if (!btnWrapper || !likeBtn) return;
6651

67-
if (btnWrapper.getElementsByClassName(LYRICS_CLASSNAME).length) return;
52+
if (btnWrapper.getElementsByClassName(localConfig.LYRICS_CLASSNAME).length) return;
6853

6954
btnWrapper.style.display = 'flex';
7055
const lyricsBtn = likeBtn.cloneNode(true) as HTMLButtonElement;
71-
lyricsBtn.classList.add(LYRICS_CLASSNAME);
56+
lyricsBtn.classList.add(localConfig.LYRICS_CLASSNAME);
7257

7358
if (document.pictureInPictureElement) {
7459
if (document.pictureInPictureElement !== video) {
7560
video.requestPictureInPicture().catch(() => document.exitPictureInPicture());
7661
} else {
77-
lyricsBtn.classList.add(LYRICS_ACTIVE_CLASSNAME);
62+
lyricsBtn.classList.add(localConfig.LYRICS_ACTIVE_CLASSNAME);
7863
}
7964
}
8065

8166
lyricsBtn.title = 'Toggle lyrics(L)';
82-
if (document.pictureInPictureElement === video) lyricsBtn.classList.add(LYRICS_ACTIVE_CLASSNAME);
67+
if (document.pictureInPictureElement === video) lyricsBtn.classList.add(localConfig.LYRICS_ACTIVE_CLASSNAME);
8368
video.addEventListener('enterpictureinpicture', () => {
84-
lyricsBtn.classList.add(LYRICS_ACTIVE_CLASSNAME);
69+
lyricsBtn.classList.add(localConfig.LYRICS_ACTIVE_CLASSNAME);
8570
});
8671
video.addEventListener('leavepictureinpicture', () => {
87-
lyricsBtn.classList.remove(LYRICS_ACTIVE_CLASSNAME);
72+
lyricsBtn.classList.remove(localConfig.LYRICS_ACTIVE_CLASSNAME);
8873
});
8974
lyricsBtn.addEventListener('click', () => {
9075
sendEvent(options.cid, events.clickToggleLyrics);
9176
if (document.pictureInPictureElement) {
92-
document.exitPictureInPicture().catch(console.error);
77+
document.exitPictureInPicture();
9378
} else {
9479
video
9580
.requestPictureInPicture()
9681
.then(() => {
9782
sharedData.updateTrack(true);
98-
// automatically pause when the video is removed from the DOM tree
99-
if (!audio?.paused) video.play();
10083
})
101-
.catch(e => {
102-
captureException(e);
103-
});
84+
.catch(captureException);
10485
}
10586
});
10687
btnWrapper.append(lyricsBtn);

‎src/page/config.json

+17
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,37 @@
11
{
22
"API_HOST": "https://music.xianqiao.wang/neteaseapi",
3+
"LOGGED_MARK_SELECTOR": "header [data-testid=user-widget-link]",
34
"LYRICS_CONTAINER_SELECTOR": ".Root__nav-bar nav",
5+
"PAGE_PIP_STYLE": "position: relative; width: 100%; height: auto;",
46
"ALBUM_COVER_SELECTOR": "[role=contentinfo] > div:nth-child(1) img",
57
"TRACK_INFO_SELECTOR": "[role=contentinfo] > div:nth-child(2)",
68
"TRACK_NAME_SELECTOR": "[role=contentinfo] > div:nth-child(2) > div:nth-child(1)",
79
"TRACK_ARTIST_SELECTOR": "[role=contentinfo] > div:nth-child(2) > div:nth-child(2)",
810
"BTN_WRAPPER_SELECTOR": "[role=contentinfo] > div:nth-child(3)",
911
"BTN_LIKE_SELECTOR": "[role=contentinfo] > div:nth-child(3) > [class*=spoticon-heart]",
1012
"PIP_BTN_SELECTOR": "[role=contentinfo] > div:nth-child(4)",
13+
"AUDIO_SELECTOR": "NONE",
14+
"YOUTUBE": {
15+
"LOGGED_MARK_SELECTOR": "ytmusic-settings-button",
16+
"LYRICS_CONTAINER_SELECTOR": "ytmusic-player",
17+
"PAGE_PIP_STYLE": "position: absolute; width: 100%; height: 100%; top: 0; object-fit: cover;",
18+
"ALBUM_COVER_SELECTOR": "ytmusic-player-bar .ytmusic-player-bar.image",
19+
"TRACK_INFO_SELECTOR": "ytmusic-player-bar .content-info-wrapper",
20+
"TRACK_NAME_SELECTOR": "ytmusic-player-bar .content-info-wrapper .title",
21+
"TRACK_ARTIST_SELECTOR": "ytmusic-player-bar .content-info-wrapper .subtitle yt-formatted-string > :first-child",
22+
"BTN_WRAPPER_SELECTOR": "ytmusic-like-button-renderer",
23+
"BTN_LIKE_SELECTOR": "ytmusic-like-button-renderer paper-icon-button:last-child",
24+
"PIP_BTN_SELECTOR": "NONE",
25+
"AUDIO_SELECTOR": "video.html5-main-video"
26+
},
1127
"SINGER": {
1228
"jia jia": "家家",
1329
"sue": "苏运莹",
1430
"shirley chen": "陈雪凝",
1531
"ada zhuang": "庄心妍",
1632
"dicky cheung": "张卫健",
1733
"show luo": "罗志祥",
34+
"show lo": "罗志祥",
1835
"a-mei chang": "张惠妹",
1936

2037
"kenshi yonezu": "米津玄師",

‎src/page/config.ts

+83-8
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,94 @@
1+
/**
2+
* The default is Spotify configuration
3+
*/
14
import { isProd } from '../common/consts';
25

36
import config from './config.json';
7+
import { css, svg, getSVGDataUrl } from './utils';
8+
9+
const REMOTE_URL = 'https://raw.githubusercontent.com/mantou132/Spotify-Lyrics/master/src/page/config.json';
10+
11+
const currentService = (() => {
12+
if (location.host.includes('youtube')) return 'YOUTUBE';
13+
return 'SPOTIFY';
14+
})();
415

516
async function getConfig() {
17+
let result = config;
618
if (isProd) {
719
try {
8-
return (await fetch(
9-
'https://raw.githubusercontent.com/mantou132/Spotify-Lyrics/master/src/page/config.json',
10-
).then(res => res.json())) as typeof config;
11-
} catch {
12-
return config;
13-
}
14-
} else {
15-
return config;
20+
result = (await (await fetch(REMOTE_URL)).json()) as typeof config;
21+
} catch {}
1622
}
23+
return currentService === 'SPOTIFY' ? result : Object.assign(result, result[currentService]);
1724
}
1825

1926
export default getConfig();
27+
28+
interface LocalConfig {
29+
STATIC_STYLE: string;
30+
NO_PIP_STYLE: string;
31+
LYRICS_CLASSNAME: string;
32+
LYRICS_ACTIVE_CLASSNAME: string;
33+
}
34+
35+
export const localConfig: LocalConfig = (() => {
36+
const LYRICS_CLASSNAME = 'spoticon-lyrics-16';
37+
const LYRICS_ACTIVE_CLASSNAME = 'active';
38+
39+
if (currentService === 'YOUTUBE') {
40+
const iconUrl = getSVGDataUrl(svg`
41+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white" width="18px" height="18px">
42+
<path d="M12 20c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2s-2 .9-2 2v12c0 1.1.9 2 2 2zm-6 0c1.1 0 2-.9 2-2v-4c0-1.1-.9-2-2-2s-2 .9-2 2v4c0 1.1.9 2 2 2zm10-9v7c0 1.1.9 2 2 2s2-.9 2-2v-7c0-1.1-.9-2-2-2s-2 .9-2 2z"/>
43+
</svg>
44+
`);
45+
return {
46+
STATIC_STYLE: css`
47+
.${LYRICS_CLASSNAME} {
48+
margin-left: var(--ytmusic-like-button-renderer-button-spacing, 8px);
49+
}
50+
.${LYRICS_CLASSNAME} iron-icon {
51+
background: var(--ytmusic-icon-inactive);
52+
transform: rotate(90deg) scale(1.2);
53+
-webkit-mask: url(${iconUrl}) center / 100% no-repeat;
54+
mask: url(${iconUrl}) center / 100% no-repeat;
55+
}
56+
.${LYRICS_CLASSNAME}.${LYRICS_ACTIVE_CLASSNAME} iron-icon {
57+
background: var(--ytmusic-text-primary);
58+
}
59+
`,
60+
NO_PIP_STYLE: '',
61+
LYRICS_CLASSNAME,
62+
LYRICS_ACTIVE_CLASSNAME,
63+
};
64+
} else {
65+
return {
66+
STATIC_STYLE: css`
67+
/* download link */
68+
.NavBar__download-item,
69+
/* icon */
70+
[role='banner'],
71+
/* ad */
72+
[role='main'] ~ div {
73+
display: none;
74+
}
75+
.${LYRICS_CLASSNAME}::before {
76+
content: '\\f345';
77+
font-size: 16px;
78+
transform: rotate(90deg);
79+
color: #b3b3b3;
80+
}
81+
.${LYRICS_CLASSNAME}.${LYRICS_ACTIVE_CLASSNAME}::before {
82+
color: #1db954;
83+
}
84+
`,
85+
NO_PIP_STYLE: css`
86+
[role='contentinfo'] > div:nth-child(1) > button {
87+
display: none;
88+
}
89+
`,
90+
LYRICS_CLASSNAME,
91+
LYRICS_ACTIVE_CLASSNAME,
92+
};
93+
}
94+
})();

‎src/page/element.ts

+40-29
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,21 @@ import { Message, Event } from '../common/consts';
33
import { sharedData } from './share-data';
44
import { captureException } from './utils';
55
import { getLyricsBtn } from './btn';
6+
import { loggedPromise } from './observer';
7+
import config from './config';
68

79
export const video = document.createElement('video');
810

911
video.muted = true;
1012
video.width = 640;
1113
video.height = 640;
1214

15+
export const canvas = document.createElement('canvas');
16+
canvas.width = video.width;
17+
canvas.height = video.height;
18+
canvas.getContext('2d');
19+
video.srcObject = canvas.captureStream();
20+
1321
export const videoMetadataloaded = new Promise(res => {
1422
video.addEventListener('loadedmetadata', () => res());
1523
});
@@ -42,42 +50,21 @@ export const audioPromise = new Promise<HTMLAudioElement>(res => {
4250
const element = createElement(tagName, options);
4351
if (tagName === 'video' && !audio) {
4452
audio = element as HTMLAudioElement;
45-
46-
audio.addEventListener('playing', async () => {
47-
const isMusic = audio?.duration && audio.duration > 2 * 60 && audio.duration < 4 * 60;
48-
if (isMusic && !(await getLyricsBtn())) {
49-
captureException(new Error('Lyrics button not found'));
50-
}
51-
});
52-
53-
// when next track
54-
audio.addEventListener('emptied', () => {
55-
sharedData.removeLyrics();
56-
});
57-
58-
if (navigator.mediaSession) {
59-
const mediaSession = navigator.mediaSession;
60-
audio.addEventListener('play', () => {
61-
video.play();
62-
mediaSession.playbackState = 'playing';
63-
});
64-
audio.addEventListener('pause', () => {
65-
video.pause();
66-
mediaSession.playbackState = 'paused';
67-
});
68-
}
6953
res(audio);
7054
}
7155
return element;
7256
};
7357

7458
window.addEventListener('load', () => {
75-
setTimeout(() => {
59+
setTimeout(async () => {
7660
if (!audio) {
77-
captureException(new Error('Audio not found'), {
78-
now: performance.now(),
79-
all: document.all.length,
80-
});
61+
const { AUDIO_SELECTOR } = await config;
62+
const audioSelected = document.querySelector(AUDIO_SELECTOR);
63+
if (audioSelected) {
64+
return res(audioSelected as HTMLAudioElement);
65+
}
66+
await loggedPromise;
67+
captureException(new Error('Audio not found'));
8168
}
8269
}, 3000);
8370
});
@@ -91,4 +78,28 @@ audioPromise.then(audio => {
9178
video.addEventListener('pause', () => {
9279
audio.pause();
9380
});
81+
82+
audio.addEventListener('playing', async () => {
83+
const isMusic = audio?.duration && audio.duration > 2 * 60 && audio.duration < 4 * 60;
84+
if (isMusic && !(await getLyricsBtn())) {
85+
captureException(new Error('Lyrics button not found'));
86+
}
87+
});
88+
89+
// when next track
90+
audio.addEventListener('emptied', () => {
91+
sharedData.removeLyrics();
92+
});
93+
94+
if (navigator.mediaSession) {
95+
const mediaSession = navigator.mediaSession;
96+
audio.addEventListener('play', () => {
97+
video.play();
98+
mediaSession.playbackState = 'playing';
99+
});
100+
audio.addEventListener('pause', () => {
101+
video.pause();
102+
mediaSession.playbackState = 'paused';
103+
});
104+
}
94105
});

‎src/page/index.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ import { video, audioPromise } from './element';
99
import { sharedData } from './share-data';
1010

1111
import './pip';
12-
import './misc';
1312
import './observer';
1413

1514
import { optionsPromise } from './options';
15+
import { appendStyle } from './utils';
16+
import { localConfig } from './config';
1617

1718
const INTERVAL = 80;
1819

@@ -101,3 +102,5 @@ window.addEventListener('message', async ({ data }: MessageEvent) => {
101102
sharedData.chooseLyricsTrack(data.data as PopupStore);
102103
}
103104
});
105+
106+
appendStyle(localConfig.STATIC_STYLE);

‎src/page/misc.ts

-22
This file was deleted.

‎src/page/observer.ts

+17-12
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
import config from './config';
22

3-
import { video } from './element';
3+
import { video, canvas } from './element';
44
import { insetLyricsBtn } from './btn';
55
import { sharedData } from './share-data';
66

7+
let loginResolve: (value?: unknown) => void;
8+
export const loggedPromise = new Promise(res => (loginResolve = res));
9+
710
const weakMap = new WeakMap<Element, MutationObserver>();
811

9-
config.then(({ ALBUM_COVER_SELECTOR, TRACK_INFO_SELECTOR }) => {
12+
config.then(({ ALBUM_COVER_SELECTOR, TRACK_INFO_SELECTOR, LOGGED_MARK_SELECTOR }) => {
1013
let infoElement: Element | null = null;
1114

1215
const checkElement = () => {
16+
if (document.querySelector(LOGGED_MARK_SELECTOR)) loginResolve();
1317
// https://github.com/mantou132/Spotify-Lyrics/issues/30
1418
insetLyricsBtn();
1519
const prevInfoElement = infoElement;
@@ -20,28 +24,26 @@ config.then(({ ALBUM_COVER_SELECTOR, TRACK_INFO_SELECTOR }) => {
2024
if (!weakMap.has(infoElement)) {
2125
const cover = document.querySelector(ALBUM_COVER_SELECTOR) as HTMLImageElement;
2226
// https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image
27+
// YouTube video has no cors
2328
cover.crossOrigin = 'anonymous';
24-
const coverCanvas = document.createElement('canvas');
25-
coverCanvas.width = video.width;
26-
coverCanvas.height = video.height;
27-
const ctx = coverCanvas.getContext('2d');
29+
const ctx = canvas.getContext('2d');
2830
if (!ctx) return;
29-
const stream = coverCanvas.captureStream();
30-
video.srcObject = stream;
3131
// May load multiple times in a short time
3232
// Need to remember the last cover image
3333
let largeImage: HTMLImageElement;
3434
cover.addEventListener('load', () => {
35-
const draw = () => {
35+
const drawSmallCover = () => {
3636
ctx.imageSmoothingEnabled = false;
3737
const blur = 10;
3838
ctx.filter = `blur(${blur}px)`;
3939
ctx.drawImage(cover, -blur * 2, -blur * 2, video.width + 4 * blur, video.height + 4 * blur);
4040
};
4141
// https://github.com/mantou132/Spotify-Lyrics/issues/26#issuecomment-638019333
4242
const reg = /00004851(?=\w{24}$)/;
43-
if (!reg.test(cover.src)) {
44-
draw();
43+
if (cover.naturalWidth >= 480) {
44+
ctx.drawImage(cover, 0, 0, video.width, video.height);
45+
} else if (!reg.test(cover.src)) {
46+
drawSmallCover();
4547
} else {
4648
const largeUrl = cover.src.replace(reg, '0000b273');
4749
largeImage = new Image();
@@ -51,10 +53,13 @@ config.then(({ ALBUM_COVER_SELECTOR, TRACK_INFO_SELECTOR }) => {
5153
ctx.filter = `blur(0px)`;
5254
ctx.drawImage(largeImage, 0, 0, video.width, video.height);
5355
});
54-
largeImage.addEventListener('error', draw);
56+
largeImage.addEventListener('error', drawSmallCover);
5557
largeImage.src = largeUrl;
5658
}
5759
});
60+
cover.addEventListener('error', () => {
61+
ctx.fillRect(0, 0, video.width, video.height);
62+
});
5863
const infoEleObserver = new MutationObserver(() => {
5964
sharedData.updateTrack();
6065
});

‎src/page/pip.ts

+7-11
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,15 @@
11
// https://w3c.github.io/picture-in-picture
22
// https://bugzilla.mozilla.org/show_bug.cgi?id=pip
3-
import config from './config';
3+
import config, { localConfig } from './config';
44

5-
import { appendStyle, css } from './utils';
5+
import { appendStyle } from './utils';
66
import { optionsPromise } from './options';
77

88
let polyfilled = false;
99
const polyfill = () => {
1010
if (polyfilled) return;
1111
polyfilled = true;
12-
// sync write
13-
appendStyle(css`
14-
[role='contentinfo'] > div:nth-child(1) > button {
15-
display: none;
16-
}
17-
`);
12+
appendStyle(localConfig.NO_PIP_STYLE);
1813

1914
Object.defineProperties(document, {
2015
pictureInPictureElement: {
@@ -30,11 +25,12 @@ const polyfill = () => {
3025
});
3126

3227
HTMLVideoElement.prototype.requestPictureInPicture = async function() {
33-
const { LYRICS_CONTAINER_SELECTOR } = await config;
28+
const { LYRICS_CONTAINER_SELECTOR, PAGE_PIP_STYLE } = await config;
3429
const container = document.querySelector(LYRICS_CONTAINER_SELECTOR);
3530
if (container) {
36-
this.setAttribute('style', 'width: 100%; position: relative; height: auto;');
31+
this.setAttribute('style', PAGE_PIP_STYLE);
3732
container.append(this);
33+
this.hidden = false;
3834
document.pictureInPictureElement = this;
3935
this.dispatchEvent(new CustomEvent('enterpictureinpicture'));
4036
return;
@@ -45,7 +41,7 @@ const polyfill = () => {
4541

4642
document.exitPictureInPicture = async function() {
4743
const video = document.pictureInPictureElement;
48-
video?.remove();
44+
if (video) video.hidden = true;
4945
document.pictureInPictureElement = null;
5046
video?.dispatchEvent(new CustomEvent('leavepictureinpicture'));
5147
return;

‎src/page/share-data.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ export class SharedData {
8282
const artists = document.querySelector(TRACK_ARTIST_SELECTOR)?.textContent;
8383
if (!name || !artists) {
8484
if (isTrust) {
85-
captureException(new Error(`Can't find track info`));
85+
captureException(new Error(`Track info not found`));
8686
}
8787
return;
8888
}

‎src/page/svg-renderer.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Lyric } from './lyrics';
2-
import { svg, html, css, captureException } from './utils';
2+
import { svg, html, css, captureException, getSVGDataUrl } from './utils';
33

44
const style = css`
55
:root {
@@ -111,7 +111,7 @@ export async function renderLyricsWithSVG(
111111
lyrics: Lyric,
112112
currentTime: number, // s
113113
): Promise<HTMLImageElement | undefined> {
114-
const url = `data:image/svg+xml,${encodeURIComponent(generateSVG(lyrics, currentTime))}`;
114+
const url = getSVGDataUrl(generateSVG(lyrics, currentTime));
115115
const img = new Image(ctx.canvas.width, ctx.canvas.height);
116116
return new Promise(res => {
117117
img.onload = () => res(img);

‎src/page/utils.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ export const svg = raw;
77
export const html = raw;
88
export const css = raw;
99

10+
export function getSVGDataUrl(s: string) {
11+
return `data:image/svg+xml,${encodeURIComponent(s)}`;
12+
}
13+
1014
export const headReady = new Promise(res => {
1115
if (document.head) res();
1216
document.addEventListener('readystatechange', () => {
@@ -15,13 +19,14 @@ export const headReady = new Promise(res => {
1519
});
1620

1721
export async function appendStyle(s: string) {
22+
if (s === '') return;
1823
await headReady;
1924
const style = document.createElement('style');
2025
style.textContent = s;
2126
document.head.append(style);
2227
}
2328

24-
export function captureException(err: Error, extra?: any) {
29+
export function captureException(err: Error, extra: any = { herf: location.href }) {
2530
if (!isProd) console.error(err, extra);
2631
const msg: Message = {
2732
type: Event.CAPTURE_EXCEPTION,

0 commit comments

Comments
 (0)
Please sign in to comment.