Skip to content

Commit

Permalink
Happy holidays :)
Browse files Browse the repository at this point in the history
  • Loading branch information
shiyiya committed Sep 17, 2024
1 parent a1027c7 commit 30cf8b1
Show file tree
Hide file tree
Showing 15 changed files with 207 additions and 174 deletions.
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
1 change: 1 addition & 0 deletions examples/standalone/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ const player = Player.make<Ctx>('#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',
Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
2 changes: 1 addition & 1 deletion packages/danmaku/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
289 changes: 166 additions & 123 deletions packages/danmaku/src/heatmap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,156 +9,199 @@ 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[]) => {
const lengthX = pointB[0]! - pointA[0]!
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 = `
<svg viewBox="0 0 ${w} ${h}" class="${pa}">
<defs>
<linearGradient id="heatmap-solids" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:var(--primary-color); stop-opacity:${options.opacity}" />
<stop offset="0%" style="stop-color:var(--primary-color); stop-opacity:${options.opacity}" id="heatmap-start" />
<stop offset="0%" style="stop-color:var(--heatmap-color, ${options.fill});" id="heatmap-stop" />
<stop offset="100%" style="stop-color:var(--heatmap-color, ${options.fill});" />
</linearGradient>
</defs>
<path fill="url(#heatmap-solids)" d="${pathD}"></path>
</svg>
`

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 = `
<svg viewBox="0 0 ${w} ${h}" class="${pa}">
<defs>
<linearGradient id="heatmap-solids" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:var(--primary-color); stop-opacity:${options.opacity}" />
<stop offset="0%" style="stop-color:var(--primary-color); stop-opacity:${options.opacity}" id="heatmap-start" />
<stop offset="0%" style="stop-color:var(--heatmap-color, ${options.fill});" id="heatmap-stop" />
<stop offset="100%" style="stop-color:var(--heatmap-color, ${options.fill});" />
</linearGradient>
</defs>
<path fill="url(#heatmap-solids)" d="${pathD}"></path>
</svg>
`

this.$start = $root.querySelector('#heatmap-start')!
this.$stop = $root.querySelector('#heatmap-stop')!
}
}
Loading

0 comments on commit 30cf8b1

Please sign in to comment.