Skip to content

Commit

Permalink
[MVP] user select
Browse files Browse the repository at this point in the history
  • Loading branch information
mantou132 committed Mar 8, 2020
1 parent 9f6a07c commit ba41653
Show file tree
Hide file tree
Showing 12 changed files with 172 additions and 45 deletions.
12 changes: 10 additions & 2 deletions public/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,23 @@
"web_accessible_resources": [
"page.js"
],
"permissions": [
"activeTab",
"tabs"
],
"background": {
"scripts": ["background.js"]
},
"content_scripts": [
{
"matches": ["*://open.spotify.com/*"],
"js": ["content.js"],
"run_at": "document_start"
}
],
"browser_action": {
"default_popup": "popup.html"
"page_action": {
"default_popup": "popup.html",
"default_icon": "icons/48.png"
},
"icons": {
"48": "icons/48.png",
Expand Down
10 changes: 10 additions & 0 deletions src/background.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { browser } from 'webextension-polyfill-ts';

browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
if (!tab?.id) return;
if (tab?.url?.match(/^https?:\/\/open.spotify.com\/.*/)) {
browser.pageAction.show(tab.id);
} else {
browser.pageAction.hide(tab.id);
}
});
1 change: 1 addition & 0 deletions src/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ export interface Message<T = any> {
export enum Event {
GET_SONGS = 'get-songs',
SEND_SONGS = 'send-songs',
SELECT_SONG = 'select-song',
}
5 changes: 1 addition & 4 deletions src/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,5 @@ window.addEventListener('message', ({ data }) => {
});

browser.runtime.onMessage.addListener((msg: Message) => {
const { type } = msg;
if (type === Event.GET_SONGS) {
window.postMessage(msg, '*');
}
window.postMessage(msg, '*');
});
25 changes: 23 additions & 2 deletions src/page/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { Event } from '../consts';

import generateSVG from './svg';
import songObserver from './song';
import songObserver, { Query } from './song';
import { setSongId } from './store';
import { video, audio } from './element';
import { lyric, updateLyric, Lyric } from './lyrics';
import { lyric, updateLyric, Lyric, sendMatchedData } from './lyrics';

import './pip';
import './misc';
Expand Down Expand Up @@ -86,3 +89,21 @@ if (ctx) {
} else {
throw new Error('lyric canvas context fail');
}

window.addEventListener('message', async ({ data }: MessageEvent) => {
if (data?.type === Event.GET_SONGS) {
sendMatchedData();
}
if (data?.type === Event.SELECT_SONG && video && video.srcObject) {
const info = data.data as Query & { id: number };
const { id, name, artists } = info;
await setSongId(info);
const coverTrack = weakMap.get(video.srcObject as CanvasCaptureMediaStream);
lyricWeakMap.delete(coverTrack as CanvasCaptureMediaStreamTrack);
if (id) {
updateLyric(id as number);
} else {
updateLyric({ name, artists });
}
}
});
58 changes: 36 additions & 22 deletions src/page/lyrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import config from '../config';
import { Message, Event } from '../consts';

import { Query } from './song';
import { getSongId } from './store';

export interface Artist {
name: string;
Expand All @@ -24,6 +25,8 @@ interface SearchResult {
export interface SharedData {
list: Song[];
id: number;
name: string;
artists: string;
}

interface SongResult {
Expand Down Expand Up @@ -60,23 +63,24 @@ const getHalfSizeText = (s: string) => {
.replace(/‘|’/g, "'");
};

const sharedData: SharedData = { list: [], id: 0 };
function sendShareDate() {
const sharedData: SharedData = { list: [], id: 0, name: '', artists: '' };
export function sendMatchedData(data?: Partial<SharedData>) {
if (data) Object.assign(sharedData, data);
const msg: Message = { type: Event.SEND_SONGS, data: sharedData };
window.postMessage(msg, '*');
}

async function fetchLyric(query: Query) {
const { name, artists } = query;
async function searchSong(query: Query) {
const { name = '', artists = '' } = query;
const simplifiedName = getSimplified(name);
const simplifiedArtists = getSimplified(artists);
const { API_HOST } = await config;
const searchQuery = new URLSearchParams({ type: '1 ', keywords: `${artists} ${name}`, limit: '100' });
let songId = 0;
let songs: Song[] = [];
try {
const { API_HOST } = await config;
const searchQuery = new URLSearchParams({ type: '1 ', keywords: `${artists} ${name}`, limit: '100' });
const { result }: SearchResult = await (await fetch(`${API_HOST}/search?${searchQuery}`)).json();
const songs = result?.songs || [];
// TODO: Support pinyin matching
let songId = 0;
songs = result?.songs || [];
let rank = 0; // Maximum score
songs.forEach(song => {
let currentRank = 0;
Expand Down Expand Up @@ -124,13 +128,21 @@ async function fetchLyric(query: Query) {
rank = currentRank;
}
});
sharedData.list = songs;
sharedData.id = songId;
sendShareDate();
const saveId = await getSongId(query);
if (saveId) songId = saveId;
if (!songId) {
console.log('Not matched:', { query, songs, rank });
return '';
}
} finally {
sendMatchedData({ list: songs, id: songId, name, artists });
return songId;
}
}

async function fetchLyric(songId: number) {
const { API_HOST } = await config;
if (!songId) return '';
try {
const { lrc }: SongResult = await (
await fetch(`${API_HOST}/lyric?${new URLSearchParams({ id: String(songId) })}`)
).json();
Expand All @@ -140,12 +152,6 @@ async function fetchLyric(query: Query) {
}
}

window.addEventListener('message', ({ data }: MessageEvent) => {
if (data?.type === Event.GET_SONGS) {
sendShareDate();
}
});

class Line {
startTime: number | null = null;
text = '';
Expand All @@ -154,9 +160,17 @@ class Line {
export type Lyric = Line[];

export let lyric: Lyric = [];
export async function updateLyric(query: Query) {
export async function updateLyric(query: Query | number) {
lyric = [];
const lyricStr = await fetchLyric(query);
let songId = 0;
if (typeof query === 'number') {
songId = query;
sendMatchedData({ id: songId });
} else {
songId = await searchSong(query);
}
if (!songId) return;
const lyricStr = await fetchLyric(songId);
if (!lyricStr) return;
const lines = lyricStr.split('\n').map(line => line.trim());
lyric = lines
Expand All @@ -178,7 +192,7 @@ export async function updateLyric(query: Query) {
const [min, sec] = [parseFloat(key), parseFloat(value)];
if (!isNaN(min)) {
result.startTime = min * 60 + sec;
result.text = text;
result.text = text?.trim();
} else {
result.text = `${key?.toUpperCase()}: ${value}`;
}
Expand Down
20 changes: 20 additions & 0 deletions src/page/store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Query } from './song';

const KEY = 'spotify.lyrics.extension';

export async function getSongId(data: Query) {
try {
const store = JSON.parse(localStorage[KEY] || '{}');
return store[`${data.name}-${data.artists}`] || 0;
} catch {
return 0;
}
}

export async function setSongId(data: Query & { id: number }) {
try {
const store = JSON.parse(localStorage[KEY] || '{}');
store[`${data.name}-${data.artists}`] = data.id;
localStorage[KEY] = JSON.stringify(store);
} catch {}
}
29 changes: 29 additions & 0 deletions src/popup/elements/item.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { html, customElement, connectStore, GemElement, property } from '@mantou/gem';

import { store } from '../store';

import { Song } from '../../page/lyrics';

@connectStore(store)
@customElement('song-item')
export class SongItem extends GemElement {
@property song: Song;

render() {
if (!this.song) {
return html`
<slot></slot>
`;
}
const { id, name } = this.song;
return html`
<style>
:host {
display: block;
cursor: default;
}
</style>
${id === store.id ? '✅ ' : ''}${name}
`;
}
}
35 changes: 23 additions & 12 deletions src/popup/elements/list.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,11 @@
import { html, customElement, connectStore, GemElement } from '@mantou/gem';

import { store } from '../store';

import { Song } from '../../page/lyrics';

// TODO: update current lyrics
import './item';
import { store, changeSong } from '../store';

@connectStore(store)
@customElement('song-list')
export class SongList extends GemElement {
renderItem = (song: Song) => {
return html`
<div>${song.id === store.id ? '🐶' : ''}${song.name}</div>
`;
};

render() {
if (store.list.length === 0) {
return html`
Expand All @@ -28,8 +19,28 @@ export class SongList extends GemElement {
height: 100%;
overflow: auto;
}
div {
font-style: italic;
}
song-item,
div {
padding: 0.2em 0.5em;
border-bottom: 1px solid #ccc;
}
song-item:hover {
background: #eee;
}
</style>
${store.list.map(this.renderItem)}
<div>
If your lyrics are not displayed or wrong, you can manually select the correct track to reload the lyrics
</div>
<div><button @click=${() => changeSong(0)}>Auto select</button></div>
${store.list.map(
song =>
html`
<song-item @click=${() => changeSong(song.id)} .song=${song}></song-item>
`,
)}
`;
}
}
2 changes: 1 addition & 1 deletion src/popup/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ render(
font-family: sans-serif;
}
</style>
<song-list>No lyrics currently available</song-list>
<song-list>No songs currently available</song-list>
`,
document.body,
);
19 changes: 17 additions & 2 deletions src/popup/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,30 @@ import { Event, Message } from '../consts';
import { SharedData } from '../page/lyrics';

export const store = createStore<SharedData>({
name: '',
artists: '',
list: [],
id: 0,
});

export function sendMessage(msg: Message) {
browser.tabs.query({ active: true, currentWindow: true }).then(([tab]) => {
if (tab?.id) browser.tabs.sendMessage(tab.id, msg);
});
}

export function changeSong(id: number) {
const data: Message = {
type: Event.SELECT_SONG,
data: { id, name: store.name, artists: store.artists },
};
sendMessage(data);
}

browser.runtime.onMessage.addListener((msg: Message) => {
if (msg?.type === Event.SEND_SONGS) {
updateStore(store, msg.data);
}
});

const getLyricsMsg: Message = { type: Event.GET_SONGS };
browser.runtime.sendMessage(getLyricsMsg);
sendMessage({ type: Event.GET_SONGS });
1 change: 1 addition & 0 deletions webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ module.exports = {
content: './src/content',
page: './src/page',
popup: './src/popup',
background: './src/background',
},
module: {
rules: [
Expand Down

0 comments on commit ba41653

Please sign in to comment.