1

No one on here ever comments on my questions but maybe this is an easy one? I'm new to coding and I am trying to find a way to navigate a video, as in play the first 10 seconds then stop, play seconds 30-40 then stop, play second 60-30 then stop, etc and to have buttons for it. I have found a tutorial but the buttons don't respond. I've cleaned up the code a bit (don't think I removed anything necessary) and pasted it below. Tutorial link: https://www.sitepoint.com/html5-video-fragments-captions-dynamic-thumbnails/

HTML:

<head>

    <meta charset="UTF-8">
    <title>Untitled Document</title>

    <link href="style.css" rel="stylesheet" type="text/css">
    <script type="text/javascript" src="script.js"></script>

</head>

<body>

<section class="wrapper">

  <div>
    <video id="frag1" controls preload="metadata">

      <source src="assets/disney.mp4"  type='video/mp4;codecs="avc1.42E01E, mp4a.40.2"' data-original="assets/disney.mp4">

      <source src="assets/disney.mp4" type='video/webm;codecs="vp8, vorbis"' data-original="assets/disney.mp4">

    </video>
    <nav>
      <button data-start="0">Section One</button>
      <button data-start="6">Section Two</button>
      <button data-start="17">Section Three</button>
    </nav>
  </div>

</section>


</body>

</html>

CSS:

@charset "UTF-8";

#main { width: 85%; margin: 1em auto; }

section>div { margin: 0 1.5% 1.5% 0; padding: 1.5%; float: left; background-color: #efefef; }


video { width: 100%; min-height: 430px; }
button { cursor: pointer; }
nav>button { margin: 0.27em; }
nav>button:first-child { margin-left: 0; }
.wrapper { width: 520px; margin: auto; overflow: hidden; text-align: center; }

.p {
  text-align: center;
  padding-top: 120px;
}

Resources 1×0.5×0.25× Rerun

JAVASCRIPT:

function mediaFragOne() 
{
var video, sources, nav, buttons;

video = document.querySelector('video#frag1');
sources = video.getElementsByTagName('source');
nav = document.querySelector('video#frag1+nav');
buttons = nav.getElementsByTagName('button');

for(var i = buttons.length - 1; i >= 0; i--) 
{
    buttons[i].addEventListener('click', function() {
            for (var i = sources.length - 1; i >= 0; i--) {
                sources[i].setAttribute(
                    'src', (sources[i].getAttribute('data-original')
                    .concat('#t=' + this.getAttribute('data-start'))));
                    video.load();
                    video.play();
            };
        });
    }
}
mediaFragOne();

3 Answers3

1

Use Event Delegation when you have multiple tags to click.

  1. Find an ancestor tag (ex. <nav>) that all of the target tags (ex. <a>) have in common. Register the ancestor tag to the event. Now whenever the ancestor tag or its descendant tags triggers the registered event, the callback function bound to the ancestor tag will fire. So essentially that's one event listener for an unlimited number of target tags.

    document.querySelector(ancestor).addEventListener('click', callback);
    
  2. Always have callback function pass the Event Object.

    function callback(event) {...
    
  3. Determine the tag that is the origin of event (button clicked, video time updated, etc...) by referencing Event.target.

    const clicked = event.target
    
  4. In the callback function it should only apply to the target tags and exclude all other tags.

    if (clicked.matches('a')) {...
    ...}
    return false;
    

Details commented in demo

<!DOCTYPE html>
<html>

<head>
  <meta charset='utf-8'>
  <style>
     :root {
      font: 400 2.5vw/1.2 Consolas
    }
    
    .vid {
      display: block;
      margin: 0 auto;
      width: 80vw;
    }
    
    .btns {
      display: flex;
      justify-content: center;
      width: 80vw;
      margin: 0 auto;
    }
    
    a {
      display: block;
      margin: 5px 8px;
      border: 1px solid #000;
      border-radius: 8px;
      text-decoration: none;
      text-align: center;
      padding: 3px 5px;
      width: 15vw;
    }
  </style>
</head>

<body>
  <video class='vid' src='https://storage04.dropshots.com/photos6000/photos/1381926/20170326/005610.mp4' controls></video>
  <nav class='btns'>
    <a href='#/' data-start='00' data-end='10'>00 - 10</a>
    <a href='#/' data-start='10' data-end='20'>10 - 20</a>
    <a href='#/' data-start='20' data-end='30'>20 - 30</a>
    <a href='#/' data-start='30' data-end='40'>30 - 40</a>
  </nav>
  <script>
    // Declare end
    let end;
    // Reference the parent tag of all buttons
    const btns = document.querySelector('.btns');
    // Reference the video
    const vid = document.querySelector('.vid');

    // Register the click event to nav.btns
    btns.addEventListener('click', frag);

    // Register the timeupdate event to video.vid
    vid.addEventListener('timeupdate', stop);

    // Pass Event Object
    function frag(event) {
      // Event.target always points to the clicked button, time-updated video, etc.
      const clicked = event.target;

      // if clicked tag is an <a>...
      if (clicked.matches('a')) {
        // Get the value of its data-start and convert it to a real number
        let start = Number(clicked.dataset.start);
        // Set end to the value of it data-end and convert it to a real number
        end = Number(clicked.dataset.end);
        // Set video current time to value of start
        vid.currentTime = start;
        // Play video
        vid.play();
      }
      // End function
      return false;
    }

    // Pass Event Object
    function stop(event) {
      // Reference the video
      const vid = event.target;

      // if it is a <video>...
      if (vid.matches('video')) {
        // and if that video time is currently at or past the value of end...
        if (vid.currentTime >= end) {
          // Pause video
          vid.pause();
        }
      }
      // End function
      return false;
    }
  </script>
</body>

</html>
zer00ne
  • 41,936
  • 6
  • 41
  • 68
  • Thanks, I know it's dull but it has a visual representation of the time segments – zer00ne Jul 21 '19 at 23:27
  • Thanks for this detailed explanation that helps a lot, as well as the additional reading. Unfortunately it still doesn't work on my end though. The page loads but the buttons don't respond. None of the code that people have posted here responds I'm starting to wonder if there isn't a problem on my end? I'm just wondering what the heck it could be. I'm testing the page both in dreamweaver and in chrome through terminal using python -m SimpleHTTPServer –  Jul 22 '19 at 18:31
  • I just thought to check the console and it's giving me this error (I can't format it because every time I hit enter this comment just saves: script.js:6 Uncaught TypeError: Cannot read property 'getElementsByTagName' of null at mediaFragOne (script.js:6) at script.js:27 –  Jul 22 '19 at 18:36
  • On the advice above I uploaded this one to my server too and now it's giving me a different error message in the console: Uncaught TypeError: Cannot read property 'addEventListener' of null at script.js:9 –  Jul 22 '19 at 18:55
  • Hi @epignosis567 try using my code on a blank page, I'll update the snippet so it's a complete page. The answer is standalone and can easily be adapted to a normal page but care must be taken when integrating it into a page with pre-existing HTML, CSS, and JavaScript (especially CSS and more so JS). Concerning the first error, my answer doesn't use `document.getElementsByTagName(`**tagName**`)`. But if I were in need to collect elements by `tagName` -- I'd use `document.querySelectorAll(`'**tagName**`)` because the former fails in certain situations such as a `for` loop that mutates lengths. – zer00ne Jul 22 '19 at 20:25
  • @epignosis567 also I noticed that you are using an external JS file. I suggest that any `` and `` be placed before the closing `

    ` tag.

    – zer00ne Jul 22 '19 at 20:34
  • Yay this one is working! Thank you so much this gives me something to dig into. I created a new html file in dreamweaver and pasted it in, I will work on adapting it and post back. This really helps me learn having something to dissect. Thank you again!! –  Jul 22 '19 at 21:14
  • No problem. Happy coding! – zer00ne Jul 22 '19 at 21:40
  • ^^^ This is how code should be taught (if it isn't). I don't know I'm just trying to figure it out but files like this with comments are enormously helpful fyi. The only reason I'm bothering trying to learn code again is because I've discovered P5.js and their excellent documentation. I only mention it because I know people here are interested in this stuff. Thank you again –  Jul 23 '19 at 00:46
  • That's great @epignosis567 remember to click the checkmark ✅ to accept this answer if and when it has resolved your problem. – zer00ne Jul 23 '19 at 01:08
  • I noticed the script code for this can only be linked in the body, I always thought the head and body were supposed to behave the same in terms of linking files. I wonder if that was causing me problems in the beginning. –  Jul 23 '19 at 01:09
  • A strong possibility script 99% of the time is best placed at the position right after all of the HTML has been rendered (before the closing `

    ` tag). That guarantees that before any jQ/JS is ran that the HTML exists otherwise if a script happens to reference a `

    – zer00ne Jul 23 '19 at 02:07
0

This is easier than you're making it to be. Just set the currentTime property.

Untested, but this should get you started:

document.querySelector('nav').addEventListener('click', (e) => {
  if (!e.target.matches('[data-start]')) {
    return;
  }
  document.querySelector('video').currentTime = Number.parseFloat(e.target.dataset.start);
});
Brad
  • 159,648
  • 54
  • 349
  • 530
  • Thanks a lot but that's over my head I guess. I've been trying to follow that tutorial, which I agree seems like it's more complicated than it needs to be. I don't understand why it works in the codepen but not when I copy and paste it. I appreciate you trying to help, and I would welcome any alternative ways of achieving it, but I'm honestly not sure what to do with that code you typed. I tried pasting it into my js file but it didn't work. –  Jul 21 '19 at 22:33
  • @epignosis567 There was an error in this code. Fixed it. All this code does is add a `click` handler for the `nav` element, which will get fired if any of those buttons inside of it are clicked. (We then check to make sure it was one of those buttons that was clicked, by checking the event target [what was clicked] for the `data-start` attributed.) Then, we just set the `currentTime` of the video element to be the number that was in the `data-start` attribute. (This is accessible via the `dataset` property.) – Brad Jul 21 '19 at 23:10
  • Thanks and I really appreciate the help but it's still not working on my end. I'm trying to figure it out now. –  Jul 22 '19 at 18:27
  • The console is giving me this message: Uncaught SyntaxError: missing ) after argument list script.js:2 –  Jul 22 '19 at 18:41
  • @epignosis567 Fixed another typo... note that the bracket for `[data-start]` was outside of the string. – Brad Jul 22 '19 at 19:08
  • script.js:3 Uncaught SyntaxError: Unexpected identifier –  Jul 22 '19 at 19:26
0

Just to elaborate @Brand answer, you can do it like that:

var vid = document.getElementById("frag1"); // set video to variable
var buttons = document.getElementsByTagName("button"); // element collection
for (var i = 0; i < buttons.length; i++) { // run for loop
  buttons[i].addEventListener("click", function() { // add event listenr
    var dataStart = this.getAttribute('data-start'); // extract start time
    var dataEnd = this.getAttribute('data-end'); // extract end time
    var dataDuration = Number(dataEnd - dataStart + '000'); // make the duration will assist on the setTimeOut
    vid.currentTime = dataStart; // this is the magic
    vid.play();
    window.setTimeout(function(){ vid.pause(); }, dataDuration);// play according to duration   
  });
}
<section class="wrapper">
  <div>
    <video id="frag1" controls preload="metadata" width="400px">
      <source src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4">
      <source src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.ogg">
    </video>
    <nav>
      <button data-start="0" data-end="10">Section One</button>
      <button data-start="30" data-end="40">Section Two</button>
      <button data-start="30" data-end="60">Section Three</button>
    </nav>
  </div>
</section>

You can find a lot more information about this here and here.

Hope that helps.

A. Meshu
  • 4,053
  • 2
  • 20
  • 34
  • Thank you. So here's my question: I can click on run code snippet and it looks like it works on this page, but when I copy in over to my files it suddenly doesn't work. I'll read up on the references you linked but do you have any idea why this isn't working? What you've written above is what I'm trying to do. –  Jul 21 '19 at 22:59
  • yes i had mistake with dataDuration - forgot to add '000'. check now. – A. Meshu Jul 21 '19 at 23:19
  • Hi. Yes I'm just now looking at it. I'm trying but it's still not working. The page loads but the buttons don't respond. I'll keep playing with it though. –  Jul 22 '19 at 18:26
  • If I click 'run code snippet' on this page it works, but if I copy it into my html editing program and load the page via terminal using python -m SimpleHTTPServer it doesn't work... –  Jul 22 '19 at 18:34
  • I just checked the console but it's not giving me any error messages, the buttons just don't work. –  Jul 22 '19 at 18:39
  • https://stackoverflow.com/questions/18035433/audio-video-streaming-fails-using-simplehttpserver – A. Meshu Jul 22 '19 at 18:40
  • Thanks I was unaware of that. I uploaded it to my server space but it still doesn't work... –  Jul 22 '19 at 18:52
  • Since it work here i guess it local error, glad you came with a solution though.. (: – A. Meshu Jul 23 '19 at 19:53