From 0805e1225e2bc82a7acdbc0706da0fe92e316141 Mon Sep 17 00:00:00 2001 From: Akari <60416767+MotooriKashin@users.noreply.github.com> Date: Thu, 1 Aug 2024 16:28:50 +0800 Subject: [PATCH] =?UTF-8?q?=E8=8E=B7=E5=8F=96=E6=92=AD=E6=94=BE=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E5=85=A8=E9=83=A8=E5=86=85=E5=AE=B9=20(#7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 推荐视频使用虚拟列表 * 获取播单全部内容 --- .../bilibili/api/x/v3/fav/resource/list.ts | 6 +- src/main/bilibili/index.ts | 17 +++- src/main/bilibili/player/index.ts | 4 +- src/player/auxiliary/danmaku/index.ts | 4 +- src/player/auxiliary/recommend/index.ts | 98 ++++++++++++++++--- .../style/auxiliary/recommend/index.css | 10 ++ 6 files changed, 117 insertions(+), 22 deletions(-) diff --git a/src/io/com/bilibili/api/x/v3/fav/resource/list.ts b/src/io/com/bilibili/api/x/v3/fav/resource/list.ts index e40e36a..5868ec0 100644 --- a/src/io/com/bilibili/api/x/v3/fav/resource/list.ts +++ b/src/io/com/bilibili/api/x/v3/fav/resource/list.ts @@ -10,6 +10,8 @@ export async function favResourceList( media_id: number | string, pn = 1, ) { + const key = `${media_id},${pn}`; + if (CATCH[key]) return CATCH[key]; const url = new URL(Api + '/x/v3/fav/resource/list'); url.searchParams.set('media_id', media_id); url.searchParams.set('pn', pn); @@ -20,9 +22,11 @@ export async function favResourceList( url.searchParams.set('tid', '0'); url.searchParams.set('platform', 'web'); const response = await fetch(url, { credentials: 'include' }); - return (await response.json()).data; + return (CATCH[key] = (await response.json()).data); } +const CATCH: Record = {}; + interface IFavResourceList { has_more: boolean; info: IFavResourceInfo; diff --git a/src/main/bilibili/index.ts b/src/main/bilibili/index.ts index b2b3c2b..ca743f8 100644 --- a/src/main/bilibili/index.ts +++ b/src/main/bilibili/index.ts @@ -393,8 +393,8 @@ export class Router { const path = url.pathname.split('/'); const ml = +path[2].slice(2); if (ml) { - favResourceList(ml).then(media => { - this.aid = Number(url.searchParams.get('aid')) || media.medias[0].id; + favResourceList(ml).then(({ medias, has_more }) => { + this.aid = Number(url.searchParams.get('aid')) || medias[0].id; if (this.aid) { Promise.allSettled([cards({ av: this.aid }), pagelist(this.aid), detail(this.aid)]) .then(([cards, pagelist, detail]) => { @@ -423,7 +423,7 @@ export class Router { } if (this.cid) { this.$player.connect(this.aid, this.cid, this.ssid, this.epid); - page ? this.$player.partMedialist(media, page) : this.$player.partMedialist(media); + page ? this.$player.partMedialist(medias, page) : this.$player.partMedialist(medias); if (de && de.View) { this.$info.avDetail(de); this.$desc.update(de); @@ -433,6 +433,17 @@ export class Router { } } } + + // 请求更多媒体 + let pn = 2; + const getMore = () => { + favResourceList(ml, pn).then(({ medias, has_more }) => { + pn++; + this.$player.partMedialist(medias); + has_more && getMore(); + }) + } + has_more && getMore(); }); this.$comment.oid = this.aid; } else { diff --git a/src/main/bilibili/player/index.ts b/src/main/bilibili/player/index.ts index 8d69080..3dea90a 100644 --- a/src/main/bilibili/player/index.ts +++ b/src/main/bilibili/player/index.ts @@ -661,10 +661,10 @@ export class BilibiliPlayer extends Player { } /** 播放列表分P管理 */ - partMedialist(page: Awaited>, part?: Awaited>) { + partMedialist(page: Awaited>['medias'], part?: Awaited>) { const ids: string[] = []; let ci = 0; - this.$auxiliary.$recommend.add(page.medias.map(d => { + this.$auxiliary.$recommend.add(page.map(d => { d.id === this.aid && (ci = ids.length); if (d.page === 1) { const url = new URL(location.href); diff --git a/src/player/auxiliary/danmaku/index.ts b/src/player/auxiliary/danmaku/index.ts index 08f7d32..13c9785 100644 --- a/src/player/auxiliary/danmaku/index.ts +++ b/src/player/auxiliary/danmaku/index.ts @@ -135,7 +135,7 @@ export class Danmaku extends HTMLDivElement { }); new ResizeObserver(this.observeResizeCallback).observe(this.$list); - new IntersectionObserver(this.observeCallback).observe(this.$list); + new IntersectionObserver(this.observeIntersectionCallback).observe(this.$list); this.$list.addEventListener('scroll', () => { const { scrollTop } = this.$list; const startindex = Math.floor(scrollTop / 24); @@ -157,7 +157,7 @@ export class Danmaku extends HTMLDivElement { }); } - private observeCallback = (entries: IntersectionObserverEntry[]) => { + private observeIntersectionCallback = (entries: IntersectionObserverEntry[]) => { let isIntersecting = false; for (const entry of entries) { isIntersecting = entry.isIntersecting; diff --git a/src/player/auxiliary/recommend/index.ts b/src/player/auxiliary/recommend/index.ts index be6bb5c..568fbf7 100644 --- a/src/player/auxiliary/recommend/index.ts +++ b/src/player/auxiliary/recommend/index.ts @@ -36,24 +36,89 @@ export class Recommend extends HTMLDivElement { /** 每当元素被移动到新文档中时调用。 */ // adoptedCallback() {} + private $items: IRecommend[] = []; + + private $startIndex = 0; + + private $seeLength = 0; + constructor() { super(); this.classList.add('bofqi-auxiliary-recommend'); ev.bind(PLAYER_EVENT.IDENTIFY, this.identify); + + new ResizeObserver(this.observeResizeCallback).observe(this); + new IntersectionObserver(this.observeIntersectionCallback).observe(this); + this.addEventListener('scroll', () => { + const { scrollTop } = this; + const startindex = Math.floor(scrollTop / 72); + const { length } = this.$items; + if (this.$startIndex < startindex) { + // 向下滚动 + for (; (this.$startIndex < startindex && (this.$startIndex + this.$seeLength) < length); this.$startIndex++) { + this.appendChild(this.renderItem(this.$items[this.$startIndex + this.$seeLength], this.$startIndex + this.$seeLength + 1)); + this.firstElementChild?.remove(); + } + } else { + // 向上滚动 + for (; (this.$startIndex > startindex && (this.$startIndex - 1) >= 0); this.$startIndex--) { + this.prepend(this.renderItem(this.$items[this.$startIndex - 1], this.$startIndex)); + this.lastElementChild?.remove(); + } + } + this.marginFix(); + }); } - add(items: IRecommend | IRecommend[]) { - Array.isArray(items) || (items = [items]); - const p = document.createDocumentFragment(); - let i = 0; - for (const d of items) { - const div = document.createElement('div'); - div.classList.add('recommend'); - d.selected && div.classList.add('selected'); - div.dataset.index = ++i; - div.innerHTML = ` + private observeIntersectionCallback = (entries: IntersectionObserverEntry[]) => { + let isIntersecting = false; + for (const entry of entries) { + isIntersecting = entry.isIntersecting; + } + + if (!isIntersecting) { + this.$startIndex = 0; + } + } + + private observeResizeCallback = (entries: ResizeObserverEntry[]) => { + let blockSize = 0; + for (const entry of entries) { + for (const borderBoxSize of entry.borderBoxSize) { + borderBoxSize.blockSize && (blockSize = borderBoxSize.blockSize); + } + } + + if (blockSize) { + this.render(blockSize); + } + } + + private render(blockSize: number) { + this.replaceChildren(); + const { length } = this.$items; + const max = Math.ceil(blockSize / 72) + 2; + for (let i = 0; (i < max && (this.$startIndex + i) < length); i++) { + this.appendChild(this.renderItem(this.$items[this.$startIndex + i], this.$startIndex + i + 1)); + this.$seeLength = i + 1; + } + this.marginFix(); + } + + private marginFix() { + const { length } = this.$items; + this.style.setProperty('--margin-block-start', `${this.$startIndex * 72}px`); + this.style.setProperty('--margin-block-end', `${(length - this.$seeLength - this.$startIndex) * 72}px`); + } + + private renderItem(d: IRecommend, i: number) { + const div = document.createElement('div'); + div.classList.add('recommend'); + d.selected && div.classList.add('selected'); + div.dataset.index = i; + div.innerHTML = `
${d.title}
${d.duration ? Format.fmSeconds(d.duration) : ''}
@@ -66,13 +131,18 @@ export class Recommend extends HTMLDivElement { ${d.author ? `
${svg_upper + d.author}
` : ''}
`; - d.callback && div.addEventListener('click', d.callback); - p.appendChild(div); - } - this.appendChild(p); + d.callback && div.addEventListener('click', d.callback); + return div + } + + add(items: IRecommend | IRecommend[]) { + this.$items = this.$items.concat(items); + this.hasChildNodes() || (this.offsetHeight && this.render(this.offsetHeight)); } private identify = () => { + this.$startIndex = 0; + this.$items.length = 0; this.replaceChildren(); } } diff --git a/src/player/style/auxiliary/recommend/index.css b/src/player/style/auxiliary/recommend/index.css index 4143169..a5f8f03 100644 --- a/src/player/style/auxiliary/recommend/index.css +++ b/src/player/style/auxiliary/recommend/index.css @@ -7,6 +7,8 @@ scrollbar-width: thin; scrollbar-gutter: stable; content-visibility: auto; + --margin-block-start: 0; + --margin-block-end: 0; &:empty { align-items: center; @@ -126,5 +128,13 @@ color: #00a1d6; } } + + &:first-child { + margin-block-start: var(--margin-block-start); + } + + &:last-child { + margin-block-end: var(--margin-block-end); + } } } \ No newline at end of file