Initial commit

This commit is contained in:
Erik Thuning 2020-11-10 15:50:27 +01:00
commit 547d9a777c
5 changed files with 619 additions and 0 deletions

2
.gitignore vendored Normal file

@ -0,0 +1,2 @@
*~
*.mp4

BIN
favicon.ico Normal file

Binary file not shown.

After

Width: 16px  |  Height: 16px  |  Size: 318 B

167
index.html Normal file

@ -0,0 +1,167 @@
<!doctype html>
<html lang="se">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="shortcut icon" href="favicon.ico">
<link rel="stylesheet" href="./style.css?a">
<title>DSVPlayer</title>
<script src="player.js" defer></script>
</head>
<body>
<div id="wrapper">
<div class="main">
<video></video>
<svg>
<use href="#play-icon"></use>
</svg>
</div>
<div class="about">
<button id="playlist-show">
<svg>
<use href="#playlist-icon"></use>
</svg>
</button>
<h1>TITLE</h1>
</div>
<div id="controls">
<div id="progress-container">
<div id="buffer"></div>
<div id="progress"></div>
</div>
<div class="left-controls">
<button title="Spela" id="play-button">
<svg>
<use href="#play-icon"></use>
</svg>
</button>
<div id="speed-select">
<button id="speed-current">1</button>
<ul id="speed-list">
<li>
<button>1</button>
</li>
<li>
<button>1.5</button>
</li>
<li>
<button>2</button>
</li>
<li>
<button>4</button>
</li>
</ul>
</div>
<div id="volume-controls">
<button id="volume-button" title="Stäng av ljud">
<svg>
<use href="#volume-icon"></use>
<use href="#mute-icon" class="hidden"></use>
</svg>
</button>
<input type="range" id="volume"
min="0" max="1" step="0.01" value="1">
</div>
<div class="time">
<time id="time-elapsed">00:00</time>
<span> / </span>
<time id="duration">00:00</time>
</div>
</div>
<div class="right-controls">
<button title="Undertexter är av" id="subtitles-button">
<svg>
<use href="#subtitles-off-icon"></use>
<use href="#subtitles-on-icon" class="hidden"></use>
</svg>
</button>
<button title="Helskärm" id="fullscreen-button">
<svg>
<use href="#fullscreen-enter-icon"></use>
<use href="#fullscreen-exit-icon" class="hidden"></use>
</svg>
</button>
</div>
</div>
<div id="playlist" class="hidden">
<div class="about">
<button id="playlist-hide">
<svg>
<use href="#close-icon"></use>
</svg>
</button>
<h1>TITLE</h1>
</div>
<ul>
<li>
ITEM 1
</li>
<li>
ITEM 2
</li>
<li>
ITEM 3
</li>
</ul>
</div>
</div>
<template id="stream-template">
<div class="secondary">
<video></video>
<svg>
<use href="#switch-icon"></use>
</svg>
</div>
</template>
<svg class="hidden">
<defs>
<symbol id="pause-icon" viewBox="0 0 24 24">
<path d="M14.016 5.016h3.984v13.969h-3.984v-13.969zM6 18.984v-13.969h3.984v13.969h-3.984z"></path>
</symbol>
<symbol id="play-icon" viewBox="0 0 24 24">
<path d="M8.016 5.016l10.969 6.984-10.969 6.984v-13.969z"></path>
</symbol>
<symbol id="playlist-icon" viewBox="0 0 24 24">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M3 13h2v-2H3v2zm0 4h2v-2H3v2zm0-8h2V7H3v2zm4 4h14v-2H7v2zm0 4h14v-2H7v2zM7 7v2h14V7H7z"/>
</symbol>
<symbol id="close-icon" viewBox="0 0 24 24">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
</symbol>
<symbol id="switch-icon" viewBox="0 0 24 24">
<path d="M12 6v1.79c0 .45.54.67.85.35l2.79-2.79c.2-.2.2-.51 0-.71l-2.79-2.79c-.31-.31-.85-.09-.85.36V4c-4.42 0-8 3.58-8 8 0 1.04.2 2.04.57 2.95.27.67 1.13.85 1.64.34.27-.27.38-.68.23-1.04C6.15 13.56 6 12.79 6 12c0-3.31 2.69-6 6-6zm5.79 2.71c-.27.27-.38.69-.23 1.04.28.7.44 1.46.44 2.25 0 3.31-2.69 6-6 6v-1.79c0-.45-.54-.67-.85-.35l-2.79 2.79c-.2.2-.2.51 0 .71l2.79 2.79c.31.31.85.09.85-.35V20c4.42 0 8-3.58 8-8 0-1.04-.2-2.04-.57-2.95-.27-.67-1.13-.85-1.64-.34z"/>
</symbol>
<symbol id="volume-icon" viewBox="0 0 24 24">
<path d="M14.016 3.234q3.047 0.656 5.016 3.117t1.969 5.648-1.969 5.648-5.016 3.117v-2.063q2.203-0.656 3.586-2.484t1.383-4.219-1.383-4.219-3.586-2.484v-2.063zM16.5 12q0 2.813-2.484 4.031v-8.063q1.031 0.516 1.758 1.688t0.727 2.344zM3 9h3.984l5.016-5.016v16.031l-5.016-5.016h-3.984v-6z"></path>
</symbol>
<symbol id="mute-icon" viewBox="0 0 24 24">
<path d="M12 3.984v4.219l-2.109-2.109zM4.266 3l16.734 16.734-1.266 1.266-2.063-2.063q-1.547 1.313-3.656 1.828v-2.063q1.172-0.328 2.25-1.172l-4.266-4.266v6.75l-5.016-5.016h-3.984v-6h4.734l-4.734-4.734zM18.984 12q0-2.391-1.383-4.219t-3.586-2.484v-2.063q3.047 0.656 5.016 3.117t1.969 5.648q0 2.203-1.031 4.172l-1.5-1.547q0.516-1.266 0.516-2.625zM16.5 12q0 0.422-0.047 0.609l-2.438-2.438v-2.203q1.031 0.516 1.758 1.688t0.727 2.344z"></path>
</symbol>
<symbol id="subtitles-off-icon" viewBox="0 0 24 24">
<path d="M20,4H6.83l8,8H19c0.55,0,1,0.45,1,1c0,0.55-0.45,1-1,1h-2.17l4.93,4.93C21.91,18.65,22,18.34,22,18V6C22,4.9,21.1,4,20,4 z"/><path d="M20,20l-6-6l-1.71-1.71L12,12L3.16,3.16c-0.39-0.39-1.02-0.39-1.41,0c-0.39,0.39-0.39,1.02,0,1.41l0.49,0.49 C2.09,5.35,2,5.66,2,6v12c0,1.1,0.9,2,2,2h13.17l2.25,2.25c0.39,0.39,1.02,0.39,1.41,0c0.39-0.39,0.39-1.02,0-1.41L20,20z M8,13 c0,0.55-0.45,1-1,1H5c-0.55,0-1-0.45-1-1c0-0.55,0.45-1,1-1h2C7.55,12,8,12.45,8,13z M14,17c0,0.55-0.45,1-1,1H5 c-0.55,0-1-0.45-1-1c0-0.55,0.45-1,1-1h8c0.08,0,0.14,0.03,0.21,0.04l0.74,0.74C13.97,16.86,14,16.92,14,17z"/>
</symbol>
<symbol id="subtitles-on-icon" viewBox="0 0 24 24">
<path d="M20 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zM5 12h2c.55 0 1 .45 1 1s-.45 1-1 1H5c-.55 0-1-.45-1-1s.45-1 1-1zm8 6H5c-.55 0-1-.45-1-1s.45-1 1-1h8c.55 0 1 .45 1 1s-.45 1-1 1zm6 0h-2c-.55 0-1-.45-1-1s.45-1 1-1h2c.55 0 1 .45 1 1s-.45 1-1 1zm0-4h-8c-.55 0-1-.45-1-1s.45-1 1-1h8c.55 0 1 .45 1 1s-.45 1-1 1z"/>
</symbol>
<symbol id="fullscreen-enter-icon" viewBox="0 0 24 24">
<path d="M14.016 5.016h4.969v4.969h-1.969v-3h-3v-1.969zM17.016 17.016v-3h1.969v4.969h-4.969v-1.969h3zM5.016 9.984v-4.969h4.969v1.969h-3v3h-1.969zM6.984 14.016v3h3v1.969h-4.969v-4.969h1.969z"></path>
</symbol>
<symbol id="fullscreen-exit-icon" viewBox="0 0 24 24">
<path d="M15.984 8.016h3v1.969h-4.969v-4.969h1.969v3zM14.016 18.984v-4.969h4.969v1.969h-3v3h-1.969zM8.016 8.016v-3h1.969v4.969h-4.969v-1.969h3zM5.016 15.984v-1.969h4.969v4.969h-1.969v-3h-3z"></path>
</symbol>
</defs>
</svg>
</body>
</html>

235
player.js Normal file

@ -0,0 +1,235 @@
document.addEventListener('DOMContentLoaded', init)
function init() {
loadStreams(getStreams())
setupHiding()
setupPlayback()
setupSwitching()
setupProgress()
setupSpeed()
setupVolume()
setupSubs()
setupFullscreen()
setupPlaylist()
}
function getStreams() {
return window.location.search.substring(1).split('&')
}
function loadStreams(streamlist) {
var mainstream = document.querySelector('.main > video')
var mainparent = mainstream.parentNode
var template = document.getElementById('stream-template')
mainstream.src = streamlist[0]
mainstream.load()
for (var i = 1; i < streamlist.length; i++) {
var newstream = template.content.cloneNode(true)
var video = newstream.querySelector('video')
video.src = streamlist[i]
video.load()
video.muted = true
mainparent.parentNode.insertBefore(newstream, mainparent.nextSibling)
}
}
function setupHiding() {
const selector = 'nocursor'
var timer = null
var body = document.querySelector('body')
var controls = document.querySelector('#controls')
var about = document.querySelector('#wrapper > .about')
function hide() {
if(!body.classList.contains(selector)) {
body.classList.add(selector)
}
}
function mouseMove(event) {
if(timer) {
window.clearTimeout(timer)
}
if(body.classList.contains(selector)) {
body.classList.remove(selector)
}
if(!controls.contains(event.target) && !about.contains(event.target)) {
timer = window.setTimeout(hide, 1500)
}
}
body.addEventListener('mousemove', mouseMove)
}
function setupPlayback() {
var playing = false
var videos = document.querySelectorAll('video')
function togglePlayback(event) {
videos.forEach(function(video) {
if(!playing) {
video.play()
} else {
video.pause()
}
})
playing = !playing;
}
document.querySelectorAll('.main, #play-button')
.forEach(function(button) {
button.addEventListener('click', togglePlayback)
})
}
function setupSwitching() {
var main = document.querySelector('.main')
function switchStreams(event) {
var target = event.currentTarget
var stream = target.querySelector('video')
var other = main.querySelector('video')
main.replaceChild(stream, other)
target.insertBefore(other, target.firstElementChild)
}
document.querySelectorAll('.secondary')
.forEach(function(div) {
div.addEventListener('click', switchStreams)
})
}
function setupProgress() {
var body = document.querySelector('body')
var backdrop = document.querySelector('#progress-container')
var pb = document.querySelector('#progress')
var dragging = false
function startDrag(event) {
dragging = true
}
function stopDrag(event) {
dragging = false
}
function update(event) {
if(dragging) {
pb.style.width = event.clientX + 'px'
}
}
function set(event) {
pb.style.width = event.clientX + 'px'
}
backdrop.addEventListener('mousedown', startDrag)
backdrop.addEventListener('click', set)
body.addEventListener('mousemove', update)
body.addEventListener('mouseup', stopDrag)
}
function setupSpeed() {
var videos = document.querySelectorAll('video')
var current = document.querySelector('#speed-current')
function setSpeed(event) {
var speed = event.currentTarget.textContent
if(event.currentTarget.id == 'speed-current') {
speed = 1
}
current.textContent = speed
videos.forEach(function(video) {
video.playbackRate = speed
})
}
var buttons = document.querySelectorAll('#speed-select button')
buttons.forEach(function(button) {
button.addEventListener('click', setSpeed)
})
}
function setupVolume() {
var soundStream = document.querySelector('.main > video')
var icons = document.querySelectorAll('#volume-button > svg > use')
var volume = document.querySelector('#volume')
var muted = false
var mutedVol = 0
// There may be a cached setting to apply
soundStream.volume = volume.value
function toggleVolume(event) {
if(!muted) {
mutedVol = volume.value
volume.value = 0
soundStream.volume = 0
muted = true
} else {
if(mutedVol < 0.1) {
mutedVol = 0.1
}
volume.value = mutedVol
soundStream.volume = mutedVol
muted = false;
}
icons.forEach(function(icon) {
icon.classList.toggle('hidden')
})
}
function slideVolume(event) {
soundStream.volume = event.currentTarget.value
}
var button = document.querySelector('#volume-button')
button.addEventListener('click', toggleVolume)
var slider = document.querySelector('#volume-controls > input[type="range"]')
slider.addEventListener('input', slideVolume)
}
function setupSubs() {
var icons = document.querySelectorAll('#subtitles-button > svg > use')
function toggleSubs(event) {
icons.forEach(function(icon) {
icon.classList.toggle('hidden')
})
}
var button = document.querySelector('#subtitles-button')
button.addEventListener('click', toggleSubs)
}
function setupFullscreen() {
var body = document.querySelector('body')
var icons = document.querySelectorAll('#fullscreen-button > svg > use')
function toggleFullscreen(event) {
if(document.fullscreenElement) {
document.exitFullscreen()
} else {
body.requestFullscreen()
}
icons.forEach(function(icon) {
icon.classList.toggle('hidden')
})
}
var button = document.querySelector('#fullscreen-button')
button.addEventListener('click', toggleFullscreen)
}
function setupPlaylist() {
function togglePlaylist(event) {
const h = 'hidden'
var about = document.querySelector('#wrapper > .about')
var playlist = document.querySelector('#playlist')
about.classList.toggle(h)
playlist.classList.toggle(h)
}
document.querySelectorAll('#playlist-show, #playlist-hide')
.forEach(function(button) {
button.addEventListener('click', togglePlaylist)
})
}

215
style.css Normal file

@ -0,0 +1,215 @@
:root {
--main-width: 75vw;
--main-height: 100vh;
--secondary-width: 25vw;
--secondary-height: 33vh;
--background: black;
--foreground: white;
--highlight: #9BB2CE;
--controls-height: 28px;
}
* {
position: relative;
}
body {
margin: 0;
padding: 0;
display: grid;
align-items: center;
justify-items: center;
background-color: var(--background);
color: var(--foreground);
}
svg {
height: 24px;
width: 24px;
fill: var(--foreground);
}
button {
background: none;
border: none;
color: var(--foreground);
}
.nocursor {
cursor: none;
}
.nocursor .about,
.nocursor #controls,
.nocursor .main > svg,
.nocursor .secondary > svg {
display: none !important;
}
#wrapper {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: 1fr 1fr 1fr;
grid-auto-columns: 1fr;
grid-auto-rows: 1fr;
}
.about {
margin-top: 1em;
margin-left: 1em;
display: flex;
}
#wrapper > .about {
position: absolute;
top: 0;
left: 0;
}
h1 {
margin: 0;
padding: 0;
font-size: 120%;
}
video {
object-fit: contain;
width: 100%;
height: 100%;
}
.main, .secondary {
display: grid;
align-items: center;
justify-items: center;
}
.main {
grid-row: 1 / 4;
grid-column: 1 / 4;
}
.secondary {
grid-column-start: 4;
}
.main > video {
grid-row: 1 / 2;
grid-column: 1 / 2;
max-height: var(--main-height);
max-width: var(--main-width);
}
.secondary > video {
grid-row: 1 / 2;
grid-column: 1 / 2;
max-height: var(--secondary-height);
max-width: var(--secondary-width);
}
.main > svg,
.secondary > svg {
grid-row: 1 / 2;
grid-column: 1 / 2;
display: none;
}
.main:hover > svg,
.secondary:hover > svg {
display: block;
opacity: 60%;
}
.main > svg {
width: calc(minmax(720px, var(--main-height)/2));
height: calc(minmax(720px, var(--main-height)/2));
}
.secondary > svg {
width: calc(var(--secondary-height)/2);
height: calc(var(--secondary-height)/2);
}
#controls {
position: absolute;
width: 100%;
bottom: 0;
left: 0;
display: grid;
grid-template-areas: "progress progress" "left right";
background-color: rgba(0, 0, 0, 0.7);
}
#progress-container {
grid-area: progress;
height: 20px;
display: grid;
grid-template-areas: "bar";
justify-items: start;
align-items: end;
}
#progress, #buffer {
grid-area: bar;
}
#buffer {
/* DEBUG */
width: 50%;
/* END DEBUG */
height: 8px;
background-color: gray;
}
#progress {
height: 8px;
background-color: var(--highlight);
}
.left-controls {
grid-area: left;
display: flex;
height: var(--controls-height);
}
#speed-list {
display: none;
position: absolute;
left: 0;
bottom: 0;
padding: 0;
margin-top: 0;
margin-bottom: var(--controls-height);
transform: translate(-25%);
list-style: none;
}
#speed-list {
background-color: darkgray;
}
#speed-list button {
width: 100%;
text-align: right;
}
#speed-select button::after {
content: "x";
}
#speed-list button:hover,
#speed-current:hover,
#speed-select:focus-within #speed-current {
background-color: var(--highlight);
}
#speed-select:focus-within #speed-list,
#speed-select:hover #speed-list {
display: block;
}
.right-controls {
grid-area: right;
display: flex;
justify-content: flex-end;
height: var(--controls-height);
}
#playlist {
position: absolute;
top: 0;
left: 0;
height: 100%;
display: flex;
flex-direction: column;
}
/* small screens */
@media screen and (aspect-ratio < 16/9) {
:root {
--main-width: 100vw;
--main-height: 75vh;
--secondary-width: 33vw;
--secondary-height: 25vh;
}
.secondary {
grid-column-start: initial;
grid-row-start: 4;
}
}
/* playlist hiding */
.hidden {
display: none !important;
}