diff --git a/demo.html b/demo.html deleted file mode 100644 index aa24501..0000000 --- a/demo.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/index.html b/index.html new file mode 100644 index 0000000..6853f69 --- /dev/null +++ b/index.html @@ -0,0 +1,12 @@ + + + + podcast-player demo + + + + + + + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..6320409 --- /dev/null +++ b/package.json @@ -0,0 +1,21 @@ +{ + "name": "podcast-player", + "version": "1.0.0", + "description": "![Screenshot of podcast-player](https://s3.amazonaws.com/f.cl.ly/items/3Q47193Z0f00142R0O42/1d0pzyOUeVH2-d1m77Vxx9QkL0vexZ5bJNbyEGRCfbY.png)", + "main": "podcast-player.js", + "scripts": { + "start": "npx http-server", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/davatron5000/podcast-player.git" + }, + "keywords": [], + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/davatron5000/podcast-player/issues" + }, + "homepage": "https://github.com/davatron5000/podcast-player#readme" +} diff --git a/podcast-player.html b/podcast-player.html deleted file mode 100644 index 3ff5e6e..0000000 --- a/podcast-player.html +++ /dev/null @@ -1,215 +0,0 @@ - - diff --git a/podcast-player.js b/podcast-player.js new file mode 100644 index 0000000..bff9035 --- /dev/null +++ b/podcast-player.js @@ -0,0 +1,342 @@ +import { + LitElement, + html, + css, +} from "https://unpkg.com/lit-element@2.2.1/lit-element.js?module"; + +class PodcastPlayer extends LitElement { + static get properties() { + return { + currentTime: { type: String }, + currentSpeedIdx: { type: Number }, + duration: { type: String }, + playing: { type: Boolean, reflect: true }, + muted: { type: Boolean, reflect: true }, + }; + } + + // TODO: Make styles optional? Easily overrideable? + static get styles() { + return css` + .sr-only { + clip: rect(0 0 0 0); + clip-path: inset(50%); + height: 1px; + overflow: hidden; + position: absolute; + white-space: nowrap; + width: 1px; + } + + .podcast-player { + display: flex; + gap: 0.5rem; + width: 100%; + align-items: center; + grid-gap: 0.5rem; + } + + .podcast-player .progress { + flex: 1; + } + + .podcast-player button svg { + width: 0.8em; + height: 0.8em; + } + + /* Speed */ + .podcast-player .button-speed:after { + content: "x"; + } + + /* Play/Pause */ + .button-play .pause { + display: none; + } + + :host([playing]) .button-play .pause { + display: inline; + } + :host([playing]) .button-play .play { + display: none; + } + + /* Mute/Unmute */ + .button-mute .muted { + display: none; + } + + :host([muted]) .button-mute .muted { + display: inline; + } + + :host([muted]) .button-mute .unmuted { + display: none; + } + `; + } + + constructor() { + super(); + + // HTMLAudioElement + this.audio = this.querySelector("audio"); + this.audio.controls = false; // remove controls if it has 'em + + this.speeds = [1, 1.25, 1.5, 1.75, 2]; + this.currentSpeedIdx = 0; + this.currentTime = 0; + this.duration = 0; + this.playing = false; + this.muted = false; + + this.audio.addEventListener("timeupdate", this.handleTimeUpdate.bind(this)); + this.audio.addEventListener( + "loadedmetadata", + this.handleLoadedMetadata.bind(this) + ); + this.audio.addEventListener("ended", this.stop.bind(this)); + + window.addEventListener( + "DOMContentLoaded", + this.timeJump.bind(this), + false + ); + window.addEventListener("hashchange", this.timeJump.bind(this), false); + } + + handleLoadedMetadata() { + this.duration = this.audio.duration; + } + + handleTimeUpdate(e) { + this.currentTime = this.audio.currentTime; + } + + timeJump(event) { + let params = new URLSearchParams(window.location.hash.substring(1)); + let t = params.get("t") || 0; + + console.log(window, params, t, event); + + var timestamp = this.parseTime(t); + + if (t) { + // Preload the media + this.audio.setAttribute("preload", "true"); + // Set the current time. Will update if playing. Will fail if paused. + this.audio.currentTime = timestamp; + // If the media is able to play, play. + this.audio.addEventListener( + "canplay", + () => { + /* only start the player if it is not already playing */ + if (!this.audio.paused) { + return false; + } + + this.audio.currentTime = timestamp; + this.audio.play(); + this.playing = true; + }, + false + ); + } + } + + parseTime(str) { + var plain = /^\d+(\.\d+)?$/g, + npt = /^(?:npt:)?(?:(?:(\d+):)?(\d\d?):)?(\d\d?)(\.\d+)?$/, + quirks = /^(?:(\d\d?)[hH])?(?:(\d\d?)[mM])?(\d\d?)[sS]$/, + match; + if (plain.test(str)) { + return parseFloat(str); + } + match = npt.exec(str) || quirks.exec(str); + if (match) { + return ( + 3600 * (parseInt(match[1], 10) || 0) + + 60 * (parseInt(match[2], 10) || 0) + + parseInt(match[3], 10) + + (parseFloat(match[4]) || 0) + ); + } + return 0; + } + + toHHMMSS(totalsecs) { + var sec_num = parseInt(totalsecs, 10); // don't forget the second param + var hours = Math.floor(sec_num / 3600); + var minutes = Math.floor((sec_num - hours * 3600) / 60); + var seconds = sec_num - hours * 3600 - minutes * 60; + + if (hours < 10) { + hours = "0" + hours; + } + if (minutes < 10) { + minutes = "0" + minutes; + } + if (seconds < 10) { + seconds = "0" + seconds; + } + + hours = hours > 0 ? hours + ":" : ""; + minutes = minutes + ":"; + + var time = hours + minutes + seconds; + return time; + } + + changeSpeed() { + this.currentSpeedIdx = + this.currentSpeedIdx + 1 < this.speeds.length + ? this.currentSpeedIdx + 1 + : 0; + this.audio.playbackRate = this.speeds[this.currentSpeedIdx]; + } + + mute() { + this.audio.muted = !this.audio.muted; + this.muted = this.audio.muted; + } + + play() { + if (this.audio.paused) { + console.log(this.currentTime, this.audio.currentTime); + this.audio.play(); + } else { + this.audio.pause(); + } + this.playing = !this.audio.paused; + } + + stop() { + this.playing = !this.audio.paused; + } + + rewind() { + this.audio.currentTime -= 30; + } + + ff() { + this.audio.currentTime += 30; + } + + seek(e) { + this.audio.currentTime = e.target.value; + } + + render() { + // TODO: Make icons, button text, button labels overrideable? + return html` + + + + + + + + + + + + + + + + + + + +
+ + + + + + +
+ Current Time + ${this.toHHMMSS(this.currentTime)} +
+ + + +
+ Duration + ${this.toHHMMSS(this.duration)} +
+ + + + +
+ `; + } +} + +customElements.define("podcast-player", PodcastPlayer);