diff --git a/.vscode/settings.json b/.vscode/settings.json index 52652b8..f23cea0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,7 @@ { "cSpell.words": ["danmaku", "danmakuload", "stime"], "conventionalCommits.scopes": ["repo", "utils", "player", "pages", "start"], + "prettier.printWidth": 120, "markdownlint.config": { "MD001": false, "MD033": false, diff --git a/public/index.html b/public/index.html index 3de3504..12af695 100644 --- a/public/index.html +++ b/public/index.html @@ -2,10 +2,7 @@ - + @@ -15,11 +12,7 @@ - + (Last: None ) @@ -34,11 +27,7 @@ Time Offset: - + @@ -69,8 +58,7 @@ return document.getElementById(id); } (function () { - const lastVideoFile = - window.localStorage.getItem('lastVideoFile'); + const lastVideoFile = window.localStorage.getItem('lastVideoFile'); if (lastVideoFile) { id('last-video-file').textContent = lastVideoFile; } @@ -82,25 +70,14 @@ window.localStorage.setItem('lastVideoFile', video.name); const danmaku = id('danmaku-file').files[0]; - const danmakuUrl = danmaku - ? URL.createObjectURL(danmaku) - : null; + const danmakuUrl = danmaku ? URL.createObjectURL(danmaku) : null; - const player = new Player( - id('player'), - __player_metadata__, - video.name, - videoUrl, - danmakuUrl, - { - autoPlay: id('auto-play').checked, - fullscreen: id('fullscreen').checked, - muted: id('muted').checked, - danmakuTimeOffset: parseFloat( - id('danmaku-time-offset').value - ), - } - ); + const player = new Player(id('player'), __player_metadata__, video.name, videoUrl, danmakuUrl, { + autoPlay: id('auto-play').checked, + fullscreen: id('fullscreen').checked, + muted: id('muted').checked, + danmakuTimeOffset: parseFloat(id('danmaku-time-offset').value), + }); window.player = player; id('start-panel').remove(); id('player').removeAttribute('style'); diff --git a/src/player.main.ts b/src/player.main.ts index f85bd0f..3772abb 100644 --- a/src/player.main.ts +++ b/src/player.main.ts @@ -53,9 +53,7 @@ class Player { { this.options.autoPlay ? this.play() : this.pause(); this.options.muted ? this.mute() : this.unmute(); - this.options.fullscreen - ? this.requestFullscreen() - : this.setContainerData('fullscreen', false); + this.options.fullscreen ? this.requestFullscreen() : this.setContainerData('fullscreen', false); } this.constructed = true; if (this.options.autoPlay) this.toast('Autoplay'); @@ -71,15 +69,9 @@ class Player { this.setContainerData('paused', this.video.paused); this.overHour = this.video.duration >= 60 * 60; }); - this.onVideoEvent('play', () => - this.setContainerData('paused', this.video.paused) - ); - this.onVideoEvent('pause', () => - this.setContainerData('paused', this.video.paused) - ); - this.onVideoEvent('volumechange', () => - this.setContainerData('muted', this.video.muted) - ); + this.onVideoEvent('play', () => this.setContainerData('paused', this.video.paused)); + this.onVideoEvent('pause', () => this.setContainerData('paused', this.video.paused)); + this.onVideoEvent('volumechange', () => this.setContainerData('muted', this.video.muted)); this.onPlayerEvent('fullscreenchange', () => { const fullscreen = document.fullscreenElement === this.container; this.setContainerData('fullscreen', fullscreen ? true : false); @@ -118,9 +110,7 @@ class Player { this.container.addEventListener(type, listener); } firePlayerEvent(type: string, detail?: any) { - this.container.dispatchEvent( - new CustomEvent(type, detail ? { detail: detail } : null) - ); + this.container.dispatchEvent(new CustomEvent(type, detail ? { detail: detail } : null)); } toast(html: string) { if (this.constructed) { @@ -129,11 +119,7 @@ class Player { } seek(time: number) { const fixedTime = clamp(time, 0, this.video.duration); - this.toast( - `Seek: ${fTime(fixedTime, this.overHour)} / ${fTime( - this.video.duration - )}` - ); + this.toast(`Seek: ${fTime(fixedTime, this.overHour)} / ${fTime(this.video.duration)}`); this.video.currentTime = fixedTime; } seekPercent(percent: number) { diff --git a/src/player.metadata.ts b/src/player.metadata.ts index 990deef..f930d73 100644 --- a/src/player.metadata.ts +++ b/src/player.metadata.ts @@ -1,18 +1,12 @@ type MetaEventTypes = 'selfEvent' | 'playerEvent' | 'videoEvent'; -type MetaEvents = - | EventListenerMap - | (() => EventListenerMap); +type MetaEvents = EventListenerMap | (() => EventListenerMap); type ElementMetaEvents = MetaEvents< (player: Player, element: HTMLElementTagNameMap[T]) => void >; type ElementVideoMetaEvents = MetaEvents< - ( - player: Player, - element: HTMLElementTagNameMap[T], - video: HTMLVideoElement - ) => void + (player: Player, element: HTMLElementTagNameMap[T], video: HTMLVideoElement) => void >; function bindMetadataEvents( // TODO improve @@ -112,10 +106,7 @@ class EDC { { selfEvent: [element, () => [player, element]], playerEvent: [player.container, () => [player, element]], - videoEvent: [ - player.video, - () => [player, element, player.video], - ], + videoEvent: [player.video, () => [player, element, player.video]], } ); for (const childBulder of this._childrenBuilders) { diff --git a/src/player.player.ts b/src/player.player.ts index d418ecf..a2c5c3a 100644 --- a/src/player.player.ts +++ b/src/player.player.ts @@ -17,10 +17,7 @@ const __player_metadata__: PlayerMetadata = { E.innerHTML = T.detail.content; opacityVisible(E); clearTimeout(P._dyn.toastTimer); - P._dyn.toastTimer = setTimeout( - () => opacityInvisible(E), - 800 - ); + P._dyn.toastTimer = setTimeout(() => opacityInvisible(E), 800); }, }), new EDC('div') @@ -43,9 +40,7 @@ const __player_metadata__: PlayerMetadata = { .children( new EDC('button', 'playToggle') // .attr({ class: 'play-toggle' }) - .css((s) => - toggleByPlayerData('paused', s.attributes.class) - ) + .css((s) => toggleByPlayerData('paused', s.attributes.class)) .selfEvents({ click: (P) => P.togglePlay() }) .children(...newSpans('⏵', '⏸')), new EDC('div') // @@ -53,18 +48,11 @@ const __player_metadata__: PlayerMetadata = { .children( new EDC('button', 'muteToggle') .attr({ class: 'mute-toggle' }) - .css((s) => - toggleByPlayerData( - 'muted', - s.attributes.class - ) - ) + .css((s) => toggleByPlayerData('muted', s.attributes.class)) .selfEvents({ click: (P) => P.toggleMute(), }) - .children( - ...newSpans('Mute', 'Mute') - ), + .children(...newSpans('Mute', 'Mute')), new EDC('input', 'volume') .attr({ class: 'volume', @@ -75,18 +63,14 @@ const __player_metadata__: PlayerMetadata = { value: '100', }) .selfEvents({ - input: (P, E) => - P.setVolume(E.valueAsNumber / 100), + input: (P, E) => P.setVolume(E.valueAsNumber / 100), }) .playerEvents({ mute: (_, E) => (E.disabled = true), unmute: (_, E) => (E.disabled = false), }) .videoEvents({ - volumechange: (_, E, V) => - (E.valueAsNumber = Math.round( - V.volume * 100 - )), + volumechange: (_, E, V) => (E.valueAsNumber = Math.round(V.volume * 100)), }) ), new EDC('div') @@ -106,36 +90,23 @@ const __player_metadata__: PlayerMetadata = { create: (_, E) => (E.valueAsNumber = 0), change: (P, E) => { P.seekPercent(E.valueAsNumber); - opacityInvisible( - P.elements.progressPopup - ); + opacityInvisible(P.elements.progressPopup); P._dyn.progressInputting = false; }, input: (P, E) => { P._dyn.progressInputting = true; const value = E.valueAsNumber; - const popup = - P.elements.progressPopup; - popup.textContent = fTime( - P.video.duration * value - ); - popup.style.left = `calc(${ - value * 100 - }% + (${ - 8 - value * 100 * 0.15 - }px))`; - popup.style.transform = - 'translateX(' + - -popup.offsetWidth / 2 + - 'px)'; + const popup = P.elements.progressPopup; + popup.textContent = fTime(P.video.duration * value); + popup.style.left = `calc(${value * 100}% + (${8 - value * 100 * 0.15}px))`; + popup.style.transform = 'translateX(' + -popup.offsetWidth / 2 + 'px)'; opacityVisible(popup); }, }) .videoEvents({ timeupdate: (P, E, V) => { if (!P._dyn.progressInputting) { - const v = - V.currentTime / V.duration; + const v = V.currentTime / V.duration; E.valueAsNumber = v ? v : 0; } }, @@ -148,16 +119,8 @@ const __player_metadata__: PlayerMetadata = { new EDC('div') .attr({ class: 'time-label' }) .selfEvents({ - mouseover: (P) => - toggleDisplayBi( - P.elements.timeInput, - P.elements.timeCurrent - ), - mouseleave: (P) => - toggleDisplayBi( - P.elements.timeCurrent, - P.elements.timeInput - ), + mouseover: (P) => toggleDisplayBi(P.elements.timeInput, P.elements.timeCurrent), + mouseleave: (P) => toggleDisplayBi(P.elements.timeCurrent, P.elements.timeInput), }) .children( new EDC('input', 'timeInput') @@ -171,48 +134,31 @@ const __player_metadata__: PlayerMetadata = { if (E.validity.valid) { P.seek(timeToSeconds(E.value)); } else { - E.value = fTime( - P.video.currentTime - ); + E.value = fTime(P.video.currentTime); } }, }) .videoEvents({ canplay: (P, E, V) => { E.step = P.overHour ? '1' : '0'; - E.value = fTime( - V.currentTime, - P.overHour - ); + E.value = fTime(V.currentTime, P.overHour); E.max = fTime(V.duration); }, timeupdate: (P, E, V) => { - E.value = fTime( - V.currentTime, - P.overHour - ); + E.value = fTime(V.currentTime, P.overHour); }, }), new EDC('span', 'timeCurrent') // .html('--:--') .videoEvents({ - canplay: (P, E, V) => - (E.textContent = fTime( - V.currentTime, - P.overHour - )), - timeupdate: (P, E, V) => - (E.textContent = fTime( - V.currentTime, - P.overHour - )), + canplay: (P, E, V) => (E.textContent = fTime(V.currentTime, P.overHour)), + timeupdate: (P, E, V) => (E.textContent = fTime(V.currentTime, P.overHour)), }), new EDC('span').html(' / '), new EDC('span') .html('--:--') // .videoEvents({ - canplay: (_, E, V) => - (E.textContent = fTime(V.duration)), + canplay: (_, E, V) => (E.textContent = fTime(V.duration)), }) ), new EDC('div', 'danmaku-controls') @@ -220,52 +166,33 @@ const __player_metadata__: PlayerMetadata = { .attr({ class: 'danmaku-controls' }) .children( new EDC('button', 'danmakuToggle') - .condition((P) => - P.danmakuUrl ? true : false - ) + .condition((P) => (P.danmakuUrl ? true : false)) .attr({ class: 'danmaku-toggle' }) - .css((s) => - toggleByPlayerData( - 'danmaku-on', - s.attributes.class - ) - ) + .css((s) => toggleByPlayerData('danmaku-on', s.attributes.class)) .selfEvents({ click: (P) => { if (!P._dyn.danmakuOn) { P._dyn.danmakuOn = true; P.commentManager.start(); - P.setContainerData( - 'danmakuOn', - true - ); + P.setContainerData('danmakuOn', true); P.toast('Danmaku On'); } else { P._dyn.danmakuOn = false; P.commentManager.clear(); P.commentManager.stop(); - P.setContainerData( - 'danmakuOn', - false - ); + P.setContainerData('danmakuOn', false); P.toast('Danmaku Off'); } }, }) - .children( - ...newSpans('Danmaku', 'Danmaku') - ), + .children(...newSpans('Danmaku', 'Danmaku')), new EDC('button', 'danmakuListToggle') // .html('?') // .selfEvents({ - click: (P) => - toggleDisplay( - P.elements.danmakuList - ), + click: (P) => toggleDisplay(P.elements.danmakuList), }) .playerEvents({ - danmakuload: (P, E) => - (E.innerHTML = `(${P.commentManager.timeline.length})`), + danmakuload: (P, E) => (E.innerHTML = `(${P.commentManager.timeline.length})`), }), new EDC('input') .attr({ @@ -276,26 +203,18 @@ const __player_metadata__: PlayerMetadata = { }) .selfEvents({ create: (P, E) => { - if (!P.options.danmakuTimeOffset) - P.options.danmakuTimeOffset = 0; - E.valueAsNumber = - P.options.danmakuTimeOffset; + if (!P.options.danmakuTimeOffset) P.options.danmakuTimeOffset = 0; + E.valueAsNumber = P.options.danmakuTimeOffset; }, input: (P, E) => { - P.options.danmakuTimeOffset = - E.valueAsNumber; + P.options.danmakuTimeOffset = E.valueAsNumber; P.commentManager.clear(); }, }) ), new EDC('button', 'fullscreenToggle') .attr({ class: 'fullscreen-toggle' }) - .css((s) => - toggleByPlayerData( - 'fullscreen', - s.attributes.class - ) - ) + .css((s) => toggleByPlayerData('fullscreen', s.attributes.class)) .selfEvents({ click: (P) => P.toggleFullscreen(), }) @@ -310,16 +229,11 @@ const __player_metadata__: PlayerMetadata = { .playerEvents({ danmakuload: async (P, E) => { const timeline = P.commentManager.timeline; - const overHour = timeline - ? timeline[timeline.length - 1].stime >= 36e5 - : false; + const overHour = timeline ? timeline[timeline.length - 1].stime >= 36e5 : false; let html = ''; for (const data of timeline) { html += // for performance, do not use document.createElement - `
  • ${fTime( - data.stime / 1e3, - overHour - )}` + + `
  • ${fTime(data.stime / 1e3, overHour)}` + `${data.text}
  • `; } E.innerHTML = html; @@ -337,31 +251,19 @@ const __player_metadata__: PlayerMetadata = { .condition((P) => (P.danmakuUrl ? true : false)) .selfEvents({ create: (P, E) => { - P.commentManager = initDanmaku( - E, - P.danmakuUrl, - () => P.firePlayerEvent('danmakuload') - ); + P.commentManager = initDanmaku(E, P.danmakuUrl, () => P.firePlayerEvent('danmakuload')); P._dyn.danmakuOn = true; P.setContainerData('danmakuOn', true); - if (!P.options.danmakuTimeOffset) - P.options.danmakuTimeOffset = 0; + if (!P.options.danmakuTimeOffset) P.options.danmakuTimeOffset = 0; }, }) .videoEvents({ timeupdate: (P, _, V) => { if (!P._dyn.danmakuOn) return; const cm = P.commentManager; - const time = Math.floor( - 1e3 * - (V.currentTime - - P.options.danmakuTimeOffset) - ); + const time = Math.floor(1e3 * (V.currentTime - P.options.danmakuTimeOffset)); const deltaTime = time - cm._lastPosition; - if ( - deltaTime < 0 || - deltaTime > cm.options.seekTrigger - ) { + if (deltaTime < 0 || deltaTime > cm.options.seekTrigger) { cm.clear(); } cm.time(time); @@ -414,15 +316,13 @@ const __player_metadata__: PlayerMetadata = { break; case 73: // I P.toast( - `${new Date().toLocaleTimeString()}
    ${fTime( - P.video.currentTime, - P.overHour - )} / ${fTime(P.video.duration)}` + `${new Date().toLocaleTimeString()}
    ${fTime(P.video.currentTime, P.overHour)} / ${fTime( + P.video.duration + )}` ); break; case 68: // D - if (P.elements.danmakuToggle) - P.elements.danmakuToggle.click(); + if (P.elements.danmakuToggle) P.elements.danmakuToggle.click(); break; default: break; diff --git a/src/utils.ts b/src/utils.ts index 84aa902..d1a20b3 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -12,25 +12,18 @@ interface StrKV { type AnyFunction = (...args: any[]) => any; -type AppendArguments = F extends ( - ...arg: [...infer P] -) => infer R +type AppendArguments = F extends (...arg: [...infer P]) => infer R ? (...args: [...P, ...A]) => R : never; -type PrependArguments = F extends ( - ...arg: [...infer P] -) => infer R +type PrependArguments = F extends (...arg: [...infer P]) => infer R ? (...args: [...A, ...P]) => R : never; type ExtendedHTMLEventMap = StrAnyKV & HTMLElementEventMap; type EventListenerMap = { - [key in keyof ExtendedHTMLEventMap]?: AppendArguments< - F, - [ExtendedHTMLEventMap[key]] - >; + [key in keyof ExtendedHTMLEventMap]?: AppendArguments; }; type HTMLTagNames = keyof HTMLElementTagNameMap; @@ -115,11 +108,7 @@ function timeToSeconds(time: string): number { return time.split(':').reduce((acc, time) => 60 * acc + parseInt(time)); } -function bindEvent( - target: HTMLElement, - entries: { [s: string]: Function }, - params: any[] -) { +function bindEvent(target: HTMLElement, entries: { [s: string]: Function }, params: any[]) { for (const [type, listener] of Object.entries(entries)) { target.addEventListener(type, (event) => listener(...params, event)); } @@ -127,14 +116,8 @@ function bindEvent( function initDanmaku(stage: HTMLElement, url: string, onload: () => void) { const provider = new CommentProvider(); - provider.addStaticSource( - CommentProvider.XMLProvider('GET', url, null, null), - CommentProvider.SOURCE_XML - ); - provider.addParser( - new BilibiliFormat.XMLParser(), - CommentProvider.SOURCE_XML - ); + provider.addStaticSource(CommentProvider.XMLProvider('GET', url, null, null), CommentProvider.SOURCE_XML); + provider.addParser(new BilibiliFormat.XMLParser(), CommentProvider.SOURCE_XML); const commentManager = new CommentManager(stage); // @ts-expect-error provider.addTarget(commentManager);