Search code examples
javascripthtmlbase64html5-videobase64url

HTML Video Tag's Source Doesn't Work With Base64 Code


My project is about user chooses a directory and then the program takes all the videos inside the directory and via help of a video player then shows the content of the video. This photo might explain the project layout: (Right side is where you choose the video and left side is where the video plays)

Project Layout

I have used vanilla javascript so far. here is the code:

HTML:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="style.css">
    <title>Video Player</title>
</head>
<body>
    <div class="select-folder-div">
        <input type="file" name="file" id="file-selector" class="file-selector" 
webkitdirectory multiple>
    </div>
    <div class="main-div">
        <div class="video-player-div" id="video-player-div">

        </div>
        <div class="video-chooser-div" id="video-chooser-div">
        
        </div>
    </div>
    <script src="index.js"></script>
</body>
</html>

CSS:

body {
    background: rgb(34, 32, 32);
    color: rgb(204, 87, 204);
    font-size: 1.5em;
    font-family:'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans 
Unicode', Geneva, Verdana, sans-serif;
}

.main-div {
    display: flex;
    flex-direction: row;
}

.video-player-div{
    display: flex;
    align-items: center;
    align-self: flex-start;
    border: 5px solid rgb(204, 87, 204);
    width: 70%;
    margin-top: 5%;
    margin-bottom: 10%;
    margin-left: 3%;
    margin-right: 3%;
    padding: 3%;
}

.video-chooser-div {
    display: flex;
    flex-direction: column;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: 20px;
    border: 5px solid rgb(204, 87, 204);
    width: 21%;
    margin-top: 5%;
    margin-bottom: 10%;
    margin-right: 3%;
    padding: 3%;
    overflow: auto;
}

.file-selector {
    margin-left: 45%;
    margin-top: 3%;
}

.video-name:hover {
    cursor: pointer;
    color: blueviolet;
}

.video-name:active {
    color: rgb(99, 14, 178);
}

Javascript:

const directoryInput = document.getElementById("file-selector");
const videoChooser = document.getElementById("video-chooser-div");
const videoPlayer = document.getElementById("video-player-div");

directoryInput.addEventListener("change", (event) => {
    videoChooser.innerHTML = "";

    for (const file of event.target.files) {
        if (file.name.endsWith(".mp4")) {
            let videoContentDiv = document.createElement("div");
            videoContentDiv.className = "video-content";

            let videoName = file.name;
            let videoNameTag = document.createElement("p");
            videoNameTag.className = "video-name";
            videoNameTag.textContent = videoName;

            videoContentDiv.appendChild(videoNameTag);

            videoChooser.appendChild(videoContentDiv);

            videoContentDiv.addEventListener("click", () => {
                videoPlayer.innerHTML = "";

                const video = document.createElement("video");

                const reader = new FileReader();
                reader.onload = (event) => {
                    video.src = event.target.result;
                    console.log(video.src)
                };
                reader.readAsDataURL(file); 
            
                video.width = "100%";
                video.controls = true;

                videoPlayer.appendChild(video);
            });
        }
    }
}, false);

The Problem is I am able to select a directory and show all the mp4 contents inside it however when I click on the text the video tag doesn't show up. When I check what is inside the div there is indeed a video tag with a base64 code as source, but there is nothing on the screen not even an empty video place. I tried to check the base64, and I think I realized there are empty parts inside the base64 as I scrolled down, and I am not sure if it is normal. Even it is I don't know how to solve it.


Solution

  • You can simplify your code.
    Add a static <video> element to your HTML and hide it via css if src is empty

    video[src=""] {
      opacity: 0
    }
    

    On upload you create an object URL URL.createObjectURL(file); and save it to the new playlist item.

    Then you're adding a click event swapping the current video src attribute with the playlist item's object URL (e.g saved in a data attribute).

    const directoryInput = document.getElementById("file-selector");
    const videoChooser = document.getElementById("video-chooser-div");
    const videoPlayer = document.getElementById("video-player-div");
    
    directoryInput.addEventListener("change", (event) => {
      videoChooser.innerHTML = "";
    
      for (const file of event.target.files) {
    
        if (file.name.endsWith(".mp4")) {
          let videoContentDiv = document.createElement("div");
          videoContentDiv.className = "video-content";
    
          // add playlist items
          let videoName = file.name;
          let videoNameTag = document.createElement("p");
          videoNameTag.className = "video-name";
          videoNameTag.textContent = videoName;
    
          // create object url and save it in data attribute 
          videoNameTag.dataset.src = URL.createObjectURL(file);
    
          // play playlist item on click
          videoNameTag.addEventListener('click', e => {
            video.src = e.currentTarget.dataset.src;
            video.play()
          })
    
          videoContentDiv.appendChild(videoNameTag);
          videoChooser.appendChild(videoContentDiv);
    
        }
      }
    }, false);
    body {
      background: rgb(34, 32, 32);
      color: rgb(204, 87, 204);
      font-size: 1.5em;
      font-family: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans 
     Unicode', Geneva, Verdana, sans-serif;
    
    }
    
    .main-div {
      display: flex;
      flex-direction: row;
    }
    
    .video-player-div {
      display: flex;
      align-items: center;
      align-self: flex-start;
      border: 5px solid rgb(204, 87, 204);
      width: 70%;
      margin-top: 5%;
      margin-bottom: 10%;
      margin-left: 3%;
      margin-right: 3%;
      padding: 3%;
    }
    
    .video-chooser-div {
      display: flex;
      flex-direction: column;
      flex-wrap: wrap;
      align-items: flex-start;
      gap: 20px;
      border: 5px solid rgb(204, 87, 204);
      width: 21%;
      margin-top: 5%;
      margin-bottom: 10%;
      margin-right: 3%;
      padding: 3%;
      overflow: auto;
    }
    
    .file-selector {
      margin-left: 45%;
      margin-top: 3%;
    }
    
    .video-name:hover {
      cursor: pointer;
      color: blueviolet;
    }
    
    .video-name:active {
      color: rgb(99, 14, 178);
    }
    
    /* hide empty video elements */
    video[src=""] {
      opacity: 0
    }
    <div class="select-folder-div">
      <input type="file" name="file" id="file-selector" class="file-selector" multiple>
    </div>
    <div class="main-div">
      <div class="video-player-div" id="video-player-div">
    
        <video id="video" style="width:100%; height:100%" src="" controls>
        </video>
      </div>
      <div class="video-chooser-div" id="video-chooser-div">
      </div>
    </div>

    This approach should be more efficient than creating a base64 data URL. Also, you might encounter problems due to URL length limitations when using base64.