cplayer
Version:
A beautiful and clean WEB Music Player by HTML5.
380 lines (350 loc) • 11.3 kB
text/typescript
require('./polyfill')
import { listloopPlaymode } from './playmode/listloop';
import { IAudioItem, Iplaymode, IplaymodeConstructor, Iplaylist } from './interfaces';
import { EventEmitter } from 'events';
import View, { ICplayerViewOption } from './view';
import { decodeLyricStr } from "./lyric";
import { singlecyclePlaymode } from "./playmode/singlecycle";
import { listrandomPlaymode } from "./playmode/listrandom";
import shallowEqual from "./helper/shallowEqual";
import { cplayerMediaSessionPlugin } from "./mediaSession";
let cplayerView:typeof View = undefined;
if (!process.env.cplayer_noview) {
cplayerView = require('./view').default;
}
export interface ICplayerOption {
playlist?: Iplaylist;
playmode?: string;
volume?: number;
point?: number;
autoplay?: boolean;
}
const defaultOption: ICplayerOption = {
playlist: [],
point: 0,
volume: 1,
playmode: 'listloop',
autoplay: false
}
const playmodes: { [key: string]: IplaymodeConstructor } = {
listloop: listloopPlaymode,
singlecycle: singlecyclePlaymode,
listrandom: listrandomPlaymode
}
function playlistPreFilter(playlist: Iplaylist) {
return playlist.map((audio, index) => {
let res = {
...audio
};
if (typeof audio.lyric === 'string' && audio.lyric.replace(/\n+/gi, "\n").trim()) {
res.lyric = decodeLyricStr(audio.lyric)
}
if (typeof audio.sublyric === 'string' && audio.sublyric.replace(/\n+/gi, "\n").trim()) {
res.sublyric = decodeLyricStr(audio.sublyric)
}
return res;
})
}
export default class cplayer extends EventEmitter {
private __paused = true;
public view: View;
public audioElement: HTMLAudioElement | HTMLVideoElement;
private playmode: Iplaymode;
private playmodeName: string = 'listloop';
private audioElementType: string;
private _volume: number = 0;
set mode(playmode: string) {
this.setMode(playmode);
}
set volume(volume: number) {
this.setVolume(volume);
}
get volume() {
return this._volume
}
get mode() {
return this.playmodeName;
}
get playlist() {
return this.playmode.playlist;
}
get nowplay() {
return this.playmode && this.playmode.now();
}
get nowplaypoint() {
return this.playmode && this.playmode.nowpoint();
}
get played() {
return !this.__paused;
}
get paused() {
return this.__paused;
}
get duration() {
if (this.audioElement) {
return this.audioElement.duration;
}
return 0;
}
get currentTime() {
if (this.audioElement) {
return this.audioElement.currentTime;
}
return 0;
}
constructor(options: ICplayerOption & ICplayerViewOption) {
super();
options = {
...defaultOption,
...options
}
this.playmode = new playmodes[options.playmode](playlistPreFilter(options.playlist), options.point);
if (!process.env.cplayer_noview)this.view = new cplayerView(this, options);
cplayerMediaSessionPlugin(this);
// 同步调用会导致,用户的事件得不到触发
setImmediate(() => {
this.openAudio();
this.setVolume(options.volume);
if (options.autoplay && this.playlist.length > 0) {
this.play(true).catch((e) => {
console.log(e);
this.pause();
});
}
});
}
private initializeEventEmitter(element: HTMLElement) {
const a = element.addEventListener.bind(element);
const e = this.eventHandlers;
a('timeupdate', e.handleTimeUpdate);
a('canplaythrough', e.handleCanPlayThrough);
a('pause', e.handlePause);
a('play', e.handlePlay);
a('playing', e.handlePlaying)
a('ended', e.handleEnded);
a('loadeddata', e.handleLoadeddata);
}
private removeEventEmitter(element: HTMLElement) {
const r = element.removeEventListener.bind(element);
const e = this.eventHandlers;
r('timeupdate', e.handleTimeUpdate);
r('canplaythrough', e.handleCanPlayThrough);
r('pause', e.handlePause);
r('play', e.handlePlay);
r('playing', e.handlePlaying)
r('ended', e.handleEnded);
r('loadeddata', e.handleLoadeddata);
}
private eventHandlers: { [key: string]: (...args: any[]) => void } = {
handlePlay: (...args) => {
if (this.__paused) {
this.pause();
}
},
handlePlaying: (...args) => {
if (this.audioElement.currentTime === 0) {
this.emit('started');
}
},
handleTimeUpdate: (...args) => {
let time = this.audioElement.duration;
let playedTime = this.audioElement.currentTime;
this.emit('timeupdate', playedTime, time);
},
handleCanPlayThrough: (...args) => {
this.emit('canplaythrough', ...args);
},
handlePause: (...args) => {
if (!this.__paused && !this.audioElement.ended) {
this.play(true).catch((e) => {
console.log(e);
this.pause();
});
}
},
handleEnded: (...args) => {
this.emit('ended', ...args);
if (!this.__paused) {
this.next();
}
},
handlePlayListChange: (...args) => {
this.emit('playlistchange', ...args);
},
handlePlaymodeChange: (mode: string = this.mode) => {
this.emit('playmodechange', mode);
},
handleLoadeddata: (...args) => {
let time = this.audioElement.duration;
let playedTime = this.audioElement.currentTime;
this.emit('timeupdate', playedTime, time);
}
}
public setCurrentTime(currentTime: number | string) {
if (typeof currentTime === 'string') {
currentTime.trim();
if (currentTime[currentTime.length - 1] === '%') {
const percentage = parseFloat(currentTime);
currentTime = this.duration * (percentage / 100);
}
}
if (this.audioElement) {
this.audioElement.currentTime = parseFloat(currentTime.toString());
}
}
private isPlaying() {
return !!this.audioElement && this.audioElement.currentTime > 0 && !this.audioElement.paused && !this.audioElement.ended && this.audioElement.readyState > 2;
}
public openAudio(audio: IAudioItem = this.nowplay) {
if (audio) {
if (audio.type === 'video') {
if (!(this.audioElementType === 'HTMLVideoElement')) {
if(typeof this.audioElement !== 'undefined') {
this.removeEventEmitter(this.audioElement);
this.audioElement.src = '';
}
this.audioElement = document.createElement('video');
if (this.audioElement instanceof HTMLVideoElement) {
this.audioElementType = 'HTMLVideoElement';
this.audioElement.loop = false;
this.audioElement.autoplay = false;
this.audioElement.poster = audio.poster;
this.audioElement.setAttribute('playsinline', 'true');
this.audioElement.setAttribute('webkit-playsinline', 'true');
}
this.initializeEventEmitter(this.audioElement);
this.emit('audioelementchange', this.audioElement);
}
} else {
if (!(this.audioElementType === 'HTMLAudioElement')) {
if(typeof this.audioElement !== 'undefined') {
this.removeEventEmitter(this.audioElement);
this.audioElement.src = '';
}
this.audioElement = new Audio();
this.audioElementType = 'HTMLAudioElement';
this.audioElement.loop = false;
this.audioElement.autoplay = false;
this.initializeEventEmitter(this.audioElement);
this.emit('audioelementchange', this.audioElement);
}
}
this.setVolume(this.volume);
this.audioElement.src = this.nowplay.src;
this.emit('openaudio', audio);
if (!this.__paused) {
this.play();
}
}
}
public toggleMode() {
switch (this.playmodeName) {
case 'listloop': this.setMode('singlecycle'); break;
case 'singlecycle': this.setMode('listrandom'); break;
case 'listrandom': this.setMode('listloop'); break;
}
}
public setMode(playmode: string) {
if (typeof playmode === 'string') {
if (this.playmodeName !== playmode) {
if (playmodes[playmode]) {
this.playmode = new playmodes[playmode](this.playlist, this.nowplaypoint);
this.playmodeName = playmode;
this.eventHandlers.handlePlaymodeChange();
}
}
}
}
public getMode() {
return this.mode;
}
public play(Forced: boolean = false) {
let isPlaying = this.isPlaying();
let res;
if (!isPlaying && this.playlist.length > 0 || Forced) {
res = this.audioElement.play();
}
if (this.__paused) {
this.__paused = false;
this.emit('playstatechange', this.__paused);
this.emit('play');
}
return res;
}
public pause(Forced: boolean = false) {
let isPlaying = this.isPlaying();
if (isPlaying && this.playlist.length > 0 || Forced) {
this.audioElement.pause();
}
if (!this.__paused) {
this.__paused = true;
this.emit('playstatechange', this.__paused);
this.emit('pause');
}
}
public to(id: number) {
this.playmode.to(id);
this.openAudio();
}
public next() {
this.playmode.next();
this.openAudio();
}
public prev() {
this.playmode.prev();
this.openAudio();
}
public togglePlayState() {
if (this.__paused) {
this.play();
} else {
this.pause();
}
}
public add(item: IAudioItem) {
item = (playlistPreFilter([item] as Iplaylist))[0];
this.playmode.addMusic(item);
this.eventHandlers.handlePlayListChange();
if (this.playlist.length === 1) {
this.to(0);
}
}
public remove(item: IAudioItem) {
let needUpdate = this.playmode.removeMusic(item);
this.eventHandlers.handlePlayListChange();
if (needUpdate) {
this.openAudio();
}
}
public setVolume(volume: number | string) {
this._volume = parseFloat(volume as string);
if (this.audioElement)
this.audioElement.volume = Math.max(0.0, Math.min(1.0, this._volume));
this.emit('volumechange', this.volume);
}
public destroy() {
if (this.audioElement) {
this.audioElement.src = null;
this.audioElement.removeEventListener("timeupdate", this.eventHandlers.handleTimeUpdate);
this.removeAllListeners();
}
if (this.view) this.view.destroy();
Object.getOwnPropertyNames(this).forEach((name: keyof cplayer) => delete this[name]);
(this as any).__proto__ = Object;
}
}
if (!process.env.cplayer_noview) {
function parseCPlayerTag() {
Array.prototype.forEach.call(document.querySelectorAll('template[cplayer]'),(element: Element) => {
element.attributes.getNamedItem('loaded') ||
new cplayer({
generateBeforeElement: true,
deleteElementAfterGenerate: true,
element,
...JSON.parse(element.innerHTML)
})
})
}
window.addEventListener("load", parseCPlayerTag);
}
(window as any).cplayer = cplayer;