@oplayer/danmaku
Version:
Danmaku plugin for oplayer
363 lines (329 loc) • 10.3 kB
text/typescript
import { Player, $ } from '@oplayer/core'
import Danmaku from 'danmaku'
import { getMode } from './danmaku-parse'
import { Options, Comment } from './types'
function registerInputStyle() {
const modeSelectionWrap = $.css(`
display: none;
box-sizing: border-box;
width: 216px;
height: auto;
padding: 2px 0 0;
position: absolute;
bottom: 100%;
left: 50%;
margin-left: -108px;
background: rgba(21,21,21,.9);
border-radius: 2px;
user-select: none;`)
const modeSelectionRowSelection = $.css(`
display: flex;
flex-wrap: wrap;
margin: 8px -8px 0 0;`)
const danmakuTypeWrap = $.css({
width: '30px',
height: '30px',
padding: '3px 0',
'line-height': '30px',
'text-align': 'center',
color: 'hsla(0,0%,100%,.8)',
fill: '#757575',
position: 'relative',
'box-sizing': 'border-box',
cursor: 'pointer',
'&:hover': {
fill: 'var(--primary-color)'
},
[`&:hover .${modeSelectionWrap}`]: { display: 'block' },
[`& .${modeSelectionRowSelection} label`]: {
flex: 1,
'margin-bottom': '8px'
},
'& input[type="radio"]': { display: 'none' },
'& input[type="radio"]:checked + label > div': {
background: 'var(--primary-color)'
}
})
const danmakuType = $.css(`
display: inline-flex;
height: 100%;
align-items: center;
`)
const danmakuTypeIcon = $.css(`
display: block;
width: 36px;
height: 24px;
`)
const danmakuTypeSvg = $.css(`width: 100%;height: 100%;transition: fill .15s ease-in-out;`)
const modeSelectionRow = $.css(`
min-height: 22px;
margin: 10px 20px;
width: 176px;
line-height: 22px;
font-size: 12px;`)
const modeSelectionRowTitle = $.css(`text-align: left;color: #fff; line-height: 16px;`)
const modeSelectionSpan = $.css(`
position: relative;
cursor: pointer;
border-radius: 2px;
color: #fff;
text-align: center;
margin-right: 8px;
background: hsla(0,0%,100%,.2);
font-size: 12px;
`)
const colorPickerInput = $.css(`
outline: none;
background-color: transparent;
color: #fff;
border: 1px solid hsla(0,0%,100%,.2);
border-radius: 2px;
padding: 4px 7px;
margin: 0 auto 8px auto;
transition: background .2s;
color:#000;background:#fff;text-shadow: 0px 0px 6px #FFF;text-align: center;
`)
const colorPickerWrap = $.css(
`display: grid;grid-template-columns: repeat(7,auto);width: 100%;justify-items: center;`
)
const activeColor = $.css('box-shadow: 0 0 1px 1px #fff;border-color: #000;')
const colorPicker = $.css(`
width: 16px;
height: 16px;
border: 1px solid rgba(0,0,0,.3);
box-sizing: border-box;
border-radius: 2px;
margin-bottom: 4px;
cursor: pointer;
display: inline-block;
`)
const inputBar = $.css(`
min-width: 25em;
border-radius: 6px;
background: #f4f4f4;
color: #999;
height:85%;
`)
const inputBarWrap = $.css(`
flex: 1;
display: flex;
align-items: center;
`)
const input = $.css(`
flex-grow: 1;
padding: 0 8px;
height: 28px;
border: 0;
-webkit-box-sizing: border-box;
box-sizing: border-box;
background: none;
line-height: 28px;
color: #212121;
font-size: 12px;
text-decoration: none;
outline: none;
touch-action: manipulation;
`)
const send = $.css(`
height: 100%;
width: 62px;
min-width: 62px;
border-radius: 0 6px 6px 0;
cursor: pointer;
width: 60px;
min-width: 60px;
box-sizing: border-box;
overflow: hidden;
`)
const sendBottom = $.css(`
height: 100%;
background-color: var(--primary-color,#6668ab);
color: #fff;
min-width: 60px;
outline: none;
font-size: 14px;
display: flex;
align-items: center;
justify-content: center;
`)
return {
inputBar,
inputBarWrap,
input,
send,
sendBottom,
danmakuTypeWrap,
danmakuType,
danmakuTypeIcon,
danmakuTypeSvg,
modeSelectionWrap,
modeSelectionRow,
modeSelectionRowTitle,
modeSelectionRowSelection,
modeSelectionSpan,
colorPicker,
colorPickerInput,
colorPickerWrap,
activeColor
}
}
export function registerInput(player: Player, danmaku: Danmaku, options: Options) {
if (!player.context.ui) return
const {
inputBar,
inputBarWrap,
input,
send,
sendBottom,
danmakuTypeWrap,
danmakuType,
danmakuTypeIcon,
danmakuTypeSvg,
modeSelectionWrap,
modeSelectionRow,
modeSelectionRowTitle,
modeSelectionRowSelection,
modeSelectionSpan,
colorPickerWrap,
colorPicker,
colorPickerInput,
activeColor
} = registerInputStyle()
const $tpl = $.create(
`div.${inputBar}`,
{},
`<div class="${inputBarWrap}">
<div class="${danmakuTypeWrap}">
<span class="${danmakuType}">
<span class="${danmakuTypeIcon}">
<svg viewBox="0 0 22 22" class="${danmakuTypeSvg}">
<path d="M17 16H5c-.55 0-1 .45-1 1s.45 1 1 1h12c.55 0 1-.45 1-1s-.45-1-1-1zM6.96 15c.39 0 .74-.24.89-.6l.65-1.6h5l.66 1.6c.15.36.5.6.89.6.69 0 1.15-.71.88-1.34l-3.88-8.97C11.87 4.27 11.46 4 11 4s-.87.27-1.05.69l-3.88 8.97c-.27.63.2 1.34.89 1.34zM11 5.98L12.87 11H9.13L11 5.98z" />
</svg>
</span>
</span class="${inputBarWrap}">
<div class="${modeSelectionWrap}">
<div class="${modeSelectionRow}">
<div class="${modeSelectionRowTitle}">字号</div>
<div class="${modeSelectionRowSelection}">
<input type="radio" id="font-size-s" name="font-size" value="18">
<label for=font-size-s>
<div class="${modeSelectionSpan}" data-type="fontsize">
<span>小</span>
</div>
</label>
<input type="radio" id="font-size-m" name="font-size" value="25" checked>
<label for=font-size-m>
<div class="${modeSelectionSpan}" data-type="fontsize">
<span>标准</span>
</div>
</label>
</div>
</div>
<div class="${modeSelectionRow}">
<div class="${modeSelectionRowTitle}">模式</div>
<div class="${modeSelectionRowSelection}">
<input type="radio" id="mode-s" name="mode" value="1" checked>
<label for="mode-s">
<div class="${modeSelectionSpan}" data-type="mode">
<span>滚动</span>
</div>
</label>
<input type="radio" id="mode-t" name="mode" value="5">
<label for="mode-t">
<div class="${modeSelectionSpan}" data-type="mode">
<span>顶部</span>
</div>
</label>
<input type="radio" id="mode-b" name="mode" value="4">
<label for="mode-b">
<div class="${modeSelectionSpan}" data-type="mode">
<span>底部</span>
</div>
</label>
</div>
</div>
<div class="${modeSelectionRow}">
<div class="${modeSelectionRowTitle}">颜色</div>
<div class="${modeSelectionRowSelection}">
<input class="${colorPickerInput}" value="#FFFFFF" >
<div class="${colorPickerWrap}">
${[
'#FE0302',
'#FF7204',
'#FFAA02',
'#FFD302',
'#FFFF00',
'#A0EE00',
'#00CD00',
'#019899',
'#4266BE',
'#89D5FF',
'#CC0273',
'#222222',
'#9B9B9B',
'#FFFFFF'
]
.map((color, i) => {
return `<span class="${colorPicker} ${
i == 13 ? activeColor : ''
}" style="background-color: ${color};" data-value="${color}"></span>`
})
.join('')}
</div>
</div>
</div>
</div>
</div>
<input class="${input}" placeholder="发个友善的弹幕见证当下"/>
</div>
<div class="${send}">
<div class="${sendBottom}">发送</div>
</div>`
)
const $colorPickerInput = $tpl.querySelector<HTMLInputElement>(`.${colorPickerInput}`)!
const $colorPickerWrap = $tpl.querySelector<HTMLInputElement>(`.${colorPickerWrap}`)!
$colorPickerInput.oninput = function (e: Event) {
;(<HTMLInputElement>e.target).style.backgroundColor = (<HTMLInputElement>e.target).value
}
$colorPickerWrap.onclick = function (e: Event) {
const target = e.target as HTMLSpanElement
if (target.tagName == 'SPAN') {
$colorPickerInput.style.backgroundColor = $colorPickerInput.value =
target.getAttribute('data-value')!
Array.from(target.parentElement!.children).forEach((it) => it.classList.remove(activeColor))
target.classList.add(activeColor)
}
}
const parent = player.context.ui.$controllerBottom
const $input = $tpl.querySelector<HTMLInputElement>(`.${input}`)!
parent.insertBefore($tpl, parent.children[1]!)
function submit() {
const fontSize = $tpl.querySelector<HTMLInputElement>('input[name="font-size"]:checked')!.value
const mode = $tpl.querySelector<HTMLInputElement>('input[name="mode"]:checked')!.value
const color = $tpl.querySelector<HTMLInputElement>(`.${colorPickerInput}`)!.value || '#FFFFFF'
if ($input.value) {
const comment: Comment = {
mode: getMode(+mode),
text: $input.value,
time: player.currentTime,
style: { color: color, fontSize: `${fontSize}px` }
}
if (options.onEmit?.(comment) || true) {
const primaryColor = window
.getComputedStyle(player.context.ui.$root)
.getPropertyValue('--primary-color')
//@ts-ignore
comment.style!.border = `2px solid ${primaryColor}`
//@ts-ignore
comment.style!.marginTop = '4px'
danmaku.emit(comment)
$input.value = ''
$input.blur()
}
}
}
$tpl.querySelector(`.${sendBottom}`)!.addEventListener('click', submit)
$input.addEventListener('keypress', (e) => {
if (e.key === 'Enter') submit()
})
}