Skip to content

Commit 83f2bdd

Browse files
committed
Improve paid music support
1 parent 7536d52 commit 83f2bdd

File tree

11 files changed

+107
-64
lines changed

11 files changed

+107
-64
lines changed

src/template.pug

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ audio.penguin-player__audio
33
.penguin-player__player--mini
44
.penguin-player__player--thumbnail
55
img.penguin-player__player--thumbnail-img(crossorigin="anonymous")
6-
.penguin-player__player--thumbnail-play-pause
6+
.penguin-player__player--thumbnail-play-pause(role="button")
77
.penguin-player__player--thumbnail-play
88
include:svgo icons/play.svg
99
.penguin-player__player--thumbnail-pause
@@ -14,7 +14,7 @@ audio.penguin-player__audio
1414
.penguin-player__player--expanded-content
1515
h1.penguin-player__player--name
1616
p.penguin-player__player--artists
17-
button.penguin-player__player--full-toggle
17+
button.penguin-player__player--full-toggle(role="button")
1818
.penguin-player__player--full-toggle-menu
1919
include:svgo icons/menu.svg
2020
.penguin-player__player--full-toggle-close
@@ -28,18 +28,18 @@ audio.penguin-player__audio
2828
span.penguin-player__player--progress-current
2929
span.penguin-player__player--progress-duration
3030
.penguin-player__player--controls
31-
.penguin-player__player--controls-previous
31+
.penguin-player__player--controls-previous(role="button")
3232
include:svgo icons/previous.svg
33-
.penguin-player__player--controls-next
33+
.penguin-player__player--controls-next(role="button")
3434
include:svgo icons/next.svg
3535
.penguin-player__player--controls-volume
3636
include:svgo icons/volume.svg
3737
.penguin-player__player--controls-volume-bar
3838
.penguin-player__player--controls-volume-inner
3939
.penguin-player__player--controls-volume-dot
40-
.penguin-player__player--controls-playlist
40+
.penguin-player__player--controls-playlist(role="button")
4141
include:svgo icons/playlist.svg
42-
.penguin-player__player--playlist
42+
.penguin-player__player--playlist(role="list")
4343
.penguin-player__lyric
4444
h1.penguin-player__lyric--main
4545
h2.penguin-player__lyric--sub

src/typescript/controller.ts

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { print } from "./helper";
1+
import { formatTime, print } from "./helper";
22
import { container as el } from "./player";
33
import { setSong as setMediaSession } from "./mediaSession";
44
import { getLyric } from "./lyric";
5-
import { resetRotate, setThemeColor, volumeSlider } from "./ui";
5+
import { progressSlider, resetRotate, setThemeColor, volumeSlider } from "./ui";
66
import { dispatchEvent } from "./modules/event";
77
import ajax from "./modules/ajax";
88

@@ -29,18 +29,45 @@ window.addEventListener("penguininitialized", () => {
2929
audio.addEventListener("error", playFailedHandler);
3030
});
3131

32+
function playTrack(track: any) {
33+
progressSlider.maxValue = progressSlider.minValue = null;
34+
if (track.freeTrialInfo) {
35+
trialInfo = track.freeTrialInfo;
36+
progressSlider.minValue = trialInfo.start / Math.floor(songs[currentSong].duration);
37+
progressSlider.maxValue = trialInfo.end / songs[currentSong].duration;
38+
}
39+
audio.src = track.url.replace("http:", "https:");
40+
play();
41+
}
42+
43+
function reset() {
44+
setThemeColor([255, 255, 255], [[0, 0, 0]]);
45+
resetRotate();
46+
trialInfo = null;
47+
}
48+
49+
export let trialInfo: TrialInfo;
50+
3251
export function setVolume(volume: number) {
3352
volumeSlider.setValue(volume);
3453
}
3554

55+
export function getCurrentTime(): number {
56+
return audio.currentTime + (trialInfo?.start || 0);
57+
}
58+
59+
export function getRealDuration(): number {
60+
return (trialInfo?.end - trialInfo?.start) || songs[currentSong].duration;
61+
}
62+
3663
export function play(id?: number) {
3764
if (typeof id == "number") {
3865
if (id < 0 || id >= songs.length) { throw "Invalid song index"; }
3966
audio.pause();
40-
currentSong = id;
41-
let song = songs[id];
42-
setThemeColor([255, 255, 255], [[0, 0, 0]]);
67+
let song = songs[currentSong = id];
68+
reset();
4369
setMediaSession(song);
70+
(<HTMLSpanElement>el.querySelector(".penguin-player__player--progress-duration")).textContent = formatTime(song.duration);
4471
if (currentUrlReq) { currentUrlReq.cancel(); }
4572
currentUrlReq = ajax(`https://gcm.tenmahw.com/song/url?id=${song.id}`).send().then((result) => {
4673
if (result.data.code == 200) {
@@ -49,13 +76,11 @@ export function play(id?: number) {
4976
print(`${song.name} is unavailable`);
5077
next();
5178
} else {
52-
audio.src = track.url.replace("http:", "https:");
53-
audio.play();
79+
playTrack(track);
5480
}
5581
} else { playFailedHandler(); }
5682
}).catch(playFailedHandler);
5783
getLyric(song);
58-
resetRotate();
5984
dispatchEvent("penguinsongchange", { detail: song });
6085
} else { audio.play(); }
6186
}
@@ -65,17 +90,9 @@ export function pause() {
6590
}
6691

6792
export function next() {
68-
if (currentSong >= songs.length - 1) {
69-
play(0);
70-
} else {
71-
play(currentSong + 1);
72-
}
93+
play(currentSong >= songs.length - 1 ? 0 : currentSong + 1);
7394
}
7495

7596
export function prev() {
76-
if (currentSong <= 0) {
77-
play(songs.length - 1);
78-
} else {
79-
play(currentSong - 1);
80-
}
97+
play(currentSong <= 0 ? songs.length - 1 : currentSong - 1);
8198
}

src/typescript/global.d.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ declare module "*.pug" {
44
}
55

66
interface Song {
7-
id : number,
8-
name : string,
9-
artists : string,
10-
album : string,
11-
thumbnail : string
7+
id: number,
8+
name: string,
9+
artists: string,
10+
album: string,
11+
thumbnail: string,
12+
duration: number
1213
}
1314
interface LyricLine {
1415
time: number,
@@ -25,6 +26,10 @@ interface MediaMetadataOptions {
2526
album: string
2627
artwork: Array<Artwork>
2728
}
29+
interface TrialInfo {
30+
start: number
31+
end: number
32+
}
2833
declare class MediaMetadata {
2934
constructor(options: MediaMetadataOptions)
3035
}

src/typescript/lyric.ts

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { songs, currentSong } from "./controller";
1+
import { getCurrentTime } from "./controller";
22
import { print } from "./helper";
33
import ajax from "./modules/ajax";
44
import { container as el } from "./player";
@@ -19,6 +19,7 @@ let lyricReq: AjaxPromise, retryTimeout: any;
1919
let lrc: Array<LyricLine>, tLrc: Array<LyricLine>, lrcOffset = 0, tLrcOffset = 0, lastMain: string, lastSub: string, lrcTimeout: any, subLrcTimeout: any;
2020

2121
function findLrcPos(lrc: Array<LyricLine>, time: number, offset = 0): number {
22+
if (!lrc) {return;}
2223
for (let i = offset;i < lrc.length;i++) {
2324
if (lrc[i + 1] == null || lrc[i + 1].time > time * 1000) {
2425
return i;
@@ -29,7 +30,7 @@ function findLrcPos(lrc: Array<LyricLine>, time: number, offset = 0): number {
2930

3031
function setElText(text: string, sub: boolean = false) {
3132
let [el, last, timeout] = sub ? [subEl, lastSub, subLrcTimeout] : [mainEl, lastMain, lrcTimeout];
32-
if (text == last) {return;}
33+
if (text == last) { return; }
3334
el.style.opacity = "0";
3435
clearTimeout(timeout);
3536
let id = setTimeout(() => {
@@ -44,14 +45,14 @@ function setElText(text: string, sub: boolean = false) {
4445
}
4546

4647
function lyricUpdate() {
47-
if (audio.paused) {return;}
48+
if (audio.paused) { return; }
4849
let [main, sub] = ["", ""];
49-
if (!isNaN(audio.currentTime) && lrc != null && (lrcOffset = findLrcPos(lrc, audio.currentTime, lrcOffset)) != -1) {
50+
if (!isNaN(audio.currentTime) && lrc && (lrcOffset = findLrcPos(lrc, getCurrentTime(), lrcOffset)) != -1) {
5051
main = lrc[lrcOffset].value;
51-
if (tLrc && (tLrcOffset = findLrcPos(tLrc, audio.currentTime, tLrcOffset)) != -1) {
52+
if (tLrc && (tLrcOffset = findLrcPos(tLrc, getCurrentTime(), tLrcOffset)) != -1) {
5253
sub = tLrc[tLrcOffset].value;
53-
} else if (lrcOffset != -1 && lrcOffset < lrc.length - 1) {
54-
sub = lrc[lrcOffset + 1].value;
54+
} else {
55+
sub = lrc[lrcOffset + 1]?.value || "";
5556
}
5657
}
5758
setElText(main);
@@ -65,12 +66,9 @@ export function getLyric(song: Song) {
6566
clearTimeout(retryTimeout);
6667
if (lyricReq) { lyricReq.cancel(); }
6768
lyricReq = ajax(`https://gcm.tenmahw.com/resolve/lyric?id=${song.id}`).send().then((result) => {
68-
if (result.data.lyric == null) {
69-
print(`No lyric for ${songs[currentSong].name}`);
70-
} else {
71-
lrc = result.data.lyric.lrc;
72-
tLrc = result.data.lyric.tlrc;
73-
}
69+
let lyric = result.data?.lyric;
70+
lrc = lyric?.lrc;
71+
tLrc = lyric?.tlrc;
7472
}).catch(() => {
7573
print("Cannot fetch lyric");
7674
retryTimeout = setTimeout(getLyric, 5000, song);

src/typescript/modules/ajax.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ function makeRequest(ajax: any) {
1616

1717
function ajax(url?: string): AjaxPromise {
1818
let _resolve: any, _reject: any;
19-
let promise: any = <AjaxPromise>new Promise<AjaxResponse>(function(resolve, reject) { _resolve = resolve; _reject = reject });
19+
let promise: any = new Promise<AjaxResponse>(function(resolve, reject) { _resolve = resolve; _reject = reject });
2020
promise._resolve = _resolve; promise._reject = _reject;
2121
promise._url = url;
2222
promise._method = "GET";

src/typescript/modules/event.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@ export function dispatchEvent(name: string, detail?: object) {
99
window.dispatchEvent(event);
1010
}
1111

12-
export function callHandlers(handlers: Array<(e: any) => void>, ...args: any) {
12+
export function callHandlers(handlers: Array<(e: any) => void>, ...args: any): boolean {
13+
let ret = false;
1314
for (let callback of handlers) {
1415
try {
15-
callback.apply(null, args);
16+
ret = ret || callback.apply(null, args);
1617
} catch(e) { console.error(e); }
1718
}
19+
return ret;
1820
}

src/typescript/modules/slider.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ export default class Slider {
1313
private beginDragHandlers: Array<(e: MouseEvent | TouchEvent) => void> = []
1414
private endDragHandlers: Array<(e: MouseEvent | TouchEvent) => void> = []
1515
private valueChangeHandlers: Array<(e: number) => void> = []
16-
16+
17+
public minValue: number = null
18+
public maxValue: number = null
19+
1720
constructor(options: SliderOptions) {
1821
this.activeEl = el.querySelector(options.activeSelector);
1922
this.barEl = el.querySelector(options.barSelector);
@@ -53,9 +56,10 @@ export default class Slider {
5356
} else {
5457
cx = e.touches[0].pageX;
5558
}
56-
let left = Math.max(0, cx - getOffsetLeft(this.barEl));
5759
let width = this.barEl.clientWidth;
58-
let progress = Math.min(1, left / width);
60+
let left = Math.min(Math.max(0, cx - getOffsetLeft(this.barEl)), width);
61+
let progress = left / width;
62+
progress = Math.max(Math.min(progress, this.maxValue || 1), this.minValue || 0);
5963
if (progress != this.value) {
6064
this.setValue(progress);
6165
}
@@ -76,8 +80,10 @@ export default class Slider {
7680
}
7781

7882
setValue(e: number) {
79-
this.innerEl.style.width = `${e * 100}%`;
80-
this.value = e;
81-
callHandlers(this.valueChangeHandlers, this.value);
83+
let ret = callHandlers(this.valueChangeHandlers, e);
84+
if (!ret) {
85+
this.innerEl.style.width = `${e * 100}%`;
86+
this.value = e;
87+
}
8288
}
8389
}

src/typescript/player.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import LazyLoad, { ILazyLoadInstance } from "vanilla-lazyload";
88

99
import { findHighContrastColor } from "./modules/color";
1010
import { print, formatTime } from "./helper";
11-
import { songs, currentSong, play, pause, prev, next, setVolume } from "./controller";
11+
import { songs, currentSong, play, pause, prev, next, setVolume, getCurrentTime } from "./controller";
1212
import { setCircleProgress, setThemeColor, rotateToggle } from "./ui";
1313
import cookie from "./modules/cookie";
1414

@@ -37,13 +37,10 @@ const colorthief = new ColorThief();
3737
audio.addEventListener("playing", updatePlayPauseButton);
3838
audio.addEventListener("pause", updatePlayPauseButton);
3939
audio.addEventListener("ended", next);
40-
audio.addEventListener("durationchange", function() {
41-
(<HTMLSpanElement>el.querySelector(".penguin-player__player--progress-duration")).textContent = formatTime(this.duration);
42-
});
4340
audio.addEventListener("timeupdate", function() {
44-
setCircleProgress(this.currentTime / this.duration * 100);
45-
(<HTMLSpanElement>el.querySelector(".penguin-player__player--progress-current")).textContent = formatTime(this.currentTime);
46-
(<HTMLDivElement>el.querySelector(".penguin-player__player--progress-inner")).style.width = (this.currentTime / this.duration * 100) + "%";
41+
setCircleProgress(getCurrentTime() / songs[currentSong].duration * 100);
42+
(<HTMLSpanElement>el.querySelector(".penguin-player__player--progress-current")).textContent = formatTime(getCurrentTime());
43+
(<HTMLDivElement>el.querySelector(".penguin-player__player--progress-inner")).style.width = (getCurrentTime() / songs[currentSong].duration * 100) + "%";
4744
});
4845
audio.addEventListener("error", () => {print("Cannot play " + songs[currentSong].name);next();});
4946
// Controls setup
@@ -96,6 +93,7 @@ let lazyLoad: ILazyLoadInstance;
9693
function createSongElement(song: Song, click: () => void): HTMLElement {
9794
let songEl = document.createElement("div");
9895
songEl.classList.add("penguin-player__player--playlist-song");
96+
songEl.setAttribute("role", "listitem");
9997
songEl.addEventListener("click", click);
10098
let img = document.createElement("img");
10199
img.classList.add("penguin-player__player--playlist-thumbnail");
@@ -127,7 +125,7 @@ function initialize(list: any) {
127125
for (let track of list.tracks) {
128126
let artists = "";
129127
for (let artist of track.ar) { artists += `, ${artist.name}`; }
130-
songs.push({ id: track.id, name: track.name, artists: artists.substring(2), album: track.al.name, thumbnail: track.al.picUrl.replace("http:", "https:") });
128+
songs.push({ id: track.id, name: track.name, artists: artists.substring(2), album: track.al.name, thumbnail: track.al.picUrl.replace("http:", "https:"), duration: track.dt / 1000 });
131129
}
132130
print("Playlist processed");
133131
let playlist: HTMLElement = el.querySelector(".penguin-player__player--playlist");

src/typescript/polyfill.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,9 @@
33
import "core-js/features/promise";
44
import "core-js/features/number/is-integer";
55

6+
// Required by Smooth Scrollbar
7+
import 'core-js/es/map';
8+
import 'core-js/es/set';
9+
import 'core-js/es/weak-map';
10+
import 'core-js/es/array/from';
11+
import 'core-js/es/object/assign';

src/typescript/ui.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ import cookie from "./modules/cookie";
88
import { dispatchEvent } from "./modules/event";
99
import { container as el } from "./player";
1010
import Slider from "./modules/slider";
11+
import { currentSong, getRealDuration, songs, trialInfo } from "./controller";
1112

1213
export let volumeSlider: Slider;
14+
export let progressSlider: Slider;
1315

1416
window.addEventListener("penguininitialized", () => {
1517
let audio: HTMLAudioElement = el.querySelector(".penguin-player__audio");
@@ -18,21 +20,26 @@ window.addEventListener("penguininitialized", () => {
1820
/// #endif
1921
// Progress bar setup
2022
let playerOldState: boolean;
21-
let slider = new Slider({
23+
progressSlider = new Slider({
2224
activeSelector: ".penguin-player__player--progress",
2325
barSelector: ".penguin-player__player--progress-bar",
2426
innerSelector: ".penguin-player__player--progress-inner",
2527
value: 0
2628
});
27-
slider.addEventHandler("begindrag", () => {
29+
progressSlider.addEventHandler("begindrag", () => {
2830
playerOldState = audio.paused;
2931
audio.pause();
3032
});
31-
slider.addEventHandler("enddrag", () => {
33+
progressSlider.addEventHandler("enddrag", () => {
3234
if (!playerOldState) {audio.play();}
3335
});
34-
slider.addEventHandler("valuechange", (value: number) => {
35-
audio.currentTime = audio.duration * value;
36+
progressSlider.addEventHandler("valuechange", (value: number) => {
37+
let fullTime = songs[currentSong].duration * value;
38+
if (trialInfo?.start > fullTime || trialInfo?.end < fullTime) {
39+
return true;
40+
} else {
41+
audio.currentTime = getRealDuration() * value;
42+
}
3643
});
3744
// Volume bar setup
3845
volumeSlider = new Slider({

0 commit comments

Comments
 (0)