Dup Ver Goto 📝

VideoStreaming_FrontEnd_01

PT2/lang/php/video does not exist
To
298 lines, 647 words, 5999 chars Page 'VideoStreaming_FrontEnd_01' does not exist.

Series or Directories

This assumes all video files are contained in folders 1-deep, e.g.

RedDwarf_s1/...
RedDwarf_s1/...

It then presents a list of collapsable

<!DOCTYPE html>
<html>
  <head>
    <meta charset='utf8'/>
    <title>Flix Series</title>
  </head>
  <body>
<?php
$mp4ds = glob("*/");

foreach($mp4ds as $d) {
  $d = preg_replace('@/+$@','',$d);
  $xs = array_merge(glob("$d/*.mp4"),glob("$d/*.m4a"),glob("$d/*.mp3"));
  if( count($xs) > 0 ) {
    echo "<section class='collapse'>\n";
    echo "<h1>$d</h1>\n";
    if( count($xs) > 1 ) {
      echo "<p><a href='playlist.php?$d' class='playlist'>$d.m3u</a></p>\n";
    }
    echo "<ul>\n";
    foreach($xs as $mp4) {
      $mp4n = preg_replace('@^.*/@',"",$mp4);
      echo "<li><a href='stream.php?$mp4'>$mp4n</a></li>\n";
    }
    echo "</ul>\n";
    echo "</section>\n";
  }
}

?>
  </body>
<script>

window.q = (x,y=document) => y.querySelector(x)
window.qq = (x,y=document) => Array.from(y.querySelectorAll(x))

function acms(e) {
    let key = e.key
    const code = e.code.toLowerCase()
    if( code === "backquote" ) { key = "`" }
    if( code.startsWith("digit") ) { key = code.substr(5) }
    if( code === "space" ) { key = "space" }
    let t = key.toLowerCase()
    if( e.shiftKey ) t = "S-"+t
    if( e.metaKey ) t = "M-"+t
    if( e.ctrlKey ) t = "C-"+t
    if( e.altKey ) t = "A-"+t
    return t
}

function docopy(text) {
  const textArea = document.createElement("textarea");
  textArea.value = text;
  document.body.appendChild(textArea);
  textArea.focus();
  textArea.select();
  try {
    document.execCommand('copy');
    toast("copied",text)
  } catch (err) {
    console.error('Unable to copy to clipboard', err);
  }
  document.body.removeChild(textArea);
}

function toast(msg,duration=3000) {
    const dialog = document.createElement("dialog")
    dialog.classList.add("toast")
    dialog.classList.add("bold")
    dialog.classList.add("green")
    dialog.classList.add("pre")
    dialog.textContent = msg
    dialog.addEventListener("click",e => {
        e.preventDefault()
        dialog.close()
        dialog.remove()
    })
    document.body.append(dialog)
    dialog.showModal()
    setTimeout(_ => {
        dialog.close()
        dialog.remove()
    },duration)
}

qq("a.playlist").forEach( x => {
  x.addEventListener("click", e => {
    if( e.altKey ) {
      e.preventDefault()
      const href = x.href
      do_copy(href)
    }
  })  
})

function pd(e) { e.preventDefault() }
window.addEventListener("keydown",e => {
  const k = acms(e)
  switch(k) {
  case "-":
  case "S-_":
   pd(e)
   qq("section").forEach(x => x.classList.add("collapse"))
   return; 
  case "=":
  case "S-+":
   pd(e)
   qq("section").forEach(x => x.classList.remove("collapse"))
   return; 
  }
  if( k.match(/^[a-z]$/) ) {
   pd(e)
   let letter = k.toUpperCase()
   let sel = `section[letter="${letter}"]`
   let sect = q(sel)
   if( sect ) {
    qq("section").forEach(x => x.classList.add("collapse"))
    sect.classList.remove("collapse")
    sect.scrollIntoView(true)
   }
   return
  }
})

const sections = qq("section")
sections.forEach( s => {
  const h1 = q("h1",s)
  if( h1 ) {
    h1.addEventListener("click", e => {
      e.preventDefault()
      s.classList.toggle("collapse")
    })
  }
})

const links = qq("a")
links.forEach( a => {
  a.addEventListener("click",e => {
    if( e.shiftKey && ! ( e.ctrlKey || e.altKey || e.metaKey ) ) {
      e.preventDefault()
      do_copy(a.href)
    }
  })
})


function do_copy(text,elt=document.body) {
  const textArea = document.createElement("textarea")
  elt.appendChild(textArea)
  textArea.value = text
  textArea.style.position = "fixed"
  const miles = "-999999px"
  textArea.style.left = miles
  textArea.style.top = miles
  textArea.focus()
  textArea.select()
  return new Promise((res, rej) => {
    if( document.execCommand('copy') ) {
      toast(`Copied: ${textArea.value}`)
      textArea.remove()
      res()
    } else {
      toast(`Didn't copy: ${text}`)
      textArea.remove()
      rej()
    } 
  })
}
const toast_time = 800
function toast(msg,the_toast_time=toast_time) {
  const dialog = document.createElement("dialog")
  dialog.classList.add("toast")
  dialog.classList.add("bold")
  dialog.classList.add("green")
  dialog.textContent = msg
  dialog.addEventListener("click",e => {
    e.preventDefault()
    dialog.close()
    dialog.remove()
  })
  document.body.append(dialog)
  dialog.showModal()
  setTimeout(_ => {
    dialog.close()
    dialog.remove()
  },the_toast_time)
}

</script>
<style>
body {
  background-color: #007;
}
section.collapse {
  background-color: #ccc;
  display: inline-block;
}
section.collapse ul {
  display: none;
}
section.collapse p {
  display: none;
}
section p {
  padding-left: 1rem;
}
section ul {
  list-style-type: none;
}
section {
  position: relative;
  padding: 0.3rem;
  margin: 0.5rem;
  border: 1px solid black;
  box-shadow: 0.5rem 0.5rem 0.5rem black;
  background-color: white;
  color: black;
}
section h1 {
  width: 100%;
  cursor: pointer;
}
section.collapse h1 {
  background-color: #ddd;
}
section.collapse h1:hover {
  background-color: #dfd;
}
section h1:hover {
  color: #070;
}
section.collapse:has(h1:hover) {
  background-color: #dfd;
}

a {
  text-decoration: none;
}

/* Media */
a[href$=mp3] {
  color: #700;
}
a[href$=m4a] {
  color: #070;
}
a[href$=mp4] {
  color: #007;
}
a[href$=mp3]::before {
  content: "?? ";
}
a[href$=m4a]::before {
  content: "?? ";
}
a[href$=mp4]::before {
  content: "?? ";
}

/* Playlist */
a.playlist {
  color: #505;
}
a.playlist::before {
  content: "?? ";
}

/* Toast */
dialog.toast {
  border: 2px solid black;
  font-size: 2rem;
  padding: 1.5rem;
  box-shadow: 1rem 1rem 1rem black; 
  width: 60vw;
  font-family: "Optima", sans-serif;
}
dialog.toast.bold {
  font-weight: bold;
}
dialog.toast.green {
  background-color: #070;
  color: white;
}
dialog.toast.pre {
  white-space: pre-wrap;
}

</style>
</html>