diff --git a/README.md b/README.md index b584469d..2a1703cc 100644 --- a/README.md +++ b/README.md @@ -33,15 +33,14 @@ - [UPV](https://onime.netlify.app) - free anime no ads - [Binged](https://binge.lol) - Binged - Watch Movies & TV Shows Free - [NUSANIME](https://play.google.com/store/apps/details?id=com.nusanime.app) - Nonton Anime Sub Indo | Nonton Anime Subtitle Indonesia Gratis -- ~~[Animex](https://www.animex.live/) - Watch Anime for free in HD quality with English subbed or dubbed.~~ -- ~~[NGEWIBU.TV](https://ngewibu.tv/) - Nonton Anime Sub Indo | Nonton Anime Subtitle Indonesia Gratis~~ ## Support If you think this is super cool, or useful, and want to donate a little, then you are also super cool! - [Paypal](https://www.paypal.com/paypalme/ShiYiYa) -- [WeChat Pay](https://www.oaii.me/wechat_donate.png) +- [QQ](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=lZb5hM-3CL_UBknmASPHsNuPDgvFObvQ&authKey=h2A2BFkynQX3HhbIxHhMZiSt%2Fp87gljDQ5Q%2FFVwsYdZ%2B7FP7n%2BWdR8WZ1Z0fX3Ss&noverify=0&group_code=861379856) +- [WeChat Pay](https://cdn.jsdelivr.net/gh/shiyiya/shiyiya.github.io@master/source/wechat_donate.png) - ![Star](https://img.shields.io/github/stars/shiyiya/oplayer?style=social) diff --git a/examples/standalone/main.ts b/examples/standalone/main.ts index ea12b3d2..557cdbd0 100644 --- a/examples/standalone/main.ts +++ b/examples/standalone/main.ts @@ -148,6 +148,7 @@ const player = Player.make('#player', { { title: 'hls - muti quality & subtitle & audio', src: 'https://storage.googleapis.com/shaka-demo-assets/angel-one-hls/hls.m3u8' + // danmaku: SUPER_DANMAKU }, { title: 'dash - muti quality & subtitle & audio', diff --git a/packages/core/package.json b/packages/core/package.json index 45d8ca8b..c0a59b6f 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@oplayer/core", - "version": "1.2.36", + "version": "1.2.37", "description": "Oh! Another HTML5 video player.", "type": "module", "main": "./dist/index.es.js", diff --git a/packages/danmaku/package.json b/packages/danmaku/package.json index ef9d7192..bc5f1268 100644 --- a/packages/danmaku/package.json +++ b/packages/danmaku/package.json @@ -1,6 +1,6 @@ { "name": "@oplayer/danmaku", - "version": "1.2.24", + "version": "1.2.25", "description": "Danmaku plugin for oplayer", "type": "module", "main": "./dist/index.es.js", diff --git a/packages/danmaku/src/heatmap.ts b/packages/danmaku/src/heatmap.ts index 15496e05..f577a72d 100644 --- a/packages/danmaku/src/heatmap.ts +++ b/packages/danmaku/src/heatmap.ts @@ -9,13 +9,13 @@ const lib = { const s = Math.round(start / tick) * tick return Array.from( { - length: Math.floor((end - start) / tick), + length: Math.floor((end - start) / tick) }, (_, k) => { return k * tick + s } ) - }, + } } const line = (pointA: number[], pointB: number[]) => { @@ -23,142 +23,185 @@ const line = (pointA: number[], pointB: number[]) => { const lengthY = pointB[1]! - pointA[1]! return { length: Math.sqrt(Math.pow(lengthX, 2) + Math.pow(lengthY, 2)), - angle: Math.atan2(lengthY, lengthX), + angle: Math.atan2(lengthY, lengthX) } } -export default function heatmap(player: Player, danmaku: DanmakuContext, customPoints: Options['heatmap']) { - const $progress = player.context.ui.$progress.firstElementChild - const $wrap = document.createElement('div') - $wrap.style.cssText = `position:absolute;bottom:0.33em;height: 8em;width:100%;pointer-events:none;` - $progress.insertBefore($wrap, $progress.firstChild) - const { offsetHeight: h, offsetWidth: w } = $wrap - - const options = { - xMin: 0, - xMax: w, - yMin: 0, - yMax: 128, - scale: 0.25, - opacity: 0.5, - minHeight: Math.floor(h * 0.05), - sampling: Math.floor(w / 100), - fill: 'rgba(255, 255, 255, 0.5)', - smoothing: 0.2, - flattening: 0.2, +export default class Heatmap { + $root: HTMLDivElement + $start?: HTMLDivElement + $stop?: HTMLDivElement + + loaded: boolean = false + + constructor( + public player: Player, + public danmaku: DanmakuContext, + public heatmap: boolean, + public customHeatmap?: Options['customHeatmap'] + ) { + const $progress = player.context.ui.$progress.firstElementChild + const $root = document.createElement('div') + this.$root = $root + $root.style.cssText = 'position:absolute;bottom:0.33em;height: 8em;width:100%;pointer-events:none;' + $progress.insertBefore($root, $progress.firstChild) + + if (heatmap && (danmaku.comments.length || customHeatmap?.length)) { + this.renderHeatmap() + this.enable() + } + + player.on('videosourcechange', () => { + this.disable() + this.loaded = false + this.customHeatmap = [] + this.$root.innerHTML = '' + player.off('timeupdate', this._update) + player.off('seeked', this._update) + }) } - type Point = [number, number] - const points: Point[] = Array.isArray(customPoints) ? customPoints : [] + _update = () => { + const { player, $start, $stop } = this + $start?.setAttribute('offset', `${(player.currentTime / player.duration) * 100}%`) + $stop?.setAttribute('offset', `${(player.currentTime / player.duration) * 100}%`) + } - if (!points.length) { - const gap = player.duration / w - for (let x = 0; x <= w; x += options.sampling) { - const y = danmaku.comments.filter( - ({ time }) => !!time && time > x * gap && time <= (x + options.sampling) * gap - ).length - points.push([x, y]) + enable(customHeatmap?: Options['customHeatmap']) { + if (customHeatmap) this.customHeatmap = customHeatmap + const { player, $root } = this + $root.style.display = 'block' + + if (!this.loaded) { + if (isNaN(player.duration)) { + player.on('metadataloaded', () => { + this.renderHeatmap() + }) + } else { + this.renderHeatmap() + } } + + player.on(['timeupdate', 'seeked'], this._update) } - const [lastX, lastY] = points[points.length - 1]! - if (lastX !== w) { - points.push([w, lastY]) + disable() { + const { player, $root } = this + $root.style.display = 'none' + player.off('timeupdate', this._update) + player.off('seeked', this._update) } - const yPoints = points.map((point) => point[1]!) - const yMin = Math.min(...yPoints) - const yMax = Math.max(...yPoints) - const yMid = (yMin + yMax) / 2 + renderHeatmap() { + const { player, customHeatmap: customHeatmap, danmaku, $root } = this + if (!this.heatmap || !(danmaku.comments.length || customHeatmap?.length)) { + return + } - for (let i = 0; i < points.length; i++) { - const point = points[i]! - const y = point[1]! - point[1] = y * (y > yMid ? 1 + options.scale : 1 - options.scale) + options.minHeight - } + this.loaded = true + + const { offsetHeight: h, offsetWidth: w } = $root + + const options = { + xMin: 0, + xMax: w, + yMin: 0, + yMax: 128, + scale: 0.25, + opacity: 0.5, + minHeight: Math.floor(h * 0.05), + sampling: Math.floor(w / 100), + fill: 'rgba(255, 255, 255, 0.5)', + smoothing: 0.2, + flattening: 0.2 + } - const controlPoint = (current: any, previous: any, next: any, reverse?: any) => { - const p = previous || current - const n = next || current - const o = line(p, n) - const flat = lib.map(Math.cos(o.angle) * options.flattening, 0, 1, 1, 0) - const angle = o.angle * flat + (reverse ? Math.PI : 0) - const length = o.length * options.smoothing - const x = current[0] + Math.cos(angle) * length - const y = current[1] + Math.sin(angle) * length - return [x, y] - } + type Point = [number, number] + const points: Point[] = Array.isArray(customHeatmap) ? customHeatmap : [] - const bezierCommand = (point: any, i: any, a: any) => { - const cps = controlPoint(a[i - 1], a[i - 2], point) - const cpe = controlPoint(point, a[i - 1], a[i + 1], true) - const close = i === a.length - 1 ? ' z' : '' - return `C ${cps[0]},${cps[1]} ${cpe[0]},${cpe[1]} ${point[0]},${point[1]}${close}` - } + if (!points.length) { + const gap = player.duration / w + for (let x = 0; x <= w; x += options.sampling) { + const y = danmaku.comments.filter( + ({ time }) => !!time && time > x * gap && time <= (x + options.sampling) * gap + ).length + points.push([x, y]) + } + } - const pointsPositions = points.map((e: any) => { - const x = lib.map(e[0], options.xMin, options.xMax, 0, w) - const y = lib.map(e[1], options.yMin, options.yMax, h, 0) - return [x, y] - }) - - const pathD = pointsPositions.reduce( - (acc, e, i, a) => - i === 0 ? `M ${a[a.length - 1]![0]},${h} L ${e[0]},${h} L ${e[0]},${e[1]}` : `${acc} ${bezierCommand(e, i, a)}`, - '' - ) - - const pa = $.css({ - position: 'absolute', - bottom: 0, - [`@global [data-ctrl-hidden=true] &`]: { - opacity: 0, - transition: 'opacity .3s', - }, - }) - - $wrap.innerHTML = ` - - - - - - - - - - - -` - - const $start = $wrap.querySelector('#heatmap-start')! - const $stop = $wrap.querySelector('#heatmap-stop')! - - const updateProgress = () => { - $start.setAttribute('offset', `${(player.currentTime / player.duration) * 100}%`) - $stop.setAttribute('offset', `${(player.currentTime / player.duration) * 100}%`) - } + const [lastX, lastY] = points[points.length - 1]! + if (lastX !== w) { + points.push([w, lastY]) + } - updateProgress() - - player.once('videosourcechange', () => { - $wrap.remove() - danmaku.heatmap = undefined - player.off('timeupdate', updateProgress) - player.off('seeked', updateProgress) - }) - - danmaku.heatmap = { - enable() { - $wrap.style.display = 'block' - player.on(['timeupdate', 'seeked'], updateProgress) - }, - disable() { - $wrap.style.display = 'none' - player.off('timeupdate', updateProgress) - player.off('seeked', updateProgress) - }, - } + const yPoints = points.map((point) => point[1]!) + const yMin = Math.min(...yPoints) + const yMax = Math.max(...yPoints) + const yMid = (yMin + yMax) / 2 + + for (let i = 0; i < points.length; i++) { + const point = points[i]! + const y = point[1]! + point[1] = y * (y > yMid ? 1 + options.scale : 1 - options.scale) + options.minHeight + } + + const controlPoint = (current: any, previous: any, next: any, reverse?: any) => { + const p = previous || current + const n = next || current + const o = line(p, n) + const flat = lib.map(Math.cos(o.angle) * options.flattening, 0, 1, 1, 0) + const angle = o.angle * flat + (reverse ? Math.PI : 0) + const length = o.length * options.smoothing + const x = current[0] + Math.cos(angle) * length + const y = current[1] + Math.sin(angle) * length + return [x, y] + } - danmaku.heatmap.enable() + const bezierCommand = (point: any, i: any, a: any) => { + const cps = controlPoint(a[i - 1], a[i - 2], point) + const cpe = controlPoint(point, a[i - 1], a[i + 1], true) + const close = i === a.length - 1 ? ' z' : '' + return `C ${cps[0]},${cps[1]} ${cpe[0]},${cpe[1]} ${point[0]},${point[1]}${close}` + } + + const pointsPositions = points.map((e: any) => { + const x = lib.map(e[0], options.xMin, options.xMax, 0, w) + const y = lib.map(e[1], options.yMin, options.yMax, h, 0) + return [x, y] + }) + + const pathD = pointsPositions.reduce( + (acc, e, i, a) => + i === 0 + ? `M ${a[a.length - 1]![0]},${h} L ${e[0]},${h} L ${e[0]},${e[1]}` + : `${acc} ${bezierCommand(e, i, a)}`, + '' + ) + + const pa = $.css({ + position: 'absolute', + bottom: 0, + [`@global [data-ctrl-hidden=true] &`]: { + opacity: 0, + transition: 'opacity .3s' + } + }) + + $root.innerHTML = ` + + + + + + + + + + + + ` + + this.$start = $root.querySelector('#heatmap-start')! + this.$stop = $root.querySelector('#heatmap-stop')! + } } diff --git a/packages/danmaku/src/index.ts b/packages/danmaku/src/index.ts index 2f07eb32..ba493dd0 100644 --- a/packages/danmaku/src/index.ts +++ b/packages/danmaku/src/index.ts @@ -3,10 +3,9 @@ import { $, isMobile } from '@oplayer/core' import { default as _Danmaku } from 'danmaku' import { danmakuParseFromUrl } from './danmaku-parse' import type { Comment, DanmakuContext, Options } from './types' - import danmakuSvg from './danmaku.svg?raw' -import heatmap from './heatmap' import { registerInput } from './sender' +import Heatmap from './heatmap' export * from './types' @@ -20,11 +19,12 @@ export default class Danmaku implements PlayerPlugin { player: Player danmaku: DanmakuContext + heatmap: Heatmap loaded: boolean = false $root: HTMLDivElement - options: RequiredPartial = { + options: RequiredPartial = { speed: 144, opacity: 1, engine: 'dom', @@ -69,7 +69,7 @@ export default class Danmaku implements PlayerPlugin { }) this.registerSetting() - this.bootstrap(this.options.source) + this.changeSource(this.options.source) if (this.options.displaySender && !isMobile) { registerInput(player, danmaku, this.options as any) } @@ -77,16 +77,16 @@ export default class Danmaku implements PlayerPlugin { return this } - bootstrap(source: Options['source']) { + changeSource(source: Options['source'], customHeatmap?: Options['customHeatmap']) { if (!source) return this.loaded = false this.options.source = source this.danmaku.clear() if (!this.options.enable) return - this._fetchSource(source) + this._fetchSource(source, customHeatmap) } - async _fetchSource(source: Options['source']) { + async _fetchSource(source: Options['source'], customHeatmap?: Options['customHeatmap']) { try { let danmakus: Comment[] if (typeof source === 'function') { @@ -97,8 +97,8 @@ export default class Danmaku implements PlayerPlugin { danmakus = source! } - const { danmaku, player } = this - const { fontSize, enable, heatmap: heatmapEnable } = this.options + const { danmaku, options } = this + const { fontSize, enable, heatmap } = options //@ts-ignore if (!danmaku._) return @@ -113,15 +113,7 @@ export default class Danmaku implements PlayerPlugin { this.loaded = true this.setFontSize(fontSize) if (enable) danmaku.show() - if (Boolean(heatmapEnable) && danmaku.comments.length > 0) { - if (isNaN(player.duration)) { - player.once('loadedmetadata', () => { - heatmap(player, danmaku, heatmapEnable) - }) - } else { - heatmap(player, danmaku, heatmapEnable) - } - } + if (heatmap) this.heatmap.enable(customHeatmap) } catch (error) { this.player.emit('notice', { text: 'danmaku: ' + (error).message }) throw error @@ -137,7 +129,7 @@ export default class Danmaku implements PlayerPlugin { } registerSetting() { - const { danmaku, player, loaded } = this + const { danmaku, player } = this const { enable, heatmap: heatmapEnable, opacity, area } = this.options player.context.ui?.setting.register({ @@ -155,8 +147,8 @@ export default class Danmaku implements PlayerPlugin { onChange: (value: boolean) => { this.options.enable = value if (value) { - if (!loaded) { - this.bootstrap(this.options.source) + if (!this.loaded) { + this.changeSource(this.options.source) } else { danmaku.show() } @@ -172,8 +164,8 @@ export default class Danmaku implements PlayerPlugin { key: 'heatmap', onChange: (value: boolean) => { this.options.heatmap = value - if (value) danmaku.heatmap?.enable() - else danmaku.heatmap?.disable() + if (value) this.heatmap.enable() + else this.heatmap.disable() } }, { @@ -221,7 +213,7 @@ export default class Danmaku implements PlayerPlugin { } render() { - const { opacity, area, engine, speed } = this.options + const { opacity, area, engine, speed, heatmap, customHeatmap } = this.options const player = this.player this.$root = $.render($.create('div'), player.$root) this.$root.style.cssText = `height:${area * 100}%;opacity:${opacity};font-weight: normal;position: absolute;left: 0;top: 0;width: 100%;height: 100%;overflow: hidden;pointer-events: none;text-shadow: rgb(0 0 0) 1px 0px 1px, rgb(0 0 0) 0px 1px 1px, rgb(0 0 0) 0px -1px 1px, rgb(0 0 0) -1px 0px 1px;color:#fff;` @@ -232,5 +224,6 @@ export default class Danmaku implements PlayerPlugin { comments: [], speed: isMobile ? speed / 1.5 : speed }) as any + this.heatmap = new Heatmap(player, this.danmaku, heatmap, customHeatmap) } } diff --git a/packages/danmaku/src/types.ts b/packages/danmaku/src/types.ts index 81709d1b..2b6f396e 100644 --- a/packages/danmaku/src/types.ts +++ b/packages/danmaku/src/types.ts @@ -15,7 +15,8 @@ export type Options = { /** * @default: true * */ - heatmap?: boolean | Array<[number, number]> + heatmap?: boolean + customHeatmap?: Array<[number, number]> /** * @default 'dom' */ @@ -54,10 +55,4 @@ export interface Comment { export interface DanmakuContext extends Danmaku { comments: Comment[] - heatmap?: { - enable: () => void - disable: () => void - } - bootstrap: (source: Options['source']) => Promise - setFontSize: (size: number) => void } diff --git a/packages/dash/package.json b/packages/dash/package.json index 31a530e8..471387ef 100644 --- a/packages/dash/package.json +++ b/packages/dash/package.json @@ -1,6 +1,6 @@ { "name": "@oplayer/dash", - "version": "1.2.26-beta.0", + "version": "1.2.26", "description": "Dash plugin for oplayer", "type": "module", "main": "./dist/index.es.js", diff --git a/packages/docs/src/pages/docs/guide/start.mdx b/packages/docs/src/pages/docs/guide/start.mdx index ff7ec2bc..480bfea5 100644 --- a/packages/docs/src/pages/docs/guide/start.mdx +++ b/packages/docs/src/pages/docs/guide/start.mdx @@ -28,11 +28,11 @@ Player.make('#oplayer', { { name: 'Japanese', default: true, - src: 'https://cdn.jsdelivr.net/gh/shiyiya/QI-ABSL@master/o/君の名は-jp.srt', + src: 'https://cdn.jsdelivr.net/gh/shiyiya/QI-ABSL@master/o/君の名は-jp.srt' }, { name: 'CH & JP', - src: 'https://cdn.jsdelivr.net/gh/shiyiya/QI-ABSL@master/o/君の名は.srt', + src: 'https://cdn.jsdelivr.net/gh/shiyiya/QI-ABSL@master/o/君の名は.srt' } ] }, @@ -91,12 +91,12 @@ Player.make('#oplayer', { .create() -``` +```` ```html filename="app.html" - + -``` +```` diff --git a/packages/full/package.json b/packages/full/package.json index 30dadf3f..69d52ed6 100644 --- a/packages/full/package.json +++ b/packages/full/package.json @@ -1,7 +1,7 @@ { "name": "@oplayer/full", "version": "0.0.1-beta.3", - "description": "all loader in one", + "description": "Do More, Keep it simple.", "type": "module", "main": "./dist/index.min.js", "types": "./dist/src/index.d.ts", diff --git a/packages/hls/package.json b/packages/hls/package.json index aacaf6dc..ad4c29a6 100644 --- a/packages/hls/package.json +++ b/packages/hls/package.json @@ -1,6 +1,6 @@ { "name": "@oplayer/hls", - "version": "1.2.27-beta.5", + "version": "1.2.27", "description": "Hls plugin for oplayer", "type": "module", "main": "./dist/index.es.js", diff --git a/packages/plugins/package.json b/packages/plugins/package.json index 0a6bf073..62e8d3b0 100644 --- a/packages/plugins/package.json +++ b/packages/plugins/package.json @@ -1,6 +1,6 @@ { "name": "@oplayer/plugins", - "version": "1.0.14-beta.0", + "version": "1.0.14", "author": "shiyiya", "description": "oplayer's plugin", "homepage": "https://github.com/shiyiya/oplayer", diff --git a/packages/plugins/src/playlist.ts b/packages/plugins/src/playlist.ts index 9fb4f65b..d28d8cce 100644 --- a/packages/plugins/src/playlist.ts +++ b/packages/plugins/src/playlist.ts @@ -156,7 +156,7 @@ export default class PlaylistPlugin implements PlayerPlugin { this.player.context.ui.changHighlightSource(highlights) } if (danmaku) { - this.player.context.danmaku?.bootstrap(danmaku) + this.player.context.danmaku?.changeSource(danmaku) } }) }) diff --git a/packages/ui/package.json b/packages/ui/package.json index 13023285..a6c638f0 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@oplayer/ui", - "version": "1.3.3-beta.11", + "version": "1.3.3", "description": "ui plugin for oplayer", "type": "module", "main": "./dist/index.es.js", diff --git a/packages/ui/src/components/Setting.style.ts b/packages/ui/src/components/Setting.style.ts index b1c10f1e..8d7f6292 100644 --- a/packages/ui/src/components/Setting.style.ts +++ b/packages/ui/src/components/Setting.style.ts @@ -150,7 +150,7 @@ export const backRow = $.css({ export const sliderCls = $.css({ '-webkit-appearance': 'none', width: '90%', - height: '0.6em', + height: '0.4em', outline: 'none', 'border-radius': '34px', background: 'rgb(204, 204, 204)', @@ -162,8 +162,8 @@ export const sliderCls = $.css({ '-webkit-appearance': 'none', appearance: 'none', background: '#fff', - height: '1.3em', - width: '1.3em', + height: '1em', + width: '1em', 'border-radius': '50%', border: 'none' }, @@ -179,6 +179,7 @@ export const sliderCls = $.css({ transform: 'translate(-100%, -50%)', content: 'attr(value)', color: 'rgba(255, 255, 255, 0.7)', - visibility: 'hidden' + visibility: 'hidden', + 'font-size': '0.8em' } })