cleaned up code and added volume controller

This commit is contained in:
suroh 2023-10-09 10:17:25 +02:00
parent 476139bc3f
commit 99976ecc11
11 changed files with 187 additions and 123 deletions

View File

@ -1,6 +1,11 @@
import { LitElement } from 'lit'
import { LitElement, css, html, unsafeCSS } from 'lit'
import Router from './api/Router.js'
import MainCSS from './assets/styles/main.scss?inline'
import './components/Header.js'
import './components/Footer.js'
// global reset
import './assets/styles/main.scss'
@ -26,9 +31,20 @@ export class App extends LitElement {
}
render() {
return Router.render()
return html`
${Router.route.path == '/' ? '' : html`<mm-header title=${Router.route.title}></mm-header>`}
${Router.render()}
${Router.route.path == '/' ? '' : html`<mm-footer path=${Router.route.path}></mm-footer>`}
`
}
static styles = [ css`${unsafeCSS(MainCSS)}`, css`
:host {
display: flex;
flex-direction: column;
height: 100vh;
}
` ]
}
customElements.define('mm-app', App)

View File

@ -5,10 +5,16 @@ import MainCSS from '../assets/styles/main.scss?inline'
import './SvgIcon.js'
class Footer extends LitElement {
static properties = {}
static properties = {
path: { type: String }
}
constructor() {
super()
this.path = ''
}
firstUpdated() {
}
render() {
@ -16,7 +22,7 @@ class Footer extends LitElement {
<footer>
<nav>
${Router.routes.map(r => !r.hide ? html`
<a href=${r.path} class=${r.path == Router.route.path ? 'selected' : ''}>
<a href=${r.path} class=${r.path == this.path ? 'selected' : ''}>
<mm-icon name=${r.icon}></mm-icon>
${r.short}
</a>` : '') }

View File

@ -6,7 +6,9 @@ import './Button.js'
import MainCSS from '../assets/styles/main.scss?inline'
class Header extends LitElement {
static properties = {}
static properties = {
title: { type: String }
}
constructor() {
super()
@ -23,7 +25,7 @@ class Header extends LitElement {
<mm-button @click=${this.navHome}>back</mm-button>
</div>
<h1>${Router.route.title || ''}</h1>
<h1>${this.title || ''}</h1>
</header>
`

View File

@ -2,12 +2,16 @@ import { LitElement, html, css,unsafeCSS } from 'lit'
import { Task } from '@lit-labs/task'
import { formatSeconds } from '../api/utils'
import VolumeController from '../controllers/volume.js'
import MainCSS from '../assets/styles/main.scss?inline'
import './RangeSlider.js'
import './Loading.js'
class ModularPlayer extends LitElement {
volCtrl = new VolumeController(this)
static properties = {
// property
details: { type: Object },
@ -16,7 +20,6 @@ class ModularPlayer extends LitElement {
duration: { state: true },
position: { state: true },
playing: { state: true },
volume: { state: true },
track: { state: true },
loading: { state: true },
@ -35,7 +38,7 @@ class ModularPlayer extends LitElement {
this.duration = 0
this.loading = true
this.initAudio()
this._initAudio()
}
_getTrack = new Task(
@ -60,17 +63,12 @@ class ModularPlayer extends LitElement {
() => [ this.track, this.details ]
)
initAudio() {
_initAudio() {
this.audio = document.createElement('audio')
if (!localStorage.getItem('deviceVolume')) {
localStorage.setItem('deviceVolume', 0.6)
}
this.audio.addEventListener('loadstart', () => {
this.loading = true
this.audio.volume = localStorage.getItem('deviceVolume')
this.volume = this.audio.volume
this.audio.volume = this.volCtrl.volume
})
this.audio.addEventListener('canplay', () => {
@ -116,7 +114,7 @@ class ModularPlayer extends LitElement {
})
}
togglePlay() {
_togglePlay() {
if (this.audio.paused) {
this.playing = true
this.audio.play()
@ -126,7 +124,7 @@ class ModularPlayer extends LitElement {
}
}
seekTrack({ detail }) {
_seekTrack({ detail }) {
if (detail) {
const { value } = detail
this.audio.currentTime = value
@ -134,20 +132,20 @@ class ModularPlayer extends LitElement {
}
}
skipForward() {
_skipForward() {
this.audio.currentTime = this.audio.currentTime + 15
}
skipBackward() {
_skipBackward() {
this.audio.currentTime = this.audio.currentTime - 15
}
selectTrack(evt) {
_selectTrack(evt) {
const trackNumber = evt.target.dataset.trackNumber
this.track = parseInt(trackNumber)
}
nextTrack() {
_nextTrack() {
if (this.details.tracks) {
if (this.track + 1 < this.details.tracks.length) {
this.track += 1
@ -157,7 +155,7 @@ class ModularPlayer extends LitElement {
}
}
prevTrack() {
_prevTrack() {
if (this.details.tracks) {
if (this.track - 1 >= 0) {
this.track -= 1
@ -167,14 +165,13 @@ class ModularPlayer extends LitElement {
}
}
volHandler(evt) {
_volHandler(evt) {
if (evt.detail) {
this.audio.volume = evt.detail.value
localStorage.setItem('deviceVolume', evt.detail.value)
this.volCtrl.update(evt.detail.value)
}
}
progressCalc() {
_progressCalc() {
let percentage = 0
if (this.position && this.duration) {
@ -195,7 +192,7 @@ ${ this.details ?
<div class="list">
<ul>
${this.details.tracks.map((t, i) => html`
<li class="${this.track === i ? 'selected' : ''}" data-track-number=${i} @click=${this.selectTrack}>
<li class="${this.track === i ? 'selected' : ''}" data-track-number=${i} @click=${this._selectTrack}>
<mm-icon name="notes" class=${this.playing ? 'playing' : ''}></mm-icon>
<span>${t}</span>
</li>`)}
@ -219,7 +216,7 @@ ${ this.details ?
value=${this.position}
max=${this.duration}
step="0.1"
@input=${this.seekTrack}
@input=${this._seekTrack}
></mm-range>`
: html`
<div class="waveform">
@ -227,7 +224,7 @@ ${ this.details ?
value=${this.position}
max=${this.duration}
step="0.1"
@input=${this.seekTrack}
@input=${this._seekTrack}
class="progress"
></mm-range>
<img class="wav-img" loading="eager" src="/media${this.details.media}.png">
@ -245,18 +242,18 @@ ${ this.details ?
<div class="controls">
<div class="buttons">
<mm-icon name="skip-prev" @click=${this.prevTrack} ?disabled=${!this.duration || this.getAudio?.status == 1}></mm-icon>
<mm-icon name="skip-b15" @click=${this.skipBackward} ?disabled=${!this.duration || this.getAudio?.status == 1} class="skip15"></mm-icon>
<mm-icon name="${this.playing ? 'pause' : 'play'}" @click=${this.togglePlay} ?disabled=${!this.duration || this.getAudio?.status == 1} class=${this.playing ? 'pause' : 'play'}></mm-icon>
<mm-icon name="skip-f15" @click=${this.skipForward} ?disabled=${!this.duration || this.getAudio?.status == 1} class="skip15"></mm-icon>
<mm-icon name="skip-next" @click=${this.nextTrack} ?disabled=${!this.duration || this.getAudio?.status == 1}></mm-icon>
<mm-icon name="skip-prev" @click=${this._prevTrack} ?disabled=${!this.duration || this.getAudio?.status == 1}></mm-icon>
<mm-icon name="skip-b15" @click=${this._skipBackward} ?disabled=${!this.duration || this.getAudio?.status == 1} class="skip15"></mm-icon>
<mm-icon name="${this.playing ? 'pause' : 'play'}" @click=${this._togglePlay} ?disabled=${!this.duration || this.getAudio?.status == 1} class=${this.playing ? 'pause' : 'play'}></mm-icon>
<mm-icon name="skip-f15" @click=${this._skipForward} ?disabled=${!this.duration || this.getAudio?.status == 1} class="skip15"></mm-icon>
<mm-icon name="skip-next" @click=${this._nextTrack} ?disabled=${!this.duration || this.getAudio?.status == 1}></mm-icon>
</div>
<div class="volume">
<mm-icon name="volume"></mm-icon>
<mm-range
value=${this.volume}
@input=${this.volHandler}
value=${this.volCtrl.volume}
@input=${this._volHandler}
?disabled=${!this.duration || this.getAudio?.status == 1}
></mm-range>
</div>

View File

@ -1,10 +1,14 @@
import { LitElement, css, html } from 'lit'
import VolumeController from '../controllers/volume.js'
import './RangeSlider.js'
import './SvgIcon.js'
import { formatSeconds } from '../api/utils.js'
class VideoPlayer extends LitElement {
volCtrl = new VolumeController(this)
static properties = {
media: { type: String },
autoplay: { type: Boolean },
@ -14,7 +18,8 @@ class VideoPlayer extends LitElement {
ctrlTimer: { state: true },
position: { state: true },
duration: { state: true },
remain: { state: true }
remain: { state: true },
volumeOpen: { state: true },
}
constructor() {
@ -28,6 +33,7 @@ class VideoPlayer extends LitElement {
this.position = 0
this.duration = 0
this.remain = true
this.volumeOpen = false
this.autoplay = false
this.videoEl = document.createElement('video')
this.videoEl.playsInline = true
@ -42,6 +48,8 @@ class VideoPlayer extends LitElement {
this.videoEl.addEventListener('timeupdate', () => {
this.position = this.videoEl.currentTime
})
this.videoEl.volume = this.volCtrl.volume
}
willUpdate(att) {
@ -76,6 +84,7 @@ class VideoPlayer extends LitElement {
// timeout
this.ctrlTimer = setTimeout(() => {
if (this.playing) {
this.volumeOpen = false
this.controls = false
}
}, 2000)
@ -113,6 +122,20 @@ class VideoPlayer extends LitElement {
}
}
_toggleVolume() {
this.interact()
this.volumeOpen = !this.volumeOpen
}
_volHandler(evt) {
this.interact()
if (evt.detail) {
this.volCtrl.update(evt.detail.value)
}
}
render() {
return html`
<div class="videoContainer">
@ -126,7 +149,19 @@ class VideoPlayer extends LitElement {
<div class="controls ${this.controls ? '' : 'invisible'}">
<mm-icon @click=${this.toggleFullscreen} class="fullscreen" name="fullscreen"></mm-icon>
<div class="buttons">
<div class="volume-container">
<mm-icon name="volume" @click=${this._toggleVolume}></mm-icon>
<div class="popout ${this.volumeOpen ? 'open' : ''}">
<mm-range
value=${this.volCtrl.volume}
step="0.01"
@input=${this._volHandler}
></mm-range>
</div>
</div>
<mm-icon @click=${this.toggleFullscreen} class="fullscreen" name="fullscreen"></mm-icon>
</div>
<mm-range
value="${this.position}"
@ -170,6 +205,45 @@ class VideoPlayer extends LitElement {
bottom: 0;
z-index: 5;
transition: opacity 0.25s ease;
.buttons {
display: flex;
gap: 0.75em;
padding-block-start: 0.5em;
}
& mm-icon {
font-size: 1.25em;
width: 1em;
}
}
.volume-container {
position: relative;
margin-inline-start: auto;
margin-block-end: 0.75em;
& .popout {
display: flex;
align-items: center;
position: absolute;
width: 6em;
height: 100%;
left: 0;
top: 0;
transform: translateX(calc(calc(100% + 0.5em) * -1));
overflow: hidden;
& mm-range {
transform: translateX(calc(100% + 1em));
transition: transform 0.125s ease-in-out;
}
&.open mm-range {
transform: translateX(0);
}
}
}
.time {
@ -189,13 +263,6 @@ class VideoPlayer extends LitElement {
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;

22
src/controllers/volume.js Normal file
View File

@ -0,0 +1,22 @@
export default class VolumeController {
volume = 0
constructor(host) {
this.host = host
this.host.addController(this)
}
hostConnected() {
if (!localStorage.getItem('deviceVolume')) {
localStorage.setItem('deviceVolume', 0.6)
}
this.volume = localStorage.getItem('deviceVolume')
}
update(val) {
console.log(val)
this.volume = val
localStorage.setItem('deviceVolume', val)
}
}

View File

@ -4,8 +4,6 @@ import { Task } from '@lit-labs/task'
import Photo from '/images/Meredith Monk (1974) Photo Lauretta Harris.jpg'
// components
import '../components/Header.js'
import '../components/Footer.js'
import '../components/Loading.js'
import '../components/AudioCard.js'
import '../components/ModularPlayer.js'
@ -68,50 +66,38 @@ class AudioView extends LitElement {
render() {
return html`
<mm-header></mm-header>
<main>
<nav class="tracks">
<div class="scroll-items">
${this._getAudio.render({
pending: () => html`<mm-loading style="--fill-color: grey"></mm-loading>`,
complete: () => html`${this.tracks.map((t, i) => html`
<mm-acard
idx=${i}
icon=${t.album ? 'album' : 'play-circle'}
?selected=${t.title == this.selected.title && t.details == this.selected.details}
.details=${t}
></mm-acard>`)}`,
error: (err) => html`Error: ${err}`
})}
</div>
</nav>
<div class="player">
<div>
${this.selected.tracks ? '' : html`<img class="bg-img" src=${Photo} />` }
<mm-audio-player .details=${this.selected}></mm-audio-player>
</div>
<nav class="tracks">
<div class="scroll-items">
${this._getAudio.render({
pending: () => html`<mm-loading style="--fill-color: grey"></mm-loading>`,
complete: () => html`${this.tracks.map((t, i) => html`
<mm-acard
idx=${i}
icon=${t.album ? 'album' : 'play-circle'}
?selected=${t.title == this.selected.title && t.details == this.selected.details}
.details=${t}
></mm-acard>`)}`,
error: (err) => html`Error: ${err}`
})}
</div>
</main>
</nav>
<mm-footer></mm-footer>
<div class="player">
<div>
${this.selected?.tracks ? '' : html`<img class="bg-img" src=${Photo} />` }
<mm-audio-player .details=${this.selected}></mm-audio-player>
</div>
</div>
`
}
static styles = css`
:host {
display: flex;
flex-direction: column;
height: 100vh;
}
main {
flex-grow: 1;
display: grid;
grid-template-columns: 0.7fr 1fr;
gap: 0.5em;
height: 100%;
}
.tracks {
@ -161,7 +147,6 @@ class AudioView extends LitElement {
}
}
mm-audio-player {
transition: opacity 0.125s 0.125s ease-in-out;
}

View File

@ -1,8 +1,5 @@
import { LitElement, html, css } from 'lit'
import '../components/Header.js'
import '../components/Footer.js'
class FourOhFour extends LitElement {
static properties = {}
@ -12,34 +9,22 @@ class FourOhFour extends LitElement {
render() {
return html`
<mm-header></mm-header>
<main>
<h1>404</h1>
<p>The page you're looking for cannot be found</p>
</main>
<mm-footer></mm-footer>
`
}
static styles = css`
:host {
display: grid;
grid-template-rows: auto 1fr auto;
gap: 0.5em;
height: 100vh;
}
main {
display: flex;
flex-grow: 1;
flex-direction: column;
padding: 0em 0em 10em;
text-align: center;
justify-content: center;
}
main > * {
:host > * {
margin: 0.25em;
}
`

View File

@ -36,15 +36,14 @@ class Home extends LitElement {
static styles = [ css`${unsafeCSS(MainCSS)}`, css`
:host {
--margin: 0.75em;
display: block;
display: flex;
flex-direction: column;
height: calc(100vh - calc(var(--margin) * 2));
background-image: url('/images/home-bg.png');
background-size: cover;
background-position: center;
margin: var(--margin);
border: 0.75em white solid;
height: 100%;
}
div {

View File

@ -4,8 +4,6 @@ import Router from '../api/Router.js'
import MainCSS from '../assets/styles/main.scss?inline'
import '../components/Header.js'
import '../components/Footer.js'
import '../components/HorizontalScroller.js'
import '../components/VerticalCard.js'
import '../components/ImageViewer.js'
@ -47,8 +45,6 @@ class ImageView extends LitElement {
render() {
return html`
<mm-header></mm-header>
<main>
<div class="image">
<mm-imageviewer .details=${this.selected}></mm-imageviewer>
</div>
@ -58,17 +54,15 @@ class ImageView extends LitElement {
<mm-vcard @click=${this.selectImage} .details=${i} ?selected=${i.title == this.selected?.title}></mm-vcard>
`) })}
</mm-hscroller>
</main>
<mm-footer></mm-footer>
`
}
static styles = [ css`${unsafeCSS(MainCSS)}`, css`
:host {
flex-grow: 1;
display: grid;
grid-template-rows: min-content 1fr min-content;
gap: 0.25em;
height: 100vh;
grid-template-rows: 1fr auto;
gap: 0.5em;
}
mm-hscroller {
@ -80,12 +74,6 @@ class ImageView extends LitElement {
--color: var(--green-400, lime);
}
main {
display: grid;
grid-template-rows: 1fr auto;
gap: 0.5em;
}
.image {
position: relative;
inline-margin: auto;

View File

@ -4,8 +4,6 @@ import { Task } from '@lit-labs/task'
import MainCSS from '../assets/styles/main.scss?inline'
// components
import '../components/Header.js'
import '../components/Footer.js'
import '../components/VerticalCard.js'
import '../components/VideoPlayer.js'
import '../components/HorizontalScroller.js'
@ -43,8 +41,6 @@ class VideoView extends LitElement {
render() {
return html`
<mm-header></mm-header>
<main>
<aside>
<h2>${this.selected.title}</h2>
@ -64,14 +60,14 @@ class VideoView extends LitElement {
)}`
})}
</mm-hscroller>
<mm-footer></mm-footer>`
`
}
static styles = [ css`${unsafeCSS(MainCSS)}`, css`
:host {
flex-grow: 1;
display: grid;
grid-template-rows: min-content 1fr min-content min-content;
grid-template-rows: 1fr min-content;
gap: 0.25em;
height: 100vh;
}
@ -83,6 +79,7 @@ class VideoView extends LitElement {
main {
display: grid;
height: 100%;
grid-template-columns: 0.4fr 1fr;
margin-inline: 0.5em;
margin-block-end: 0.5em;