236 lines
5.0 KiB
JavaScript
236 lines
5.0 KiB
JavaScript
|
import { LitElement, css, html } from 'lit'
|
||
|
|
||
|
import './RangeSlider.js'
|
||
|
import './SvgIcon.js'
|
||
|
import { formatSeconds } from '../api/utils.js'
|
||
|
|
||
|
class VideoPlayer extends LitElement {
|
||
|
static properties = {
|
||
|
media: { type: String },
|
||
|
autoplay: { type: Boolean },
|
||
|
videoEl: { state: true },
|
||
|
playing: { state: true },
|
||
|
controls: { state: true },
|
||
|
ctrlTimer: { state: true },
|
||
|
position: { state: true },
|
||
|
duration: { state: true },
|
||
|
remain: { state: true }
|
||
|
}
|
||
|
|
||
|
constructor() {
|
||
|
super()
|
||
|
this.media = ''
|
||
|
this.hostHeight = 0
|
||
|
this.hostWidth = 0
|
||
|
this.playing = false
|
||
|
this.controls = true
|
||
|
this.ctrlTimer = {}
|
||
|
this.position = 0
|
||
|
this.duration = 0
|
||
|
this.remain = true
|
||
|
this.autoplay = false
|
||
|
this.videoEl = document.createElement('video')
|
||
|
this.videoEl.playsInline = true
|
||
|
this.videoEl.preload = 'metadata'
|
||
|
}
|
||
|
|
||
|
firstUpdated() {
|
||
|
this.videoEl.addEventListener('loadeddata', () => {
|
||
|
this.duration = this.videoEl.duration
|
||
|
})
|
||
|
|
||
|
this.videoEl.addEventListener('timeupdate', () => {
|
||
|
this.position = this.videoEl.currentTime
|
||
|
})
|
||
|
}
|
||
|
|
||
|
willUpdate(att) {
|
||
|
if (att.has('media')) {
|
||
|
this.videoEl.src = this.media
|
||
|
|
||
|
if (this.autoplay) {
|
||
|
this.togglePlay()
|
||
|
this.interact()
|
||
|
}
|
||
|
}
|
||
|
this.update()
|
||
|
}
|
||
|
|
||
|
togglePlay() {
|
||
|
if (this.playing) {
|
||
|
this.videoEl.pause()
|
||
|
} else {
|
||
|
this.videoEl.play()
|
||
|
}
|
||
|
this.playing = !this.playing
|
||
|
}
|
||
|
|
||
|
interact() {
|
||
|
if (this.playing) {
|
||
|
// show controls
|
||
|
this.controls = true
|
||
|
clearTimeout(this.ctrlTimer)
|
||
|
|
||
|
// timeout
|
||
|
this.ctrlTimer = setTimeout(() => {
|
||
|
if (this.playing) {
|
||
|
this.controls = false
|
||
|
}
|
||
|
}, 2000)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
toggleFullscreen() {
|
||
|
const container = this.shadowRoot.querySelector('.videoContainer')
|
||
|
|
||
|
if (container.webkitSupportsFullscreen) {
|
||
|
container.webkitEnterFullscreen()
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if (document.fullscreenElement !== null) {
|
||
|
document.exitFullscreen()
|
||
|
container.setAttribute('data-fullscreen', false)
|
||
|
} else {
|
||
|
container.requestFullscreen()
|
||
|
container.setAttribute('data-fullscreen', true)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
toggleDuration() {
|
||
|
console.log(this.remain)
|
||
|
this.remain = !this.remain
|
||
|
}
|
||
|
|
||
|
seek({ detail }) {
|
||
|
if (detail) {
|
||
|
this.interact()
|
||
|
|
||
|
this.videoEl.currentTime = detail.value
|
||
|
this.position = detail.value
|
||
|
}
|
||
|
}
|
||
|
|
||
|
render() {
|
||
|
return html`
|
||
|
<div class="videoContainer">
|
||
|
${this.videoEl}
|
||
|
|
||
|
<div class="touchbox" @click=${this.interact}>
|
||
|
<div class="playPause ${this.controls ? '' : 'invisible'}" @click=${this.togglePlay}>
|
||
|
<mm-icon class="play ${this.playing ? '' : 'invisible'}" name="pause"></mm-icon>
|
||
|
<mm-icon class="pause ${this.playing ? 'invisible' : ''}" name="play"></mm-icon>
|
||
|
</div>
|
||
|
|
||
|
<div class="controls ${this.controls ? '' : 'invisible'}">
|
||
|
|
||
|
<mm-icon @click=${this.toggleFullscreen} class="fullscreen" name="fullscreen"></mm-icon>
|
||
|
|
||
|
<mm-range
|
||
|
value="${this.position}"
|
||
|
max="${this.duration}"
|
||
|
step="0.1"
|
||
|
@input=${this.seek}
|
||
|
></mm-range>
|
||
|
|
||
|
<div class="time">
|
||
|
<span class="pos">${formatSeconds(this.position)}</span>
|
||
|
<span class="dur" @click=${this.toggleDuration}>${ this.remain ? `-${formatSeconds(this.duration - this.position)}` : formatSeconds(this.duration)}</span>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
`
|
||
|
}
|
||
|
|
||
|
static styles = css`
|
||
|
:host {
|
||
|
height: 100%;
|
||
|
width: 100%;
|
||
|
position: relative;
|
||
|
}
|
||
|
|
||
|
video {
|
||
|
position: absolute;
|
||
|
display: block;
|
||
|
inset: 0;
|
||
|
width: 100%;
|
||
|
height: 100%;
|
||
|
background: black;
|
||
|
}
|
||
|
|
||
|
.controls {
|
||
|
position: absolute;
|
||
|
padding-inline: 0.75em;
|
||
|
padding-block: 0.5em;
|
||
|
left: 0;
|
||
|
right: 0;
|
||
|
bottom: 0;
|
||
|
z-index: 5;
|
||
|
transition: opacity 0.25s ease;
|
||
|
}
|
||
|
|
||
|
.time {
|
||
|
display: flex;
|
||
|
color: white;
|
||
|
font-size: 0.65em;
|
||
|
padding-inline: 0.125em;
|
||
|
padding-block: 1em 0.5em;
|
||
|
}
|
||
|
|
||
|
.dur {
|
||
|
margin-inline-start: auto;
|
||
|
}
|
||
|
|
||
|
mm-icon {
|
||
|
display: block;
|
||
|
color: white;
|
||
|
}
|
||
|
|
||
|
mm-icon.fullscreen {
|
||
|
font-size: 1.25em;
|
||
|
width: 1em;
|
||
|
margin-inline-start: auto;
|
||
|
margin-block-end: 1em;
|
||
|
}
|
||
|
|
||
|
mm-range {
|
||
|
--track-height: 0.3em;
|
||
|
--thumb-size: 0.8em;
|
||
|
--track-color: #ffffff50;
|
||
|
}
|
||
|
|
||
|
.touchbox {
|
||
|
display: flex;
|
||
|
align-items: center;
|
||
|
justify-content: center;
|
||
|
position: absolute;
|
||
|
inset: 0;
|
||
|
opacity: 1;
|
||
|
transition: opacity 0.25s ease;
|
||
|
}
|
||
|
|
||
|
.playPause {
|
||
|
display: grid;
|
||
|
align-items: center;
|
||
|
justify-content: center;
|
||
|
font-size: 2.25em;
|
||
|
transition: opacity 0.25 ease;
|
||
|
}
|
||
|
|
||
|
.playPause > mm-icon {
|
||
|
inset: 0;
|
||
|
grid-column: 1 / span 1;
|
||
|
grid-row: 1 / span 1;
|
||
|
}
|
||
|
|
||
|
.invisible {
|
||
|
pointer-events: none;
|
||
|
opacity: 0;
|
||
|
}
|
||
|
|
||
|
`
|
||
|
}
|
||
|
|
||
|
customElements.define('mm-vplayer', VideoPlayer)
|